diff --git a/.clang-format b/.clang-format index 78ca1364..d6f60e37 100644 --- a/.clang-format +++ b/.clang-format @@ -10,7 +10,7 @@ BraceWrapping: AfterFunction: true AfterStruct: false AfterUnion: false - AfterExternBlock: true + AfterExternBlock: false BeforeElse: false BeforeWhile: false IndentCaseLabels: false @@ -22,6 +22,8 @@ BinPackArguments: false BinPackParameters: false AllowAllArgumentsOnNextLine: false AllowShortFunctionsOnASingleLine: Empty -AlwaysBreakAfterReturnType: None +BreakAfterReturnType: ExceptShortType +AlwaysBreakAfterReturnType: AllDefinitions AlignEscapedNewlines: Left ForEachMacros: ['ssh_callbacks_iterate'] +AlignConsecutiveMacros: 'Consecutive' diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..459f5916 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +src/external/* diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 00000000..f4117928 --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,6 @@ +--- +line_width: 80 +tab_size: 4 +use_tabchars: false +separate_ctrl_name_with_space: true +separate_fn_name_with_space: false diff --git a/.editorconfig b/.editorconfig index 59eb92bc..bb3dfbb0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,12 @@ indent_style = space indent_size = 4 tab_width = 4 -[{CMakeLists.txt,*.cmake}] +[CMakeLists.txt] indent_style = space indent_size = 4 -tab_width = 4 \ No newline at end of file +tab_width = 4 + +[*.cmake] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.gitignore b/.gitignore index 47eb46ac..aebe55f1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ compile_commands.json tags /build /obj* +doc/tags.xml +.DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3ee5286..258a1054 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ variables: BUILD_IMAGES_PROJECT: libssh/build-images CENTOS8_BUILD: buildenv-c8s CENTOS9_BUILD: buildenv-c9s + CENTOS10_BUILD: buildenv-c10s FEDORA_BUILD: buildenv-fedora MINGW_BUILD: buildenv-mingw TUMBLEWEED_BUILD: buildenv-tumbleweed @@ -15,13 +16,28 @@ stages: - test - analysis -.build: - stage: build +# This is some black magic to select between branch pipelines and +# merge request pipelines to avoid running same pipelines 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_options: 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" - CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DWITH_BENCHMARKS=ON" + CMAKE_DEFAULT_DEBUG_OPTIONS: "-DCMAKE_C_FLAGS='-O0 -g -ggdb' -DPICKY_DEVELOPER=ON" + CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_FIDO2=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 -DGSSAPI_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON" CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS + +.build: + extends: .build_options + stage: build before_script: &build - uname -a - cat /etc/os-release @@ -36,7 +52,11 @@ stages: make -j$(nproc) install # Do not use after_script as it does not make the targets fail tags: - - shared + - saas-linux-small-amd64 + only: + - merge_requests + - branches + except: - tags artifacts: @@ -59,12 +79,46 @@ stages: .fedora: extends: .tests image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - variables: - CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON .tumbleweed: extends: .tests image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + script: + # torture_gssapi_key_exchange_null is excluded because of a bug + # https://bugzilla.opensuse.org/show_bug.cgi?id=1254680 + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + ctest --output-on-failure -E "^torture_gssapi_key_exchange_null$" + +.centos: + extends: .tests + variables: + OPENSSL_ENABLE_SHA1_SIGNATURES: 1 + +.centos10: + extends: .centos + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS10_BUILD + +.centos9: + extends: .centos + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + +# Unit testing only, no client and pkd testing, because cwrap is not available +# for MinGW +.mingw: + extends: .tests + variables: + WINEDEBUG: -all + script: + - $WINEBIN $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON + -DWITH_SERVER=ON + -DWITH_ZLIB=ON + -DWITH_PCAP=ON + -DWITH_FIDO2=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure -E torture_rand .fips: extends: .tests @@ -82,30 +136,78 @@ stages: - 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,pendin || 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/shellcheck.sh || ERROR=1; + exit $ERROR + # the format is not always matching our intentions + allow_failure: true + tags: + - saas-linux-small-amd64 + only: + - merge_requests ############################################################################### # CentOS builds # ############################################################################### -centos9s/openssl_3.0.x/x86_64: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD - extends: .tests +centos10s/openssl_3.5.x/x86_64: + extends: .centos10 variables: - CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON script: - - 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: +centos10s/openssl_3.5.x/x86_64/fips: extends: .fips - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS10_BUILD + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON + OPENSSL_ENABLE_SHA1_SIGNATURES: 1 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 +centos9s/openssl_3.5.x/x86_64: + extends: .centos9 + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON + script: + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + ctest --output-on-failure + +centos9s/mbedtls_2.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + extends: .tests + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_BLOWFISH_CIPHER=OFF" + +centos9s/openssl_3.5.x/x86_64/fips: + extends: .fips + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + variables: + OPENSSL_ENABLE_SHA1_SIGNATURES: 1 + script: + # torture_gssapi_key_exchange_* tests are excluded because gssapi-keyex is disabled + # by OpenSSH in FIPS mode in RHEL 9 + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure -E "^torture_gssapi_key_exchange.*" + centos8s/openssl_1.1.1/x86_64: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD extends: .tests @@ -120,9 +222,11 @@ centos8s/openssl_1.1.1/x86_64/fips: extends: .fips image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS8_BUILD script: + # torture_gssapi_key_exchange_* and torture_gssapi_server_key_exchange_* tests are excluded + # because gssapi-keyex is not allowed in FIPS mode in RHEL 8 - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && - OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure + OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure -E "^torture_gssapi.*key_exchange.*" ############################################################################### # Fedora builds # @@ -135,20 +239,22 @@ fedora/docs: extends: .build image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD script: - - cmake .. && make docs_coverage && make docs + - cmake -DWITH_INTERNAL_DOC=ON .. && make docs_coverage && make docs coverage: '/^Documentation coverage is \d+.\d+%/' fedora/ninja: extends: .fedora image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + variables: + CTEST_OUTPUT_ON_FAILURE: 1 script: - - cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && CTEST_OUTPUT_ON_FAILURE=1 ninja test + - cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && ninja test fedora/coverage: extends: .fedora image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD variables: - CMAKE_ADDITIONAL_OPTIONS: -DCMAKE_BUILD_TYPE=Coverage + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_BUILD_TYPE=Debug -DWITH_COVERAGE=ON" script: - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && @@ -162,33 +268,31 @@ fedora/coverage: coverage_format: cobertura path: obj/coverage_xml.xml -fedora/openssl_3.0.x/x86_64: +fedora/openssl_3.x/x86_64: extends: .fedora -fedora/openssl_3.0.x/x86_64/pkcs11-provider: +fedora/openssl_3.x/x86_64/pkcs11-provider: variables: CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON extends: .fedora -fedora/openssl_3.0.x/x86_64/minimal: +fedora/openssl_3.x/x86_64/minimal: extends: .fedora variables: script: - cmake $CMAKE_DEFAULT_OPTIONS + -DWITH_EXEC=OFF -DWITH_SFTP=OFF -DWITH_SERVER=OFF -DWITH_ZLIB=OFF -DWITH_PCAP=OFF + -DWITH_GSSAPI=OFF + -DWITH_GEX=OFF -DUNIT_TESTING=ON - -DCLIENT_TESTING=ON - -DWITH_GEX=OFF .. && + -DCLIENT_TESTING=ON .. && 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 +.valgrind: extends: .fedora stage: analysis script: @@ -197,6 +301,54 @@ fedora/valgrind: make test_memcheck - cat Testing/Temporary/MemoryChecker.*.log | wc -l | grep "^0$" +fedora/libressl/x86_64: + extends: .fedora + stage: test + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + variables: + LIBRESSL_VERSION: "4.2.1" + CMAKE_ADDITIONAL_OPTIONS: > + -DCMAKE_C_FLAGS="-I/opt/libressl/include" + -DOPENSSL_ROOT_DIR=/opt/libressl + -DOPENSSL_INCLUDE_DIR=/opt/libressl/include + -DOPENSSL_CRYPTO_LIBRARY=/opt/libressl/lib/libcrypto.so + -DOPENSSL_SSL_LIBRARY=/opt/libressl/lib/libssl.so + -DWITH_GSSAPI=OFF + -DWITH_FIDO2=OFF + before_script: + - *build + - dnf install -y perl-core autoconf automake libtool pkgconf-pkg-config + - curl -LO https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz + - tar xf libressl-${LIBRESSL_VERSION}.tar.gz + - cd libressl-${LIBRESSL_VERSION} + - ./configure --prefix=/opt/libressl + - make -j$(nproc) + - make install + - cd .. + script: + - export PKG_CONFIG_PATH=/opt/libressl/lib/pkgconfig + - export LD_LIBRARY_PATH=/opt/libressl/lib + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + ctest --output-on-failure + +# The PKCS#11 support is turned off as it brings dozens of memory issues from +# engine_pkcs11 or openssl itself +fedora/valgrind/openssl: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=OFF + extends: .valgrind + +fedora/valgrind/mbedtls: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_MBEDTLS=ON + extends: .valgrind + +fedora/valgrind/libgcrypt: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_GCRYPT=ON + extends: .valgrind + # 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 @@ -250,47 +402,26 @@ 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" -fedora/mbedtls/x86_64: +fedora/mbedtls_3.x/x86_64: extends: .fedora variables: - CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON " + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON" -# Unit testing only, no client and pkd testing, because cwrap is not available -# for MinGW fedora/mingw64: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD - extends: .tests - script: - - export WINEPATH=/usr/x86_64-w64-mingw32/sys-root/mingw/bin - - export WINEDEBUG=-all - - mingw64-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 + extends: .mingw + variables: + WINEPATH: /usr/x86_64-w64-mingw32/sys-root/mingw/bin + WINEBIN: mingw64-cmake -# Unit testing only, no client and pkd testing, because cwrap is not available -# for MinGW fedora/mingw32: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$MINGW_BUILD - extends: .tests - script: - - export WINEPATH=/usr/i686-w64-mingw32/sys-root/mingw/bin - - export WINEDEBUG=-all - - mingw32-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 - + extends: .mingw + variables: + WINEPATH: /usr/i686-w64-mingw32/sys-root/mingw/bin + WINEBIN: mingw32-cmake ############################################################################### # Fedora csbuild # @@ -299,9 +430,16 @@ fedora/mingw32: stage: analysis variables: GIT_DEPTH: "100" + CSCPPC_ADD_OPTS: "--library=./.gitlab-ci/cmocka.cfg" + CMAKE_OPTIONS: "-DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON" image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD before_script: - | + # 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 @@ -311,46 +449,95 @@ fedora/mingw32: 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" + script: + - csbuild + --build-dir=obj-csbuild + --build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed + tags: - - shared + - saas-linux-small-amd64 except: - tags + only: + - merge_requests artifacts: expire_in: 1 week when: on_failure paths: - obj-csbuild/ -fedora/csbuild/openssl_3.0.x: +fedora/csbuild/openssl_3.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 @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 @SRCDIR@ && make clean && make -j$(nproc)" - --git-commit-range $CI_COMMIT_RANGE - --color - --print-current --print-fixed + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_GCRYPT=ON 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 + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_MBEDTLS=ON + +############################################################################### +# Fedora abidiff # +############################################################################### + +fedora/abidiff: + stage: analysis + variables: + GIT_DEPTH: "100" + CMAKE_OPTIONS: $CMAKE_DEFAULT_DEBUG_OPTIONS $CMAKE_BUILD_OPTIONS + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + before_script: + - uname -a + - cat /etc/os-release + - mount + - df -h + - cat /proc/swaps + - free -h + - | + # 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") + - mkdir -p obj-${CI_COMMIT_BEFORE_SHA} + - mkdir -p obj-${CI_COMMIT_SHA} + + - export INSTALL_DIR1=$(pwd)/install/${CI_COMMIT_BEFORE_SHA} + - export INSTALL_DIR2=$(pwd)/install/${CI_COMMIT_SHA} + script: + - git checkout ${CI_COMMIT_BEFORE_SHA} + - pushd obj-${CI_COMMIT_BEFORE_SHA} + - cmake ${CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR1} .. && + make -j$(nproc) && make -j$(nproc) install + - popd + - ls -l ${INSTALL_DIR1}/lib*/ + - git checkout ${CI_COMMIT_SHA} + - pushd obj-${CI_COMMIT_SHA} + - cmake ${CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR2} .. && + make -j$(nproc) && make -j$(nproc) install + - popd + - ls -l ${INSTALL_DIR2}/lib*/ + - ./.gitlab-ci/checkabi.sh ${INSTALL_DIR1} ${INSTALL_DIR2} + tags: + - saas-linux-small-amd64 + except: + - tags + only: + - merge_requests ############################################################################### # Ubuntu builds # @@ -358,12 +545,18 @@ fedora/csbuild/mbedtls: ubuntu/openssl_3.0.x/x86_64: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$UBUNTU_BUILD extends: .tests + script: + # torture_gssapi_key_exchange_null is excluded because of a bug + # https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/2134527 + - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && + make -j$(nproc) && + ctest --output-on-failure -E "^torture_gssapi_key_exchange_null$" ############################################################################### # Alpine builds # ############################################################################### -alpine/openssl_3.0.x/musl: +alpine/openssl_3.x/musl: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$ALPINE_BUILD extends: .tests script: @@ -380,12 +573,10 @@ alpine/openssl_3.0.x/musl: ############################################################################### # Tumbleweed builds # ############################################################################### -tumbleweed/openssl_3.0.x/x86_64/gcc: +tumbleweed/openssl_3.x/x86_64/gcc: extends: .tumbleweed - variables: - CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" -tumbleweed/openssl_3.0.x/x86/gcc: +tumbleweed/openssl_3.x/x86/gcc: extends: .tumbleweed script: - cmake @@ -398,12 +589,12 @@ tumbleweed/openssl_3.0.x/x86/gcc: -DUNIT_TESTING=ON .. && make -j$(nproc) -tumbleweed/openssl_3.0.x/x86_64/gcc7: +tumbleweed/openssl_3.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" -tumbleweed/openssl_3.0.x/x86/gcc7: +tumbleweed/openssl_3.x/x86/gcc7: extends: .tumbleweed script: - cmake @@ -415,18 +606,29 @@ tumbleweed/openssl_3.0.x/x86/gcc7: make -j$(nproc) && ctest --output-on-failure -tumbleweed/openssl_3.0.x/x86_64/clang: +tumbleweed/openssl_3.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++" + +tumbleweed/mbedtls-3.6.x/x86_64/gcc: + extends: .tumbleweed + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config -DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_BLOWFISH_CIPHER=OFF " + +tumbleweed/mbedtls-3.6.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 -DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_BLOWFISH_CIPHER=OFF " tumbleweed/static-analysis: extends: .tests stage: analysis image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + variables: + CCC_CC: clang + CCC_CXX: clang++ script: - - export CCC_CC=clang - - export CCC_CXX=clang++ - scan-build cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang @@ -450,19 +652,12 @@ tumbleweed/static-analysis: freebsd/openssl_1.1.1/x86_64: image: extends: .tests + variables: + CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DUNIT_TESTING=ON 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 $CMAKE_OPTIONS .. script: - - cmake $CMAKE_DEFAULT_OPTIONS - -DWITH_SFTP=ON - -DWITH_SERVER=ON - -DWITH_ZLIB=ON - -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && + - cmake $CMAKE_OPTIONS .. && make && ctest --output-on-failure tags: @@ -472,12 +667,13 @@ freebsd/openssl_1.1.1/x86_64: - branches@libssh/libssh-mirror - branches@cryptomilk/libssh-mirror - branches@jjelen/libssh-mirror - - branches@marco.fortina/libssh-mirror ############################################################################### # Visual Studio builds # ############################################################################### +# 2024-05-13: These jobs run out of the stages as they take extremely long and +# usually timeout with the update to Gitlab 17.0 .vs: stage: test cache: @@ -490,8 +686,10 @@ freebsd/openssl_1.1.1/x86_64: - cmake --build . - ctest --output-on-failure tags: - - windows - - shared-windows + - saas-windows-medium-amd64 + only: + - merge_requests + - branches except: - tags artifacts: @@ -507,13 +705,14 @@ freebsd/openssl_1.1.1/x86_64: - vcpkg install cmocka - vcpkg install openssl - vcpkg install zlib + - vcpkg install libfido2 - 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 + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_FIDO2=ON -DUNIT_TESTING=ON .. visualstudio/x86_64: @@ -555,7 +754,7 @@ coverity: --form description="CI build" https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME tags: - - shared + - saas-linux-small-amd64 only: refs: - master @@ -571,12 +770,36 @@ coverity: - obj/cov-int/*.txt ############################################################################### -# Codespell # +# MacOS # ############################################################################### -codespell: - stage: review - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - codespell --ignore-words-list=keypair,sorce,ned,nd,ue +.macos: + extends: .build_options tags: - - shared + - saas-macos-medium-m1 + image: macos-14-xcode-15 + before_script: + - echo "MacOS runner started" + - brew update + - brew install cmake openssl cmocka doxygen + - mkdir obj && cd obj + only: + - branches@libssh/libssh-mirror + - branches@cryptomilk/libssh-mirror + - branches@jjelen/libssh-mirror + +# TODO add -DFUZZ_TESTING=ON clang cant find _LLVMFuzzerInitialize on arm64 +macos-m1: + extends: .macos + variables: + HOMEBREW_NO_AUTO_UPDATE: 1 + CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON" + stage: test + script: + - cmake $CMAKE_OPTIONS .. && + make -j$(sysctl -n hw.logicalcpu) && + ctest --output-on-failure + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/ diff --git a/.gitlab-ci/checkabi.sh b/.gitlab-ci/checkabi.sh new file mode 100755 index 00000000..7be336bb --- /dev/null +++ b/.gitlab-ci/checkabi.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +INSTALL_DIR1=${1} +INSTALL_DIR2=${2} + +abidiff \ + --headers-dir1 "${INSTALL_DIR1}/include/libssh/" \ + --headers-dir2 "${INSTALL_DIR2}/include/libssh/" \ + "${INSTALL_DIR1}/lib64/libssh.so" \ + "${INSTALL_DIR2}/lib64/libssh.so" \ + --fail-no-debug-info +abiret=$? + +ABIDIFF_ERROR=$(((abiret & 0x01) != 0)) +ABIDIFF_USAGE_ERROR=$(((abiret & 0x02) != 0)) +ABIDIFF_ABI_CHANGE=$(((abiret & 0x04) != 0)) +ABIDIFF_ABI_INCOMPATIBLE_CHANGE=$(((abiret & 0x08) != 0)) +ABIDIFF_UNKNOWN_BIT_SET=$(((abiret & 0xf0) != 0)) + +if [ $ABIDIFF_ERROR -ne 0 ]; then + echo "abidiff reported ABIDIFF_ERROR." + exit 1 +fi +if [ $ABIDIFF_USAGE_ERROR -ne 0 ]; then + echo "abidiff reported ABIDIFF_USAGE_ERROR." + exit 1 +fi +if [ $ABIDIFF_UNKNOWN_BIT_SET -ne 0 ]; then + echo "abidiff reported ABIDIFF_UNKNOWN_BIT_SET." + exit 1 +fi + +if [ $ABIDIFF_ABI_INCOMPATIBLE_CHANGE -ne 0 ]; then + echo "abidiff result ABIDIFF_ABI_INCOMPATIBLE_CHANGE, this breaks the API!" + exit 1 +fi + +if [ $ABIDIFF_ABI_CHANGE -ne 0 ]; then + echo "Ignoring abidiff result ABI_CHANGE" +fi + +exit 0 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/cmocka.cfg b/.gitlab-ci/cmocka.cfg new file mode 100644 index 00000000..756cbdd0 --- /dev/null +++ b/.gitlab-ci/cmocka.cfg @@ -0,0 +1,475 @@ + + + + + + true + + + + + + + + + + arg1 != 0 + + + + + + + + + + + + + arg1 == 0 + + + + + + + + + + + + + + arg1 == arg2 + + + + + + + + + + + + arg1 != arg2 + + + + + + + + + + + + + arg1 == arg2 + + + + + + + + + + + arg1 != arg2 + + + + + + + + + + + arg1 == arg2 + + + + + + + + + + + arg1 != arg2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + arg2 <= arg1 && arg1 <= arg3 + + + + + + + + + + + + arg1 < arg2 || arg3 < arg1 + + + + + + + + + + + + arg2 <= arg1 && arg1 <= arg3 + + + + + + + + + + + + arg1 < arg2 || arg3 < arg1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 <= arg1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/local-ci.sh b/.gitlab-ci/local-ci.sh new file mode 100755 index 00000000..039447ce --- /dev/null +++ b/.gitlab-ci/local-ci.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +set -e + +RED="\033[1;31m" +GREEN="\033[1;32m" +YELLOW="\033[1;33m" +BLUE="\033[1;34m" +RESET="\033[0m" + +export GCL_IGNORE_PREDEFINED_VARS=CI_REGISTRY + +BASE_SHA=$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD~1) + +COMMON_ARGS=( + --variable "CI_MERGE_REQUEST_DIFF_BASE_SHA=$BASE_SHA" + --variable "CI_REGISTRY=registry.gitlab.com" + --json-schema-validation=false +) + +check_requirements() { + for cmd in docker git gitlab-ci-local; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo -e "${RED}Missing dependency: $cmd${RESET}" + exit 1 + fi + echo -e "${GREEN}Found: $cmd${RESET}" + done + + if ! docker info >/dev/null 2>&1; then + echo -e "${RED}Docker daemon is not running or permission denied${RESET}" + exit 1 + fi +} + +list_jobs() { + gitlab-ci-local --list --json-schema-validation=false | awk 'NR>1 {print $1}' +} + +run_job() { + JOB="$1" + echo -e "${YELLOW}Running CI job: $JOB${RESET}" + gitlab-ci-local "$JOB" "${COMMON_ARGS[@]}" +} + +cleanup_images() { + echo -e "${BLUE}Removing libssh CI images only...${RESET}" + docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" \ + | grep "$CI_REGISTRY/$BUILD_IMAGES_PROJECT" \ + | awk '{print $2}' \ + | xargs -r docker rmi -f +} + +usage() { + echo + echo -e "${BLUE}Usage:${RESET}" + echo " $0 --list" + echo " $0 --run " + echo " $0 --all" + echo " $0 --run --clean" + echo " $0 --all --clean" + echo + exit 1 +} + +check_requirements + +CLEAN=0 +MODE="" +JOB="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --list) + MODE="list" + shift + ;; + --run) + MODE="run" + JOB="$2" + shift 2 + ;; + --all) + MODE="all" + shift + ;; + --clean) + CLEAN=1 + shift + ;; + *) + usage + ;; + esac +done + +case "$MODE" in + list) + list_jobs + ;; + run) + [[ -z "$JOB" ]] && usage + run_job "$JOB" + [[ "$CLEAN" -eq 1 ]] && cleanup_images + ;; + all) + for job in $(list_jobs); do + run_job "$job" + [[ "$CLEAN" -eq 1 ]] && cleanup_images + done + ;; + *) + usage + ;; +esac + +echo -e "${GREEN}Done.${RESET}" 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/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md new file mode 100644 index 00000000..6a95d6d6 --- /dev/null +++ b/.gitlab/merge_request_templates/default.md @@ -0,0 +1,16 @@ +Add a description of the new feature/bug fix. Reference any relevant bugs. + +## Checklist + * [ ] Commits have `Signed-off-by:` with name/author being identical to the commit author + * [ ] Code modified for feature + * [ ] Test suite updated with functionality tests + * [ ] Test suite updated with negative tests + * [ ] Documentation updated + * [ ] The project pipelines timeout is [extended](https://docs.gitlab.com/ee/ci/pipelines/settings.html#set-a-limit-for-how-long-jobs-can-run) at least to 2 hours. + +## Reviewer's checklist: + * [ ] Any issues marked for closing are addressed + * [ ] There is a test suite reasonably covering new functionality or modifications + * [ ] Function naming, parameters, return values, types, etc., are consistent and according to [CONTRIBUTING.md](https://gitlab.com/libssh/libssh-mirror/-/blob/master/CONTRIBUTING.md) + * [ ] This feature/change has adequate documentation added + * [ ] No obvious mistakes in the code diff --git a/CHANGELOG b/CHANGELOG index 52633dcf..780b0607 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,218 @@ CHANGELOG ========= -version 0.10.0 (released 2022-07-xx) +version 0.12.0 (released 2026-02-10) + * Deprecations and removals: + * Bumped minimal RSA key size to 1024 bits + * New functionality: + * Add support for hybrid key exchange mechanisms using Quantum Resistant + cryptography for all backends. These are now preferred: + * sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com + * mlkem768nistp256-sha256 + * mlkem768x25519-sha256 + * mlkem1024nistp384-sha384 (only OpenSSL 3.5+ and libgcrypt) + * New cmake option WITH_HERMETIC_USR + * Added support for Ed25519 keys through PKCS#11 + * Support for host-bound public key authentication + (publickey-hostbound-v00@openssh.com) + * Use curve25519 implementation from mbedTLS and libgcrypt + * New functions for signing arbitrary data (commits) with SSH keys + * sshsig_sign() + * sshsig_verify() + * Support for FIDO/U2F keys (internal implementation using libfido2) + * Compatible with OpenSSH: should work out of the box + * Extensible with callbacks + * Add support for GSSAPI Key Exchange (RFC 4462, RFC 8732) + * Add support for new configuration options (client and server): + * RequiredRsaSize + * AddressFamily (client) + * GSSAPIKeyExchange + * GSSAPIKexAlgorithms + * New option to get list of configured identities (SSH_OPTIONS_NEXT_IDENTITY) + * More OpenSSH compatible percent expansion characters + * Add new server auth_kbdint_function() callback + * New PKI Context structure for key operations + * Stability and compatibility improvements of ProxyJump + * SFTP + * Prevent failures when SFTP status message does not contain error message + * Fix possible timeouts while waiting for SFTP messages + * Support for users-groups-by-id@openssh.com extension in client + * Support for SSH_FXF_TRUNC in server + +version 0.11.4 (released 2026-02-10) + * Security: + * CVE-2025-14821: libssh loads configuration files from the C:\etc directory + on Windows + * CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request() + * CVE-2026-0965: Possible Denial of Service when parsing unexpected + configuration files + * CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input + * CVE-2026-0967: Specially crafted patterns could cause DoS + * CVE-2026-0968: OOB Read in sftp_parse_longname() + * libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP + extensions + * Stability and compatibility improvements of ProxyJump + +version 0.11.3 (released 2025-09-09) + * Security: + * CVE-2025-8114: Fix NULL pointer dereference after allocation failure + * CVE-2025-8277: Fix memory leak of ephemeral key pair during repeated wrong KEX + * Potential UAF when send() fails during key exchange + * Fix possible timeout during KEX if client sends authentication too early (#311) + * Cleanup OpenSSL PKCS#11 provider when loaded + * Zeroize buffers containing private key blobs during export + +version 0.11.2 (released 2025-06-24) + * Security: + * CVE-2025-4877 - Write beyond bounds in binary to base64 conversion + * CVE-2025-4878 - Use of uninitialized variable in privatekey_from_file() + * CVE-2025-5318 - Likely read beyond bounds in sftp server handle management + * CVE-2025-5351 - Double free in functions exporting keys + * CVE-2025-5372 - ssh_kdf() returns a success code on certain failures + * CVE-2025-5449 - Likely read beyond bounds in sftp server message decoding + * CVE-2025-5987 - Invalid return code for chacha20 poly1305 with OpenSSL + * Compatibility + * Fixed compatibility with CPM.cmake + * Compatibility with OpenSSH 10.0 + * Tests compatibility with new Dropbear releases + * Removed p11-kit remoting from the pkcs11 testsuite + * Bugfixes + * Implement missing packet filter for DH GEX + * Properly process the SSH2_MSG_DEBUG message + * Allow escaping quotes in quoted arguments to ssh configuration + * Do not fail with unknown match keywords in ssh configuration + * Process packets before selecting signature algorithm during authentication + * Do not fail hard when the SFTP status message is not sent by noncompliant + servers + +version 0.11.1 (released 2024-08-30) + * Fixed default TTY modes that are set when stdin is not connected to tty (#270) + * Fixed zlib cleanup procedure, which could crash on i386 + * Various test fixes improving their stability + * Fixed cygwin build + +version 0.11.0 (released 2024-07-31) + * Deprecations and Removals: + * Dropped support for DSA + * Deprecated Blowfish cipher (will be removed in next release) + * Deprecated SSH_BIND_OPTIONS_{RSA,ECDSA}KEY in favor of generic HOSTKEY + * Removed the usage of deprecated OpenSSL APIs (Note: Minimum supported + OpenSSL version is 1.1.1) + * Disabled preauth compression (zlib) by default + * Support for pkcs#11 engines are deprecated, pkcs11-provider is used instead + * Deprecation of old async SFTP API + * libgcrypt cryptographic backend is deprecated + * Deprecation of knownhosts hashing + * SFTP Improvements: + * Added support for async SFTP IO + * Added support for sftp_limits() and applied capping to SFTP read/write + operations accordingly + * Added sftp_home_directory() API support for sftp extension "home-directory" + * Added sftp_lsetstat() API for lsetstat extensions + * Added sftp_expand_path() to canonicalize path using expand-path@openssh.com + extension + * Implemented stat and realpath in sftpserver + * Added sftp_readlink() API to support hardlink@openssh.com + * New extensible callback based SFTP server + * Introduced the posix-rename@openssh.com extension + * New functions and features: + * Added support for PKCS #11 provider for OpenSSL 3.0 + * Added testing for GSSAPI Authentication + * Implemented proxy jump using libssh + * Recategorized loglevels to show fatal errors and alignment with OpenSSH + log levels + * Added ssh_channel_request_pty_size_modes() API to set terminal modes for + PTYs + * Added function to check username syntax + * Added support to check all keys in authorized_keys instead of one in + example server implementation + * Handled hostkey similar to OpenSSH + * Added ssh_session_socket_close() API in order to not close socket passed + through options on error conditions + * Added option SSH_BIND_OPTIONS_IMPORT_KEY_STR to read user-supplied key + string in ssh_bind_options_set() + * Improved log handling around ssh_set_callbacks + * Added ssh_set_error_invalid in ssh_options_set() + * Prevented signature blob to start with 1 bit in libgcrypt + * Added support to unbreak key comparison of Ed25519 keys imported from PEM + or OpenSSH container + * Added support to calculate missing CRT parameters when building RSA key + * Added ssh_pki_export_privkey_base64_format() and + ssh_pki_export_privkey_file_format() to support exporting keys in different + formats (PEM, OpenSSH) + * Added support to compare certificates and handle automatic certificate + authentication + * Added support to make compile-commands generation conditional + * Built fuzzers for normal testing + * Avoided passing other events to callbacks when called recursively + * Added control master and path options + * Refactored channel_rcv_data, check for errors and report more useful errors + * Added support to connect to other host addresses than just the first one + * Terminated the server properly when the MaxAuthTries is reached + * Added support for no-more-sessions@openssh.com request in both client and + server + * Added callback to support forwarded-tcpip requests + * Bumped minimal CMake version to 3.12 + * Added support for MBedTLS 3.6.x + * Added support for +,-,^ modifiers in front of algorithm lists in options + * Added callbacks for channel open response, and channel request response + * Replaced chroot() from chroot_wrapper internal library with chroot() + from priv_wrapper package + * Added a placeholder for non-expanded identities + * Improved handling of channel transfer window sizes + +version 0.10.6 (released 2023-12-18) + * Fix CVE-2023-6004: Command injection using proxycommand + * Fix CVE-2023-48795: Potential downgrade attack using strict kex + * Fix CVE-2023-6918: Missing checks for return values of MD functions + * Fix ssh_send_issue_banner() for CMD(PowerShell) + * Avoid passing other events to callbacks when poll is called recursively (#202) + * Allow @ in usernames when parsing from URI composes + +version 0.10.5 (released 2023-05-04) + * Fix CVE-2023-1667: a NULL dereference during rekeying with algorithm guessing + * Fix CVE-2023-2283: a possible authorization bypass in + pki_verify_data_signature under low-memory conditions. + * Fix several memory leaks in GSSAPI handling code + * Escape braces in ProxyCommand created from ProxyJump options for zsh + compatibility. + * Fix pkg-config path relocation for MinGW + * Improve doxygen documentation + * Fix build with cygwin due to the glob support + * Do not enqueue outgoing packets after sending SSH2_MSG_NEWKEYS + * Add support for SSH_SUPPRESS_DEPRECATED + * Avoid functions declarations without prototype to build with clang 15 + * Fix spelling issues + * Avoid expanding KnownHosts, ProxyCommands and IdentityFiles repetitively + * Add support sk-* keys through configuration + * Improve checking for Argp library + * Log information about received extensions + * Correctly handle rekey with delayed compression + * Move the EC keys handling to OpenSSL 3.0 API + * Record peer disconnect message + * Avoid deadlock when write buffering occurs and we call poll recursively to + flush the output buffer + * Disable preauthentication compression by default + * Add CentOS 8 Stream / OpenSSL 1.1.1 to CI + * Add accidentally removed default compile flags + * Solve incorrect parsing of ProxyCommand option + +version 0.10.4 (released 2022-09-07) + * Fixed issues with KDF on big endian + +version 0.10.3 (released 2022-09-05) + * Fixed possible infinite loop in known hosts checking + +version 0.10.2 (released 2022-09-02) + * Fixed tilde expansion when handling include directives + * Fixed building the shared torture library + * Made rekey test more robust (fixes running on i586 build systems e.g koji) + +version 0.10.1 (released 2022-08-30) + * Fixed proxycommand support + * Fixed musl libc support + +version 0.10.0 (released 2022-08-26) * Added support for OpenSSL 3.0 * Added support for mbedTLS 3 * Added support for Smart Cards (through openssl pkcs11 engine) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ba50f70..474437fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ -cmake_minimum_required(VERSION 3.3.0) -cmake_policy(SET CMP0048 NEW) +cmake_minimum_required(VERSION 3.14.0) # Specify search path for CMake modules to be loaded by include() # and find_package() @@ -10,7 +9,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") include(DefineCMakeDefaults) include(DefineCompilerFlags) -project(libssh VERSION 0.10.90 LANGUAGES C CXX) +project(libssh VERSION 0.12.00 LANGUAGES C) # 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.9.0") +set(LIBRARY_VERSION "4.11.0") set(LIBRARY_SOVERSION "4") # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked @@ -42,6 +41,8 @@ macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out of source buil # Copy library files to a lib sub-directory set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +set(LIBSSSH_PC_REQUIRES_PRIVATE "") + # search for libraries if (WITH_ZLIB) find_package(ZLIB REQUIRED) @@ -49,32 +50,12 @@ 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) + message(WARNING "libgcrypt cryptographic backend is deprecated and will be removed in future releases.") 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.1.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) @@ -87,6 +68,7 @@ find_package(Threads) if (WITH_GSSAPI) find_package(GSSAPI) + list(APPEND LIBSSH_PC_REQUIRES_PRIVATE ${GSSAPI_PC_REQUIRES}) endif (WITH_GSSAPI) if (WITH_NACL) @@ -96,9 +78,19 @@ if (WITH_NACL) endif (NOT NACL_FOUND) endif (WITH_NACL) +if (WITH_FIDO2) + find_package(libfido2) + if (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 ON) + else (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 OFF) + message(WARNING "libfido2 was not found. Internal support for interacting with FIDO2/U2F devices using the USB HID protocol will not be available.") + endif (LIBFIDO2_FOUND) +endif (WITH_FIDO2) + # Disable symbol versioning in non UNIX platforms if (UNIX) - find_package(ABIMap 0.3.1) + find_package(ABIMap 0.4.0) else (UNIX) set(WITH_SYMBOL_VERSIONING OFF) endif (UNIX) @@ -189,6 +181,10 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) set(ALLOW_ABI_BREAK "BREAK_ABI") endif() + if (WITH_FINAL) + set(FINAL "FINAL") + endif() + # Target we can depend on in 'make dist' set(_SYMBOL_TARGET "${PROJECT_NAME}.map") @@ -201,7 +197,7 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION} CURRENT_MAP ${MAP_PATH} COPY_TO ${MAP_PATH} - FINAL + ${FINAL} ${ALLOW_ABI_BREAK}) # Write the current version to the source @@ -210,7 +206,8 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) # Coverage -if (CMAKE_BUILD_TYPE STREQUAL "Coverage") +if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) include(CodeCoverage) setup_target_for_coverage_lcov( NAME "coverage" @@ -221,19 +218,23 @@ if (CMAKE_BUILD_TYPE STREQUAL "Coverage") NAME "coverage_xml" EXECUTABLE make test DEPENDENCIES ssh tests) -endif (CMAKE_BUILD_TYPE STREQUAL "Coverage") +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}") @@ -243,13 +244,18 @@ message(STATUS "Server support : ${WITH_SERVER}") message(STATUS "GSSAPI support : ${WITH_GSSAPI}") message(STATUS "GEX support : ${WITH_GEX}") message(STATUS "Support insecure none cipher and MAC : ${WITH_INSECURE_NONE}") +message(STATUS "Support exec : ${WITH_EXEC}") message(STATUS "Pcap debugging support : ${WITH_PCAP}") message(STATUS "Build shared library: ${BUILD_SHARED_LIBS}") message(STATUS "Unit testing: ${UNIT_TESTING}") message(STATUS "Client code testing: ${CLIENT_TESTING}") -message(STATUS "Blowfish cipher support: ${WITH_BLOWFISH_CIPHER}") +message(STATUS "Blowfish cipher support: ${HAVE_BLOWFISH}") message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") +message(STATUS "With FIDO2/U2F support: ${WITH_FIDO2}") +if (WITH_FIDO2) + message(STATUS "With libfido2 (internal usb-hid support): ${HAVE_LIBFIDO2}") +endif (WITH_FIDO2) set(_SERVER_TESTING OFF) if (WITH_SERVER) set(_SERVER_TESTING ${SERVER_TESTING}) @@ -264,9 +270,15 @@ message(STATUS "Benchmarks: ${WITH_BENCHMARKS}") message(STATUS "Symbol versioning: ${WITH_SYMBOL_VERSIONING}") message(STATUS "Allow ABI break: ${WITH_ABI_BREAK}") message(STATUS "Release is final: ${WITH_FINAL}") +if (WITH_HERMETIC_USR) + message(STATUS "User global client config: ${USR_GLOBAL_CLIENT_CONFIG}") +endif () message(STATUS "Global client config: ${GLOBAL_CLIENT_CONFIG}") if (WITH_SERVER) -message(STATUS "Global bind config: ${GLOBAL_BIND_CONFIG}") + if (WITH_HERMETIC_USR) + message(STATUS "User global bind config: ${USR_GLOBAL_BIND_CONFIG}") + endif () + message(STATUS "Global bind config: ${GLOBAL_BIND_CONFIG}") endif() message(STATUS "********************************************") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e26015c..6c606b9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,6 +117,52 @@ libssh Developer's Certificate of Origin for each patch, or inside each patch. Just the sign-off message is all that is required once we've received the initial email. +## Continuous Integration + +Contributing patches through Merge Request workflow on Gitlab allows us to run +various checks on various configuration as part of Gitlab CI. Unfortunately, +some pipelines are slower (as they involve building dependencies) so the default +timeout of 1 hour needs to be extended at least to 2 hours. This can be done in +project settings of your libssh fork: + +https://docs.gitlab.com/ee/ci/pipelines/settings.html#set-a-limit-for-how-long-jobs-can-run + +Otherwise you will encounter errors like these, usually on visualstudio builds: + +``` +ERROR: Job failed: execution took longer than 1h0m0s seconds +The script exceeded the maximum execution time set for the job +``` + +Note, that the built dependencies are cached so after successful build in your +namespace, the rebuilds should be much faster. + +## Running GitLab CI locally (optional helper) + +For contributors working on CI, build system changes, or adding new CI jobs, it can be useful to run GitLab CI pipelines locally before pushing. + +libssh provides a small helper script based on `gitlab-ci-local` that can: + +- List all jobs defined in `.gitlab-ci.yml` +- Run a specific job or the full pipeline locally +- Automatically pick up new jobs when they are added to the CI configuration +- Optionally clean up CI Docker images after execution + +### Requirements + +- Docker (daemon running) +- git +- gitlab-ci-local + https://github.com/firecow/gitlab-ci-local + +### Usage + +```bash +./.gitlab-ci/local-ci.sh --list +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 +./.gitlab-ci/local-ci.sh --all +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 --clean +``` # Coding conventions in the libssh tree @@ -517,6 +563,37 @@ Bad example: break; } +## ABI Versioning and Symbol Management + +To maintain [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) stability +and ensure backward compatibility, libssh uses **symbol versioning** to track and manage +exported functions and variables. This allows libssh to introduce new symbols or modify +existing functions in an ABI-compatible way. + +When introducing a new symbol: + +1. Use the `LIBSSH_API` macro to mark the symbol as part of the public API. +2. If you have [abimap](https://github.com/ansasaki/abimap) installed, the new symbols are +automatically generated in the `src/libssh_dev.map` file in the **build** directory and used automatically for building the updated library. But, depending on the version of `abimap` under use, you may face linker errors like: `unable to find version dependency LIBSSH_4_9_0`. In this case, you need to manually replace the existing `src/libssh.map` file with the generated `libssh_dev.map` file to update the symbol versioning. +3. If you do not have abimap installed, the modified/added symbols must manually be added to the +`src/libssh.map` file. The symbols must be added in the following format (assuming that 4_10_0 is the latest released version): + +``` +LIBSSH_AFTER_4_10_0 +{ + global: + new_function; + new_variable; +} LIBSSH_4_10_0; +``` +4. After following either of the above steps, the library can be successfully built and +tested without any linker errors. + +5. When submitting the patch, make sure that any new symbols have been added to `libssh.map` as described in step 3, so that the new additions may not be excluded from the next release due to human error. + +Also, to maintain ABI compatibility, existing symbols must not be removed. Instead, they can +be marked as deprecated using the `LIBSSH_DEPRECATED` macro. This allows the symbol to be +removed in a future release without breaking the ABI. Have fun and happy libssh hacking! diff --git a/CPackConfig.cmake b/CPackConfig.cmake index 5bd52c56..81ed6d06 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/;/[.]cache/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]bare/;/[.]git/;/[.]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 7a25b592..e9890e2f 100644 --- a/CompilerChecks.cmake +++ b/CompilerChecks.cmake @@ -16,7 +16,6 @@ if (UNIX) endif() endif() - add_c_compiler_flag("-std=gnu99" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wpedantic" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wall" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wshadow" SUPPORTED_COMPILER_FLAGS) @@ -49,6 +48,7 @@ if (UNIX) 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) + add_c_compiler_flag("-Werror=unused-variable" SUPPORTED_COMPILER_FLAGS) check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) if (REQUIRED_FLAGS_WFORMAT) @@ -92,9 +92,12 @@ if (UNIX) endif (WITH_STACK_PROTECTOR_STRONG) 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") + # apple m* chips do not support this option + if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL arm64) + 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() endif() diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 334695f1..4e32ea53 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -64,6 +64,7 @@ check_include_file(arpa/inet.h HAVE_ARPA_INET_H) check_include_file(byteswap.h HAVE_BYTESWAP_H) check_include_file(glob.h HAVE_GLOB_H) check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H) +check_include_file(ifaddrs.h HAVE_IFADDRS_H) if (WIN32) check_include_file(io.h HAVE_IO_H) @@ -77,51 +78,38 @@ 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) + check_include_file(openssl/blowfish.h HAVE_BLOWFISH) 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) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) 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_chacha20 HAVE_OPENSSL_EVP_CHACHA20) + # Check for ML-KEM availability (OpenSSL 3.5+) + if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0") + set(HAVE_OPENSSL_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () + unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) endif() @@ -152,6 +140,7 @@ check_function_exists(strncpy HAVE_STRNCPY) check_function_exists(strndup HAVE_STRNDUP) check_function_exists(strtoull HAVE_STRTOULL) check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) +check_function_exists(memset_explicit HAVE_MEMSET_EXPLICIT) check_function_exists(memset_s HAVE_MEMSET_S) if (HAVE_GLOB_H) @@ -244,7 +233,12 @@ if (GCRYPT_FOUND) endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") set(HAVE_GCRYPT_CHACHA_POLY 1) + set(HAVE_GCRYPT_CURVE25519 1) endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + if (GCRYPT_VERSION VERSION_GREATER_EQUAL "1.10.1") + set(HAVE_GCRYPT_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () endif (GCRYPT_FOUND) if (MBEDTLS_FOUND) @@ -254,6 +248,17 @@ if (MBEDTLS_FOUND) set(CMAKE_REQUIRED_INCLUDES "${MBEDTLS_INCLUDE_DIR}/mbedtls") check_include_file(chacha20.h HAVE_MBEDTLS_CHACHA20_H) check_include_file(poly1305.h HAVE_MBEDTLS_POLY1305_H) + if (MBEDTLS_VERSION VERSION_LESS "3.0.0") + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "config.h" HAVE_MBEDTLS_CURVE25519) + else() + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "mbedtls_config.h" HAVE_MBEDTLS_CURVE25519) + endif() + + + if (WITH_BLOWFISH_CIPHER) + check_include_file(blowfish.h HAVE_BLOWFISH) + endif() + unset(CMAKE_REQUIRED_INCLUDES) endif (MBEDTLS_FOUND) @@ -471,6 +476,7 @@ if (WITH_PKCS11_URI) if (NOT PKCS11_PROVIDER) set(WITH_PKCS11_PROVIDER 0) message(WARNING "Could not find pkcs11 provider! Falling back to engines") + message(WARNING "The support for engines is deprecated in OpenSSL and will be removed from libssh in the future releases.") endif (NOT PKCS11_PROVIDER) endif () endif() diff --git a/DefineOptions.cmake b/DefineOptions.cmake index 7401e86d..f788dec5 100644 --- a/DefineOptions.cmake +++ b/DefineOptions.cmake @@ -5,26 +5,45 @@ option(WITH_SERVER "Build with SSH server support" ON) 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_GCRYPT "Compile against libgcrypt" OFF) +option(WITH_GCRYPT "Compile against libgcrypt (deprecated)" OFF) option(WITH_MBEDTLS "Compile against libmbedtls" OFF) option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) option(WITH_PCAP "Compile with Pcap generation support" ON) option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(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(WITH_PKCS11_PROVIDER + "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) +option(WITH_FIDO2 "Build with FIDO2/U2F support" 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; enables unit testing and client tests" OFF) +option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" + OFF) +option( + GSSAPI_TESTING + "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" + 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) option(WITH_ABI_BREAK "Allow ABI break" OFF) option(WITH_GEX "Enable DH Group exchange mechanisms" ON) -option(WITH_INSECURE_NONE "Enable insecure none cipher and MAC algorithms (not suitable for production!)" OFF) -option(FUZZ_TESTING "Build with fuzzer for the server and client (automatically enables none cipher!)" OFF) +option( + WITH_INSECURE_NONE + "Enable insecure none cipher and MAC algorithms (not suitable for production!)" + OFF) +option( + WITH_EXEC + "Enable libssh to execute arbitrary commands from configuration files or options (match exec, proxy commands and OpenSSH-based proxy-jumps)." + ON) +option( + FUZZ_TESTING + "Build with fuzzer for the server and client (automatically enables none cipher!)" + OFF) option(PICKY_DEVELOPER "Build with picky developer flags" OFF) +option(WITH_HERMETIC_USR "Build with support for hermetic /usr/" OFF) if (WITH_ZLIB) set(WITH_LIBZ ON) @@ -33,30 +52,58 @@ else (WITH_ZLIB) endif (WITH_ZLIB) if (WITH_BENCHMARKS) - set(UNIT_TESTING ON) - set(CLIENT_TESTING ON) -endif() + set(UNIT_TESTING ON) + set(CLIENT_TESTING ON) +endif () -if (UNIT_TESTING OR CLIENT_TESTING OR SERVER_TESTING) - set(BUILD_STATIC_LIB ON) -endif() +if (UNIT_TESTING + OR CLIENT_TESTING + OR SERVER_TESTING + OR GSSAPI_TESTING) + set(BUILD_STATIC_LIB ON) +endif () if (WITH_NACL) - set(WITH_NACL ON) + set(WITH_NACL ON) endif (WITH_NACL) if (WITH_ABI_BREAK) - set(WITH_SYMBOL_VERSIONING ON) + set(WITH_SYMBOL_VERSIONING ON) endif (WITH_ABI_BREAK) +set(GLOBAL_CONF_DIR "/etc/ssh") +if (WIN32) + # Use PROGRAMDATA on Windows + if (DEFINED ENV{PROGRAMDATA}) + set(GLOBAL_CONF_DIR "$ENV{PROGRAMDATA}/ssh") + else () + set(GLOBAL_CONF_DIR "C:/ProgramData/ssh") + endif () + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CONF_DIR "/usr${GLOBAL_CONF_DIR}") + endif () +endif () + if (NOT GLOBAL_BIND_CONFIG) - set(GLOBAL_BIND_CONFIG "/etc/ssh/libssh_server_config") + set(GLOBAL_BIND_CONFIG "${GLOBAL_CONF_DIR}/libssh_server_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_BIND_CONFIG "/usr${GLOBAL_BIND_CONFIG}") + endif () endif (NOT GLOBAL_BIND_CONFIG) if (NOT GLOBAL_CLIENT_CONFIG) - set(GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config") + set(GLOBAL_CLIENT_CONFIG "${GLOBAL_CONF_DIR}/ssh_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CLIENT_CONFIG "/usr${GLOBAL_CLIENT_CONFIG}") + endif () endif (NOT GLOBAL_CLIENT_CONFIG) if (FUZZ_TESTING) - set(WITH_INSECURE_NONE ON) + set(WITH_INSECURE_NONE ON) endif (FUZZ_TESTING) + +if (WIN32) + set(WITH_EXEC 0) +endif (WIN32) diff --git a/INSTALL b/INSTALL index 7ba53c50..a51914cc 100644 --- a/INSTALL +++ b/INSTALL @@ -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.12.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 @@ -36,14 +38,16 @@ First, you need to configure the compilation, using CMake. Go inside the `build` dir. Create it if it doesn't exist. GNU/Linux, MacOS X, MSYS/MinGW: - - cmake -DUNIT_TESTING=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. make On Windows you should choose a makefile generator with -G or use cmake-gui.exe .. +To enable building tests use -DUNIT_TESTING=ON. For this, the +[cmocka](https://cmocka.org) dependency is required. + To enable additional client tests against a local OpenSSH server, add the compile option -DCLIENT_TESTING=ON. These tests require an OpenSSH server package and some wrapper libraries (see optional requirements) to diff --git a/cmake/Modules/AddCMockaTest.cmake b/cmake/Modules/AddCMockaTest.cmake index 4b0c2dad..f49961ba 100644 --- a/cmake/Modules/AddCMockaTest.cmake +++ b/cmake/Modules/AddCMockaTest.cmake @@ -116,5 +116,10 @@ function(ADD_CMOCKA_TEST _TARGET_NAME) add_test(${_TARGET_NAME} ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} ) + if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) + 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 index 3cf81b98..0fd70ae2 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -83,6 +83,10 @@ # - Change gcovr output from -o for --xml and --html 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. @@ -147,30 +151,34 @@ 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) -list(GET LANGUAGES 0 LANG) - -if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") +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_COMPILER_IS_GNUCXX) - if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") - # Do nothing; exit conditional without error if true - elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") - # Do nothing; exit conditional without error if true - else() - message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") endif() -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 -fprofile-arcs -ftest-coverage" +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_fprofile_abs_path) - if(HAVE_fprofile_abs_path) - set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + 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() @@ -202,7 +210,7 @@ mark_as_advanced( CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT (CMAKE_BUILD_TYPE STREQUAL "Coverage" OR CMAKE_BUILD_TYPE STREQUAL "Debug" OR 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) @@ -228,7 +236,7 @@ endif() # ) function(setup_target_for_coverage_lcov) - set(options NO_DEMANGLE) + 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}) @@ -298,6 +306,18 @@ function(setup_target_for_coverage_lcov) ${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) @@ -329,6 +349,12 @@ function(setup_target_for_coverage_lcov) 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 @@ -340,6 +366,7 @@ function(setup_target_for_coverage_lcov) 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 @@ -347,6 +374,7 @@ function(setup_target_for_coverage_lcov) ${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} @@ -358,6 +386,7 @@ function(setup_target_for_coverage_lcov) 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 @@ -621,7 +650,6 @@ function(setup_target_for_coverage_fastcov) --process-gcno --output ${Coverage_NAME}.json --exclude ${FASTCOV_EXCLUDES} - --exclude ${FASTCOV_EXCLUDES} ) set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} @@ -714,7 +742,9 @@ endfunction() # append_coverage_compiler_flags # Setup coverage for specific library function(append_coverage_compiler_flags_to_target name) - target_compile_options(${name} - PRIVATE ${COVERAGE_COMPILER_FLAGS}) + 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/DefineCompilerFlags.cmake b/cmake/Modules/DefineCompilerFlags.cmake index c6c07ede..39378a10 100644 --- a/cmake/Modules/DefineCompilerFlags.cmake +++ b/cmake/Modules/DefineCompilerFlags.cmake @@ -46,16 +46,4 @@ if (UNIX AND NOT WIN32) CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") set(CMAKE_EXEC_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" CACHE STRING "Flags used by the linker during UNDEFINEDSANITIZER builds.") - - # Activate with: -DCMAKE_BUILD_TYPE=Coverage - set(CMAKE_C_FLAGS_COVERAGE "-O0 -g -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the C compiler during Coverage builds.") - set(CMAKE_CXX_FLAGS_COVERAGE "-O0 -g -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the CXX compiler during Coverage builds.") - set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during the creation of shared libraries during Coverage builds.") - set(CMAKE_MODULE_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during the creation of shared libraries during Coverage builds.") - set(CMAKE_EXEC_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during Coverage builds.") endif() 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/FindGCrypt.cmake b/cmake/Modules/FindGCrypt.cmake index b1f73d81..e28cb846 100644 --- a/cmake/Modules/FindGCrypt.cmake +++ b/cmake/Modules/FindGCrypt.cmake @@ -39,6 +39,15 @@ find_path(GCRYPT_INCLUDE_DIR include ) +find_path(GCRYPT_ERROR_INCLUDE_DIR + NAMES + gpg-error.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + find_library(GCRYPT_LIBRARY NAMES gcrypt @@ -56,8 +65,10 @@ find_library(GCRYPT_ERROR_LIBRARY libgpg-error6-0 HINTS ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib ) -set(GCRYPT_LIBRARIES ${GCRYPT_LIBRARY} ${GCRYPT_ERROR_LIBRARY}) +set(GCRYPT_LIBRARIES ${GCRYPT_ERROR_LIBRARY} ${GCRYPT_LIBRARY}) if (GCRYPT_INCLUDE_DIR) file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" _gcrypt_version_str REGEX "^#define GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]") @@ -83,5 +94,25 @@ else (GCRYPT_VERSION) GCRYPT_LIBRARIES) endif (GCRYPT_VERSION) -# show the GCRYPT_INCLUDE_DIRS and GCRYPT_LIBRARIES variables only in the advanced view -mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_LIBRARIES) +# show the GCRYPT_INCLUDE_DIRS, GCRYPT_LIBRARIES and GCRYPT_ERROR_INCLUDE_DIR variables only in the advanced view +mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_ERROR_INCLUDE_DIR GCRYPT_LIBRARIES) + +if(GCRYPT_FOUND) + if(NOT TARGET libgcrypt::libgcrypt) + add_library(libgcrypt::libgcrypt UNKNOWN IMPORTED) + set_target_properties(libgcrypt::libgcrypt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgcrypt::libgcrypt + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_LIBRARY}") + endif() + + if(NOT TARGET libgpg-error::libgpg-error) + add_library(libgpg-error::libgpg-error UNKNOWN IMPORTED) + set_target_properties(libgpg-error::libgpg-error PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_ERROR_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgpg-error::libgpg-error + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_ERROR_LIBRARY}") + endif() +endif() diff --git a/cmake/Modules/FindGSSAPI.cmake b/cmake/Modules/FindGSSAPI.cmake index d227d8dd..630d7c16 100644 --- a/cmake/Modules/FindGSSAPI.cmake +++ b/cmake/Modules/FindGSSAPI.cmake @@ -11,6 +11,8 @@ # GSSAPI_INCLUDE_DIR - the GSSAPI include directory # GSSAPI_LIBRARIES - Link these to use GSSAPI # GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI +# GSSAPI_PC_REQUIRES - pkg-config module name if found, needed for +# Requires.private for static linking # #============================================================================= # Copyright (c) 2013 Andreas Schneider @@ -24,12 +26,23 @@ #============================================================================= # +set(_mit_modname "mit-krb5-gssapi") +set(_heimdal_modname "heimdal-gssapi") + +if(NOT _GSSAPI_ROOT_HINTS AND NOT _GSSAPI_ROOT_PATHS) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_search_module(_GSSAPI ${_mit_modname} ${_heimdal_modname}) + endif() +endif() + find_path(GSSAPI_ROOT_DIR NAMES include/gssapi.h include/gssapi/gssapi.h HINTS ${_GSSAPI_ROOT_HINTS} + "${_GSSAPI_INCLUDEDIR}" PATHS ${_GSSAPI_ROOT_PATHS} ) @@ -317,9 +330,15 @@ endif (GSSAPI_FLAVOR_HEIMDAL) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR) -if (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES) - set(GSSAPI_FOUND TRUE) -endif (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES) +if(GSSAPI_FOUND) + if(_GSSAPI_FOUND) # via pkg-config + if (GSSAPI_FLAVOR_MIT) + set(GSSAPI_PC_REQUIRES ${_mit_modname}) + elseif (GSSAPI_FLAVOR_HEIMDAL) + set(GSSAPI_PC_REQUIRES ${_heimdal_modname}) + endif() + endif() +endif() -# show the GSSAPI_INCLUDE_DIRS and GSSAPI_LIBRARIES variables only in the advanced view -mark_as_advanced(GSSAPI_INCLUDE_DIRS GSSAPI_LIBRARIES) +# show the GSSAPI_INCLUDE_DIR and GSSAPI_LIBRARIES variables only in the advanced view +mark_as_advanced(GSSAPI_INCLUDE_DIR GSSAPI_LIBRARIES) diff --git a/cmake/Modules/FindMbedTLS.cmake b/cmake/Modules/FindMbedTLS.cmake index baec8adc..7c5379c6 100644 --- a/cmake/Modules/FindMbedTLS.cmake +++ b/cmake/Modules/FindMbedTLS.cmake @@ -34,7 +34,7 @@ set(_MBEDTLS_ROOT_HINTS_AND_PATHS find_path(MBEDTLS_INCLUDE_DIR NAMES - mbedtls/config.h + mbedtls/ssl.h HINTS ${_MBEDTLS_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES @@ -72,13 +72,23 @@ find_library(MBEDTLS_X509_LIBRARY set(MBEDTLS_LIBRARIES ${MBEDTLS_SSL_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} ${MBEDTLS_X509_LIBRARY}) +# mbedtls 2.8 if (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") - string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+.[0-9]+.[0-9]+).*" + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") -endif () +endif() + +# mbedtls 3.6 +if (NOT MBEDTLS_VERSION AND MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif() include(FindPackageHandleStandardArgs) if (MBEDTLS_VERSION) @@ -93,8 +103,8 @@ if (MBEDTLS_VERSION) in the system variable MBEDTLS_ROOT_DIR" ) else (MBEDTLS_VERSION) - find_package_handle_standard_args(MBedTLS - "Could NOT find mbedTLS, try to set the path to mbedLS root folder in + find_package_handle_standard_args(MbedTLS + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder in the system variable MBEDTLS_ROOT_DIR" MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) @@ -102,3 +112,32 @@ endif (MBEDTLS_VERSION) # show the MBEDTLS_INCLUDE_DIRS and MBEDTLS_LIBRARIES variables only in the advanced view mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) + +if(MBEDTLS_FOUND) + if(NOT TARGET MbedTLS::mbedcrypto) + add_library(MbedTLS::mbedcrypto UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedcrypto PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedcrypto + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_CRYPTO_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedx509) + add_library(MbedTLS::mbedx509 UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedx509 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedx509 + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_X509_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedtls) + add_library(MbedTLS::mbedtls UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedtls PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedtls + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_LIBRARY}") + endif() +endif() diff --git a/cmake/Modules/Findlibfido2.cmake b/cmake/Modules/Findlibfido2.cmake new file mode 100644 index 00000000..7111a919 --- /dev/null +++ b/cmake/Modules/Findlibfido2.cmake @@ -0,0 +1,63 @@ +# - Try to find libfido2 +# Once done this will define +# +# LIBFIDO2_ROOT_DIR - Set this variable to the root installation of libfido2 +# +# Read-Only variables: +# LIBFIDO2_FOUND - system has libfido2 +# LIBFIDO2_INCLUDE_DIR - the libfido2 include directory +# LIBFIDO2_LIBRARIES - Link these to use libfido2 +# +# The libfido2 library provides support for communicating +# with FIDO2/U2F devices over USB/NFC. +# +# Copyright (c) 2025 Praneeth Sarode +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +set(_LIBFIDO2_ROOT_HINTS + $ENV{LIBFIDO2_ROOT_DIR} + ${LIBFIDO2_ROOT_DIR} +) + +set(_LIBFIDO2_ROOT_PATHS + "$ENV{PROGRAMFILES}/libfido2" +) + +set(_LIBFIDO2_ROOT_HINTS_AND_PATHS + HINTS ${_LIBFIDO2_ROOT_HINTS} + PATHS ${_LIBFIDO2_ROOT_PATHS} +) + +find_path(LIBFIDO2_INCLUDE_DIR + NAMES + fido.h + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(LIBFIDO2_LIBRARY + NAMES + fido2 + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + lib64 +) + +set(LIBFIDO2_LIBRARIES + ${LIBFIDO2_LIBRARY} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libfido2 DEFAULT_MSG LIBFIDO2_LIBRARIES LIBFIDO2_INCLUDE_DIR) + +# show the LIBFIDO2_INCLUDE_DIR and LIBFIDO2_LIBRARIES variables only in the advanced view +mark_as_advanced(LIBFIDO2_INCLUDE_DIR LIBFIDO2_LIBRARIES) diff --git a/config.h.cmake b/config.h.cmake index 5d0afdd7..14e1031c 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -8,10 +8,16 @@ #cmakedefine BINARYDIR "${BINARYDIR}" #cmakedefine SOURCEDIR "${SOURCEDIR}" +/* Global configuration directory */ +#cmakedefine USR_GLOBAL_CONF_DIR "${USR_GLOBAL_CONF_DIR}" +#cmakedefine GLOBAL_CONF_DIR "${GLOBAL_CONF_DIR}" + /* Global bind configuration file path */ +#cmakedefine USR_GLOBAL_BIND_CONFIG "${USR_GLOBAL_BIND_CONFIG}" #cmakedefine GLOBAL_BIND_CONFIG "${GLOBAL_BIND_CONFIG}" /* Global client configuration file path */ +#cmakedefine USR_GLOBAL_CLIENT_CONFIG "${USR_GLOBAL_CLIENT_CONFIG}" #cmakedefine GLOBAL_CLIENT_CONFIG "${GLOBAL_CLIENT_CONFIG}" /************************** HEADER FILES *************************/ @@ -58,15 +64,15 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDINT_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IFADDRS_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_OPENSSL_AES_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_WSPIAPI_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_BLOWFISH_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_OPENSSL_DES_H 1 @@ -85,18 +91,24 @@ /* Define to 1 if you have elliptic curve cryptography in openssl */ #cmakedefine HAVE_OPENSSL_ECC 1 +/* Define to 1 if mbedTLS supports curve25519 */ +#cmakedefine HAVE_MBEDTLS_CURVE25519 1 + /* Define to 1 if you have elliptic curve cryptography in gcrypt */ #cmakedefine HAVE_GCRYPT_ECC 1 /* Define to 1 if you have elliptic curve cryptography */ #cmakedefine HAVE_ECC 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 gcrypt with ChaCha20/Poly1305 support */ #cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 +/* Define to 1 if you have gcrypt with curve25519 support */ +#cmakedefine HAVE_GCRYPT_CURVE25519 + /*************************** FUNCTIONS ***************************/ /* Define to 1 if you have the `EVP_chacha20' function. */ @@ -171,6 +183,9 @@ /* Define to 1 if you have the `explicit_bzero' function. */ #cmakedefine HAVE_EXPLICIT_BZERO 1 +/* Define to 1 if you have the `memset_explicit' function. */ +#cmakedefine HAVE_MEMSET_EXPLICIT 1 + /* Define to 1 if you have the `memset_s' function. */ #cmakedefine HAVE_MEMSET_S 1 @@ -180,6 +195,18 @@ /* Define to 1 if you have the `cmocka_set_test_filter' function. */ #cmakedefine HAVE_CMOCKA_SET_TEST_FILTER 1 +/* Define to 1 if we have support for blowfish */ +#cmakedefine HAVE_BLOWFISH 1 + +/* Define to 1 if we have support for ML-KEM in libgcrypt */ +#cmakedefine HAVE_GCRYPT_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM in OpenSSL */ +#cmakedefine HAVE_OPENSSL_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM1024 in either backend */ +#cmakedefine HAVE_MLKEM1024 1 + /*************************** LIBRARIES ***************************/ /* Define to 1 if you have the `crypto' library (-lcrypto). */ @@ -197,6 +224,10 @@ /* Define to 1 if you have the `cmocka' library (-lcmocka). */ #cmakedefine HAVE_CMOCKA 1 +/* Define to 1 if you have the `libfido2' library (-lfido2). + * This is required for interacting with FIDO2/U2F devices over USB-HID. */ +#cmakedefine HAVE_LIBFIDO2 1 + /**************************** OPTIONS ****************************/ #cmakedefine HAVE_GCC_THREAD_LOCAL_STORAGE 1 @@ -231,9 +262,14 @@ /* Define to 1 if you want to enable DH group exchange algorithms */ #cmakedefine WITH_GEX 1 -/* Define to 1 if you want to enable none cipher and MAC */ +/* Define to 1 if you want to enable insecure none cipher and MAC */ #cmakedefine WITH_INSECURE_NONE 1 +/* Define to 1 if you want to allow libssh to execute arbitrary commands from + * configuration files or options (match exec, proxy commands and OpenSSH-based + * proxy-jumps). */ +#cmakedefine WITH_EXEC 1 + /* Define to 1 if you want to enable blowfish cipher support */ #cmakedefine WITH_BLOWFISH_CIPHER 1 @@ -258,6 +294,9 @@ /* Define to 1 if we want to build a support for PKCS #11 provider. */ #cmakedefine WITH_PKCS11_PROVIDER 1 +/* Define to 1 if you want to enable FIDO2/U2F support */ +#cmakedefine WITH_FIDO2 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 fcc15b90..0e7a9521 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,90 +1,247 @@ # # Build the documentation # -if (${CMAKE_VERSION} VERSION_GREATER "3.8.99") - +# To build the documentation with a local doxygen-awesome-css directory: +# +# cmake -S . -B obj \ +# -DDOXYGEN_AWESOME_CSS_DIR=/path/to/doxygen-awesome-css +# cmake --build obj --target docs +# +# The tarball can be downloaded from: +# https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.1.tar.gz +# find_package(Doxygen) if (DOXYGEN_FOUND) + set(DOXYGEN_AWESOME_CSS_PROJECT + "https://github.com/jothepro/doxygen-awesome-css") + set(DOXYGEN_AWESOME_CSS_VERSION "2.4.1") + set(DOXYGEN_AWESOME_CSS_URL + "${DOXYGEN_AWESOME_CSS_PROJECT}/archive/refs/tags/v${DOXYGEN_AWESOME_CSS_VERSION}.tar.gz" + ) + + # Allow specifying a local doxygen-awesome-css directory (useful for + # packaging) + if (NOT DEFINED DOXYGEN_AWESOME_CSS_DIR) + # Custom target to download doxygen-awesome-css at build time + add_custom_target( + doxygen-awesome-css + COMMAND + ${CMAKE_COMMAND} -DURL=${DOXYGEN_AWESOME_CSS_URL} + -DDEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + -DVERSION=${DOXYGEN_AWESOME_CSS_VERSION} -P + ${CMAKE_CURRENT_SOURCE_DIR}/fetch_doxygen_awesome.cmake + COMMENT "Fetching doxygen-awesome-css theme") + + set(AWESOME_CSS_DIR + "${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css-${DOXYGEN_AWESOME_CSS_VERSION}" + ) + else () + message( + STATUS + "Using doxygen-awesome-css from ${DOXYGEN_AWESOME_CSS_DIR}") + set(AWESOME_CSS_DIR "${DOXYGEN_AWESOME_CSS_DIR}") + endif () + + # Project title shown in documentation set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) + # Project version number shown in documentation set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION}) + # Brief description shown below project name set(DOXYGEN_PROJECT_BRIEF "The SSH library") + # Project favicon (browser tab icon) + set(DOXYGEN_PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/favicon.png) + # Number of spaces used for indentation in code blocks set(DOXYGEN_TAB_SIZE 4) + # Generate output optimized for C (vs C++) set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) + # Enable parsing of markdown in comments set(DOXYGEN_MARKDOWN_SUPPORT YES) - set(DOXYGEN_FULL_PATH_NAMES NO) + # Warn about undocumented members to improve documentation quality + set(DOXYGEN_WARN_IF_UNDOCUMENTED YES) + # Do not extract private class members + set(DOXYGEN_EXTRACT_PRIVATE NO) + if (WITH_INTERNAL_DOC) + # Include internal documentation + set(DOXYGEN_INTERNAL_DOCS YES) + else () + # Do not include internal documentation + set(DOXYGEN_INTERNAL_DOCS NO) + endif( WITH_INTERNAL_DOC) + # Disable built-in clipboard (using doxygen-awesome extension instead) + set(DOXYGEN_HTML_COPY_CLIPBOARD NO) + # Disable page outline panel (using interactive TOC extension instead) + set(DOXYGEN_PAGE_OUTLINE_PANEL NO) + + # Required configuration for doxygen-awesome-css theme Generate treeview + # sidebar for navigation + set(DOXYGEN_GENERATE_TREEVIEW YES) + # Enable default index pages + set(DOXYGEN_DISABLE_INDEX NO) + # Use top navigation bar instead of full sidebar (required for theme + # compatibility) + set(DOXYGEN_FULL_SIDEBAR NO) + # Use light color style (required for Doxygen >= 1.9.5) + set(DOXYGEN_HTML_COLORSTYLE LIGHT) - set(DOXYGEN_PREDEFINED DOXYGEN - WITH_SERVER - WITH_SFTP + # Disable diagram generation (not relevant for C projects) + set(DOXYGEN_HAVE_DOT NO) + set(DOXYGEN_CLASS_DIAGRAMS NO) + set(DOXYGEN_CALL_GRAPH NO) + set(DOXYGEN_CALLER_GRAPH NO) + + # Preprocessor defines to use when parsing code + set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER WITH_SFTP 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) - set(DOXYGEN_HTML_EXTRA_STYLESHEET ${CMAKE_CURRENT_SOURCE_DIR}/that_style/that_style.css) - set(DOXYGEN_HTML_EXTRA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_left.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_right.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/nav_edge_inter.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_off.png - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/sync_on.png - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/splitbar_handle.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/doc.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/mag_glass.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderclosed.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/img/folderopen.svg - ${CMAKE_CURRENT_SOURCE_DIR}/that_style/js/striped_bg.js) - 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}) + # Exclude patterns for files we don't want to document + set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h + blf.h) + # Exclude internal structures from documentation + 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_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}) + + # Custom layout file to rename "Topics" to "API Reference" and simplify + # navigation + set(DOXYGEN_LAYOUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml) + # Custom HTML header with doxygen-awesome extension initialization + set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/header.html) + # Modern CSS theme for documentation with custom libssh.org color scheme + set(DOXYGEN_HTML_EXTRA_STYLESHEET + ${AWESOME_CSS_DIR}/doxygen-awesome.css + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-custom.css) + # JavaScript extensions: dark mode toggle, copy button, paragraph links, + # interactive TOC + set(DOXYGEN_HTML_EXTRA_FILES + ${AWESOME_CSS_DIR}/doxygen-awesome-darkmode-toggle.js + ${AWESOME_CSS_DIR}/doxygen-awesome-fragment-copy-button.js + ${AWESOME_CSS_DIR}/doxygen-awesome-paragraph-link.js + ${AWESOME_CSS_DIR}/doxygen-awesome-interactive-toc.js) - # This updates the Doxyfile if we do changes here set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in") set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs") configure_file("${_doxyfile_template}" "${_target_doxyfile}") - doxygen_add_docs(docs - ${CMAKE_SOURCE_DIR}/include/libssh - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_CURRENT_SOURCE_DIR}) + doxygen_add_docs(docs ${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 + # Make docs depend on doxygen-awesome-css download (if not using local dir) + if (TARGET doxygen-awesome-css) + add_dependencies(docs doxygen-awesome-css) + endif () -endif() # CMAKE_VERSION + add_custom_target( + docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh + ${CMAKE_BINARY_DIR}) +endif (DOXYGEN_FOUND) diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml new file mode 100644 index 00000000..14a90505 --- /dev/null +++ b/doc/DoxygenLayout.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/authentication.dox b/doc/authentication.dox index 7d0ab81d..a0b2df84 100644 --- a/doc/authentication.dox +++ b/doc/authentication.dox @@ -105,7 +105,7 @@ Here is a small example of password authentication: @code int authenticate_password(ssh_session session) { - char *password; + char *password = NULL; int rc; password = getpass("Enter your password: "); @@ -218,7 +218,7 @@ int authenticate_kbdint(ssh_session session) rc = ssh_userauth_kbdint(session, NULL, NULL); while (rc == SSH_AUTH_INFO) { - const char *name, *instruction; + const char *name = NULL, *instruction = NULL; int nprompts, iprompt; name = ssh_userauth_kbdint_getname(session); @@ -231,7 +231,7 @@ int authenticate_kbdint(ssh_session session) printf("%s\n", instruction); for (iprompt = 0; iprompt < nprompts; iprompt++) { - const char *prompt; + const char *prompt = NULL; char echo; prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); @@ -251,7 +251,7 @@ int authenticate_kbdint(ssh_session session) } else { - char *ptr; + char *ptr = NULL; ptr = getpass(prompt); if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) @@ -354,7 +354,7 @@ The following example shows how to retrieve and dispose the issue banner: int display_banner(ssh_session session) { int rc; - char *banner; + char *banner = NULL; /* *** Does not work without calling ssh_userauth_none() first *** diff --git a/doc/command.dox b/doc/command.dox index 588151c6..dde32348 100644 --- a/doc/command.dox +++ b/doc/command.dox @@ -22,7 +22,7 @@ a SSH session that uses this channel: @code int show_remote_files(ssh_session session) { - ssh_channel channel; + ssh_channel channel = NULL; int rc; channel = ssh_channel_new(session); @@ -91,4 +91,10 @@ that it used: } @endcode +Warning: In a single channel, only ONE command can be executed! +If you want to executed multiple commands, allocate separate channels for +them or consider opening interactive shell. +Attempting to run multiple consecutive commands in one channel will fail. + + */ diff --git a/doc/doxygen-custom.css b/doc/doxygen-custom.css new file mode 100644 index 00000000..0c68d575 --- /dev/null +++ b/doc/doxygen-custom.css @@ -0,0 +1,127 @@ +/** + * Custom color scheme for libssh documentation + * Based on libssh.org color palette + */ + +html { + /* Primary colors - using libssh.org orange accent */ + --primary-color: #F78C40; + --primary-dark-color: #f57900; + --primary-light-color: #fab889; + + /* Accent color - neutral gray */ + --primary-lighter-color: #5A5A5A; + + /* Page colors - clean white background */ + --page-background-color: #ffffff; + --page-foreground-color: #333333; + --page-secondary-foreground-color: #666666; + + /* Links - use the warm orange color */ + --link-color: #F78C40; + --link-hover-color: #f0690a; + + /* Code blocks and fragments - very light background */ + --code-background: #f9f9f9; + --fragment-background: #f9f9f9; + + /* Borders - subtle light grey */ + --separator-color: #e0e0e0; + --border-light-color: #f0f0f0; + + /* Side navigation - pure white */ + --side-nav-background: #ffffff; + + /* Menu colors - warm orange accent */ + --menu-selected-background: #F78C40; + + /* Tables and boxes - lighter */ + --tablehead-background: #fbc7a2; + --tablehead-foreground: #333333; +} + +/* Header styling with libssh brand colors */ +#titlearea { + background-color: #5A5A5A; + background-image: linear-gradient(to right, #5A5A5A, #6a6a6a); + border-bottom: 3px solid #F78C40; +} + +#projectname { + color: #ffffff !important; +} + +#projectbrief { + color: #fab889 !important; +} + +/* Top navigation tabs */ +#top { + background: linear-gradient(to bottom, #5A5A5A 0%, #6a6a6a 100%); +} + +.tabs, .tabs2, .tabs3 { + background-image: none; + background-color: transparent; +} + +.tablist li { + background: rgba(255, 255, 255, 0.1); + border-right: 1px solid rgba(255, 255, 255, 0.2); +} + +.tablist li:hover { + background: rgba(255, 255, 255, 0.2); +} + +.tablist li.current { + background: #F78C40; + border-bottom: 3px solid #f57900; +} + +/* Tab text colors - comprehensive selectors */ +#nav-path ul li a, +.tabs a, +.tabs2 a, +.tabs3 a, +.tablist a, +.tablist a:link, +.tablist a:visited, +.tablist li a, +#main-nav a, +.sm > li > a, +.sm > li > a .sub-arrow { + color: #ffffff !important; + text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); +} + +/* Active/current tab text */ +#nav-path ul li.current a, +.tabs .current a, +.tabs2 .current a, +.tabs3 .current a, +.tablist .current a, +.tablist .current a:link, +.tablist .current a:visited, +.tablist li.current a, +#main-nav .current a, +.sm .current a { + color: #333333 !important; + text-shadow: none; +} + +/* Dropdown arrow - white color for top menu */ +.sm-dox a span.sub-arrow { + border-right-color: #ffffff !important; + border-bottom-color: #ffffff !important; +} + +/* Dropdown menu text - must be dark on white background */ +/* Make this as specific as possible to override white color */ +.sm-dox > li > ul > li > a, +.sm-dox li ul li a, +.sm-dox ul li a, +#main-menu ul li a { + color: #333333 !important; + text-shadow: none !important; +} diff --git a/doc/favicon.png b/doc/favicon.png new file mode 100644 index 00000000..0b343c15 Binary files /dev/null and b/doc/favicon.png differ diff --git a/doc/fetch_doxygen_awesome.cmake b/doc/fetch_doxygen_awesome.cmake new file mode 100644 index 00000000..1fa89674 --- /dev/null +++ b/doc/fetch_doxygen_awesome.cmake @@ -0,0 +1,41 @@ +# Script to download doxygen-awesome-css at build time +# +# Usage: +# cmake -P fetch_doxygen_awesome.cmake \ +# -DURL= \ +# -DDEST_DIR= \ +# -DVERSION= + +if(NOT DEFINED URL) + message(FATAL_ERROR "URL not specified") +endif() +if(NOT DEFINED DEST_DIR) + message(FATAL_ERROR "DEST_DIR not specified") +endif() +if(NOT DEFINED VERSION) + message(FATAL_ERROR "VERSION not specified") +endif() + +set(EXTRACT_DIR "${DEST_DIR}/doxygen-awesome-css-${VERSION}") + +if(NOT EXISTS "${EXTRACT_DIR}/doxygen-awesome.css") + message(STATUS "Downloading doxygen-awesome-css ${VERSION}...") + set(TARBALL "${DEST_DIR}/doxygen-awesome-css.tar.gz") + file(DOWNLOAD + "${URL}" + "${TARBALL}" + STATUS download_status + SHOW_PROGRESS + ) + list(GET download_status 0 status_code) + if(NOT status_code EQUAL 0) + list(GET download_status 1 error_msg) + message(FATAL_ERROR "Download failed: ${error_msg}") + endif() + message(STATUS "Extracting doxygen-awesome-css...") + file(ARCHIVE_EXTRACT + INPUT "${TARBALL}" + DESTINATION "${DEST_DIR}" + ) + file(REMOVE "${TARBALL}") +endif() diff --git a/doc/fido2.dox b/doc/fido2.dox new file mode 100644 index 00000000..98f1ca13 --- /dev/null +++ b/doc/fido2.dox @@ -0,0 +1,601 @@ +/** + +@page libssh_tutor_fido2 Chapter 11: FIDO2/U2F Keys Support + +@section fido2_intro Introduction + +The traditional SSH public key model stores the private key on disk +and anyone who obtains that file (and possibly its passphrase) can impersonate +the user. FIDO2 authenticators, such as USB security keys, are hardware tokens +that generate or securely store private key material within a secure element +and may require explicit user interaction such as a touch, PIN, or biometric +verification for use. Hence, security keys are far safer from theft or +exfiltration than traditional file-based SSH keys. libssh provides support +for FIDO2/U2F security keys as hardware-backed SSH authentication credentials. + +This chapter explains the concepts, build prerequisites, the API, and +usage patterns for enrolling (creating) and using security key-backed SSH +keys, including resident (discoverable) credentials. + +@subsection fido2_resident_keys Resident Keys + +Two credential storage modes exist for security keys: + + - Non-resident (default): A credential ID (key handle) and metadata are + stored on the client-side in a key file. This key handle must be + presented to the FIDO2/U2F device while signing. This is somewhat + similar to traditional SSH keys, except that the key handle is not the + private key itself, but used in combination with the device's master key + to derive the actual private key. + + - Resident (discoverable): The credential (and metadata like user id) is + stored on the device. No local file is needed; the device can enumerate or + locate the credential internally when queried. + +Advantages of resident keys include portability (using the same device +across hosts) and resilience (no loss if the local machine is destroyed). +Although, they may be limited by the storage of the authenticator. + +@subsection fido2_presence_verification User Presence vs. User Verification + +FIDO2 distinguishes between: + + - User Presence (UP): A simple physical interaction (touch) to confirm a + human is present. + + - User Verification (UV): Verification of the user’s identity through + biometric authentication or a PIN. + +Requiring UV provides additional protection if the device is stolen +and used without the PIN/biometric. + +libssh exposes flags controlling these requirements (see below). + +@subsection fido2_callbacks The Callback Abstraction + +Different environments may need to access security keys through different +transport layers (e.g., USB-HID, NFC, Bluetooth, etc.). To accommodate +this variability, libssh does not hard-code a single implementation. + +Instead, it defines a small callback interface (`ssh_sk_callbacks`) used for all +security key operations. Any implementation of this callback interface can be used +by higher-level PKI functions to perform enroll/sign/load_resident_keys +operations without needing to know the transport specifics. Hence, users can +define their own implementations for these callbacks to support different +transport protocols or custom hardware. Refer @ref fido2_custom_callbacks +for additional details. + +The callback interface is defined in `libssh/callbacks.h` and the behaviour +and return values are specified by `libssh/sk_api.h`, which is the same +interface defined by OpenSSH for its security key support. This means that +any callback implementations (also called "middleware" in OpenSSH terminology) +developed for OpenSSH can be adapted to libssh with minimal changes. + +The following operations are abstracted by the callback interface: + + - api_version(): Report the version of the SK API that the callback implementation + is based on, so that libssh can check whether this implementation would be + compatible with the SK API version that it supports. + Refer @ref fido2_custom_callbacks_version for additional details. + - enroll(): Create (enroll) a new credential, returning public key, key + handle, attestation data. + - sign(): Produce a signature for supplied inputs using an existing key + handle. + - load_resident_keys(): Enumerate resident (discoverable) credentials stored + on the authenticator. + +libssh provides a default implementation of the `ssh_sk_callbacks` using +the libfido2 library for the USB-HID transport protocol. Hence, by default, +libssh can interact with any FIDO2/U2F device that supports USB-HID and is +compatible with libfido2, without requiring any additional modifications. + +@subsection fido2_build Building with FIDO2 Support + +To enable FIDO2/U2F support, libssh must be built with the WITH_FIDO2 +build option as follows: + +@verbatim + cmake -DWITH_FIDO2=ON .. +@endverbatim + +libssh will also build the default USB-HID `ssh_sk_callbacks`, if the +libfido2 library and headers are installed on your system. + +@warning If built without libfido2, support for interacting with FIDO2/U2F +devices over USB-HID will not be available. + +@subsection fido2_api_overview API Overview + +Security key operations are configured through the `ssh_pki_ctx` +which allows to specify both general PKI options and FIDO2-specific +options such as the sk_callbacks, challenge data, application string, flags, etc. + +The following sections describe the options that can be configured and how +the `ssh_pki_ctx` is used in conjunction with `ssh_key` to perform +enrollment, signing, and resident key loading operations. + +@subsection fido2_key_objects Security Key Objects & Metadata + +Security keys are surfaced as `ssh_key` objects of type +`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519` (corresponding to the +OpenSSH public key algorithm names `sk-ecdsa-sha2-nistp256@openssh.com` and +`sk-ssh-ed25519@openssh.com`). In addition to standard key handling, libssh +exposes the following helper functions to retrieve embedded SK metadata: + + - ssh_key_get_sk_application(): Returns the relying party / application + (RP ID) string. The Relying Party ID (RP ID) is a string + that identifies the application or service requesting key enrollment. It + ensures that a credential is bound to a specific origin, preventing + phishing across sites. During registration, the authenticator associates + the credential with this RP ID so that it can later only be used for + authentication requests from the same relying party. For SSH keys, the + common format is "ssh:user@host". + + - ssh_key_get_sk_user_id(): Returns a copy of the user ID associated with a key + which represents a unique identifier for the user within the relying + party (application) context. It is typically a string (such as an + email, or a random identifier) that helps distinguish credentials + belonging to different users for the same application. + + Though the user ID can be binary data according to the FIDO2 spec, libssh only + supports NUL-terminated strings for enrolling new keys in order to remain compatible + with the OpenSSH's sk-api interface. + + However, libssh does support loading existing resident keys with user IDs containing + arbitrary binary data. It does so by using an `ssh_string` to store the loaded key's + user_id, and an `ssh_string` can contain arbitrary binary data that can not be stored + in a traditional NUL-terminated string (like null bytes). + + @note The user_id is NOT stored in the key file for non-resident keys. It is only + available for resident (discoverable) keys loaded from the authenticator via + ssh_sk_resident_keys_load(). For keys imported from files, this function returns + NULL. + + - ssh_key_get_sk_flags(): Returns the flags associated with the key. The + following are the supported flags and they can be combined using + bitwise OR: + - SSH_SK_USER_PRESENCE_REQD : Require user presence (touch). + - SSH_SK_USER_VERIFICATION_REQD : Require user verification + (PIN/biometric). + - SSH_SK_RESIDENT_KEY : Request a resident discoverable credential. + - SSH_SK_FORCE_OPERATION : Force resident (discoverable) credential + creation even if one with same application and user_id already + exists. + +These functions perform no additional communication with the +authenticator, this metadata is captured during enrollment/loading and +cached in the `ssh_key`. + +@subsection fido2_options Setting Security Key Context Options + +Options are set via ssh_pki_ctx_options_set(). + +Representative security key options: + - SSH_PKI_OPTION_SK_APPLICATION (const char *): Required relying party ID + If not set, a default value of "ssh:" is used. + - SSH_PKI_OPTION_SK_FLAGS (uint8_t *): Flags described above. If not set, + defaults to SSH_SK_USER_PRESENCE_REQD. This is because OpenSSH `sshd` + requires user presence for security key authentication by default. + - SSH_PKI_OPTION_SK_USER_ID (const char *): Represents a unique identifier + for the user within the relying party (application) context. + It is typically a string (such as an email, or a random identifier) that + helps distinguish credentials belonging to different users for the same + application. If not set, defaults to 64 zeros. + - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): Custom challenge; if omitted a + random 32-byte challenge is generated. + - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): Replace the default + callbacks with custom callbacks. + +PIN callback: Use ssh_pki_ctx_set_sk_pin_callback() to register a function +matching `ssh_auth_callback` to prompt for and supply a PIN. The callback may +be called multiple times to ask for the pin depending on the authenticator policy. + +Callback options: Callback implementations may accept additional configuration +name/value options such as the path to the fido device. These options can be provided via +`ssh_pki_ctx_sk_callbacks_option_set()`. Refer @ref fido2_custom_callbacks_options +for additional details. + +The built-in callback implementation provided by libssh supports additional options, +with their names defined in `libssh.h` prefixed with `SSH_SK_OPTION_NAME_*`, such as: + +SSH_SK_OPTION_NAME_DEVICE_PATH: Used for specifying a device path. +If the device path is not specified and multiple devices are connected, then +depending upon the operation and the flags set, the callback implementation may +automatically select a suitable device, or the user may be prompted to touch the +device they want to use. + +SSH_SK_OPTION_NAME_USER_ID: Used for setting the user ID. +Note that the user ID can also be set using the ssh_pki_ctx_options_set() API. + +@subsection fido2_enrollment Enrollment Example + +An enrollment operation creates a new credential on the authenticator and +returns an ssh_key object representing it. The application and user_id +fields are required for creating the credential. The other options are +optional. A successful enrollment returns the public key, key handle, and +metadata which are stored in the ssh_key object, and may optionally return +attestation data which is used for verifying the authenticator model and +firmware version. + +Below is a simple example enrolling an Ed25519 security key (non-resident) +requiring user presence only: + +@code +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + + /* In a real application, the user would be prompted to enter the PIN */ + const char *pin = "4242"; + size_t l = strlen(pin); + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int enroll_sk_key() +{ + const char *app = "ssh:user@host"; + const char *user_id = "alice"; + uint8_t flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + const char *device_path = "/dev/hidraw6"; /* Optional device path */ + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_APPLICATION, app); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_USER_ID, user_id); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + true); + + ssh_key enrolled = NULL; + int rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + pki_ctx, + &enrolled); /* produces sk-ed25519 key */ + + /* Save enrolled key using ssh_pki_export_privkey_file, retrieve attestation + * buffer etc. */ + + /* Free context and key when done */ +} +@endcode + +After a successful enrollment, you can retrieve the attestation buffer +(if provided by the authenticator) from the PKI context: + +@code +ssh_buffer att_buf = NULL; +rc = ssh_pki_ctx_get_sk_attestation_buffer(pki_ctx, &att_buf); +if (rc == SSH_OK && att_buf != NULL) { + /* att_buf now contains the serialized attestation + * ("ssh-sk-attest-v01"). You can inspect, save, or + * parse the buffer as needed + */ + ssh_buffer_free(att_buf); +} +@endcode + +Notes: +- The attestation buffer is only populated if the enrollment operation + succeeds and the authenticator provides attestation data. +- `ssh_pki_ctx_get_sk_attestation_buffer()` returns a copy of the attestation + buffer; the caller must free it with `ssh_buffer_free()`. + +@subsection fido2_signing Authenticating with a Stored Security Key Public Key + +To authenticate using a security key, the application typically loads the +previously enrolled sk-* private key, establishes an SSH connection, and +calls `ssh_userauth_publickey()`. libssh automatically recognizes security +key types and transparently handles the required hardware-backed +authentication steps such as prompting for a touch or PIN using the +configured security key callbacks. + +Example: +@code +#include +#include + +int auth_with_sk_file(const char *host, + const char *user, + const char *privkey_path) +{ + ssh_session session = NULL; + ssh_key privkey = NULL; + int rc = SSH_ERROR; + + session = ssh_new(); + ssh_options_set(session, SSH_OPTIONS_HOST, host); + ssh_options_set(session, SSH_OPTIONS_USER, user); + ssh_connect(session); + + ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &privkey); + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + /* Optionally set PIN callback, device path, etc. */ + /* ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); */ + + ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_ctx); + + rc = ssh_userauth_publickey(session, user, privkey); + if (rc == SSH_AUTH_SUCCESS) { + printf("Authenticated with security key.\n"); + rc = SSH_OK; + } else { + fprintf(stderr, + "Authentication failed rc=%d err=%s\n", + rc, + ssh_get_error(session)); + rc = SSH_ERROR; + } + + /* Free resources */ +} +@endcode + +@subsection fido2_resident Resident Key Enumeration + +Resident keys stored on the device can be discovered and loaded with +ssh_sk_resident_keys_load() which takes a PKI context (configured with +a PIN callback) and returns each key as an ssh_key and the number of keys loaded. + +Example: + +@code +#include +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + const char *pin = "4242"; + size_t l = strlen(pin); + + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int auth_with_resident(const char *host, + const char *user, + const char *application, + const char *user_id) +{ + ssh_pki_ctx pki_ctx = NULL; + size_t num_found = 0; + ssh_key *keys = NULL; + ssh_key final_key = NULL; + int rc = SSH_ERROR; + + ssh_string cur_application = NULL; + ssh_string cur_user_id = NULL; + ssh_string expected_application = NULL; + ssh_string expected_user_id = NULL; + + pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + expected_application = ssh_string_from_char(application); + expected_user_id = ssh_string_from_char(user_id); + + rc = ssh_sk_resident_keys_load(pki_ctx, &keys, &num_found); + for (size_t i = 0; i < num_found; i++) { + cur_application = ssh_key_get_sk_application(keys[i]); + cur_user_id = ssh_key_get_sk_user_id(keys[i]); + + if (ssh_string_cmp(cur_application, expected_application) == 0 && + ssh_string_cmp(cur_user_id, expected_user_id) == 0) { + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + final_key = keys[i]; + break; + } + + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + } + + SSH_STRING_FREE(expected_application); + SSH_STRING_FREE(expected_user_id); + + /* Continue with authentication using the ssh_key with + * ssh_userauth_publickey as usual, and free resources when done. */ +} +@endcode + +@subsection fido2_sshsig Signing using the sshsig API + +Security keys can also be used for general-purpose signing of arbitrary data +(without SSH authentication) using the existing `sshsig_sign()` and `sshsig_verify()` +functions. These functions work seamlessly with security key types +(`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519`) and will automatically +invoke the configured security key callbacks to perform hardware-backed signing +operations. + +@subsection fido2_custom_callbacks Implementing Custom Callback Implementations + +Users may need to implement custom callback implementations to support +different transport protocols (e.g., NFC, Bluetooth) beyond the default USB-HID +support. This section describes how to implement and integrate custom callback +implementations. + +To implement custom callbacks, you must include the following headers: + +@code +#include /* For ssh_sk_callbacks_struct */ +#include /* For SK API constants and data structures */ +@endcode + +The `libssh/sk_api.h` header provides the complete interface specification including +request/response structures, flags, and version macros. + +@subsubsection fido2_custom_callbacks_version API Version Compatibility + +libssh validates callback implementations by checking the API version returned by +the `api_version()` callback. To ensure compatibility, libssh compares the major +version (upper 16 bits) of the returned value with `LIBSSH_SK_API_VERSION_MAJOR`. +If they don't match, libssh will reject the callback implementation. +This ensures that the callbacks' SK API matches the major version expected by libssh, +while allowing minor version differences. + +@subsubsection fido2_custom_callbacks_implementation Implementation Example + +Here's a minimal example of defining and using custom callbacks: + +@code +#include +#include +#include + +/* Your custom API version callback */ +static uint32_t my_sk_api_version(void) +{ + /* Match the major version, set your own minor version */ + return SSH_SK_VERSION_MAJOR | 0x0001; +} + +/* Your custom enroll callback */ +static int my_sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + /* Parse options array to extract custom parameters */ + if (options != NULL) { + for (size_t i = 0; options[i] != NULL; i++) { + if (strcmp(options[i]->name, "my_custom_option") == 0) { + /* Use options[i]->value */ + } + } + } + + /* Implement your enroll logic here */ + /* ... */ + + return SSH_SK_ERR_GENERAL; /* Return appropriate error code */ +} + +/* Implement other required callbacks: sign, load_resident_keys */ +/* ... */ + +/* Define your callback structure */ +static struct ssh_sk_callbacks_struct my_sk_callbacks = { + .size = sizeof(struct ssh_sk_callbacks_struct), + .api_version = my_sk_api_version, + .enroll = my_sk_enroll, + .sign = my_sk_sign, /* Your implementation */ + .load_resident_keys = my_sk_load_resident_keys, /* Your implementation */ +}; + +/* Usage example */ +void use_custom_callbacks(void) +{ + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + + /* Set your custom callbacks */ + ssh_pki_ctx_options_set(pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + &my_sk_callbacks); + + /* Pass custom options to your callbacks */ + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "my_custom_option", + "my_custom_value", + false); + + /* Use the context for enrollment, signing, etc. */ +} +@endcode + +@subsubsection fido2_custom_callbacks_options Passing Custom Options + +The `ssh_pki_ctx_sk_callbacks_option_set()` function allows you to pass +implementation-specific options as name/value string pairs: + +@code +ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "option_name", + "option_value", + required); +@endcode + +Parameters: +- `option_name`: The name of the option (e.g., "device_path", "my_custom_param") +- `option_value`: The string value for this option +- `required`: If true, this option must be processed by the callback implementation + and cannot be ignored. If false, the option is advisory and can be skipped if the + callback implementation does not support it. + +These options are passed to your callbacks in the `struct sk_option **options` +parameter as a NULL-terminated array. Each `sk_option` has the following fields: +- `name`: The option name (char *) +- `value`: The option value (char *) +- `required`: Whether the option must be processed (uint8_t, non-zero = required) + +@subsubsection fido2_custom_callbacks_openssh OpenSSH Middleware Compatibility + +Since libssh uses the same SK API as OpenSSH, middleware implementations developed +for OpenSSH can be adapted with minimal changes. +To adapt an OpenSSH middleware for libssh, create a wrapper that populates +`ssh_sk_callbacks_struct` with pointers to the middleware's functions. + +@subsection fido2_testing Testing and Environment Variables + +Unit tests covering USB-HID enroll/sign/load_resident_keys operations can be found +in the `tests/unittests/torture_sk_usbhid.c` file. To run these tests you +must have libfido2 installed and the WITH_FIDO2=ON build option set. +Additionally, you must ensure the following: + + - An actual FIDO2 device must be connected to the test machine. + - The TORTURE_SK_USBHID environment variable must be set. + - The environment variable TORTURE_SK_PIN= must be set. + +If these are not set, the tests are skipped. + +The higher level PKI integration tests can be found in +`tests/unittests/torture_pki_sk.c` and the tests related to the sshsig API +can be found in `tests/unittests/torture_pki_sshsig.c`. +These use the callback implementation provided by OpenSSH's sk-dummy.so, +which simulates an authenticator without requiring any hardware. Hence, these tests +can be run in the CI environment. +However, these tests can also be configured to use the default USB-HID callbacks +by setting the same environment variables as described above. + +The following devices were tested during development: + +- Yubico Security Key NFC - USB-A + +*/ diff --git a/doc/forwarding.dox b/doc/forwarding.dox index 2b202b4d..3ca3aa8a 100644 --- a/doc/forwarding.dox +++ b/doc/forwarding.dox @@ -100,7 +100,7 @@ used to retrieve google's home page from the remote SSH server. @code int direct_forwarding(ssh_session session) { - ssh_channel forwarding_channel; + ssh_channel forwarding_channel = NULL; int rc = SSH_ERROR; char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; int nbytes, nwritten; @@ -161,7 +161,7 @@ local libssh application, which handles them: int web_server(ssh_session session) { int rc; - ssh_channel channel; + ssh_channel channel = NULL; char buffer[256]; int nbytes, nwritten; int port = 0; diff --git a/doc/guided_tour.dox b/doc/guided_tour.dox index 904a739e..e98b5e0d 100644 --- a/doc/guided_tour.dox +++ b/doc/guided_tour.dox @@ -79,7 +79,7 @@ Here is a small example of how to use it: int main() { - ssh_session my_ssh_session; + ssh_session my_ssh_session = NULL; int verbosity = SSH_LOG_PROTOCOL; int port = 22; @@ -126,7 +126,7 @@ Here's an example: int main() { - ssh_session my_ssh_session; + ssh_session my_ssh_session = NULL; int rc; my_ssh_session = ssh_new(); @@ -190,8 +190,7 @@ int verify_knownhost(ssh_session session) ssh_key srv_pubkey = NULL; size_t hlen; char buf[10]; - char *hexa; - char *p; + char *p = NULL; int cmp; int rc; @@ -201,7 +200,7 @@ int verify_knownhost(ssh_session session) } rc = ssh_get_publickey_hash(srv_pubkey, - SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); ssh_key_free(srv_pubkey); @@ -217,7 +216,7 @@ int verify_knownhost(ssh_session session) break; case SSH_KNOWN_HOSTS_CHANGED: fprintf(stderr, "Host key for server changed: it is now:\n"); - ssh_print_hexa("Public key hash", hash, hlen); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); fprintf(stderr, "For security reasons, connection will be stopped\n"); ssh_clean_pubkey_hash(&hash); @@ -238,10 +237,9 @@ int verify_knownhost(ssh_session session) /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ case SSH_KNOWN_HOSTS_UNKNOWN: - hexa = ssh_get_hexa(hash, hlen); fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); - fprintf(stderr, "Public key hash: %s\n", hexa); - ssh_string_free_char(hexa); + fprintf(stderr, "Public key hash: "); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); ssh_clean_pubkey_hash(&hash); p = fgets(buf, sizeof(buf), stdin); if (p == NULL) { @@ -317,9 +315,9 @@ The example below shows an authentication with password: int main() { - ssh_session my_ssh_session; + ssh_session my_ssh_session = NULL; int rc; - char *password; + char *password = NULL; // Open session and set options my_ssh_session = ssh_new(); @@ -380,7 +378,7 @@ The example below shows how to execute a remote command: @code int show_remote_processes(ssh_session session) { - ssh_channel channel; + ssh_channel channel = NULL; int rc; char buffer[256]; int nbytes; diff --git a/doc/header.html b/doc/header.html new file mode 100644 index 00000000..6e43e819 --- /dev/null +++ b/doc/header.html @@ -0,0 +1,92 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/doc/introduction.dox b/doc/introduction.dox index f2f3d3dd..4415c1bb 100644 --- a/doc/introduction.dox +++ b/doc/introduction.dox @@ -14,8 +14,8 @@ libssh is a Free Software / Open Source project. The libssh library is distributed under LGPL license. The libssh project has nothing to do with "libssh2", which is a completely different and independent project. -libssh can run on top of either libgcrypt or libcrypto, -two general-purpose cryptographic libraries. +libssh can run on top of either libcrypto, mbedtls or libgcrypt (deprecated) +general-purpose cryptographic libraries. This tutorial concentrates for its main part on the "client" side of libssh. To learn how to accept incoming SSH connections (how to write a SSH server), @@ -44,6 +44,12 @@ Table of contents: @subpage libssh_tutor_threads +@subpage libssh_tutor_pkcs11 + +@subpage libssh_tutor_sftp_aio + +@subpage libssh_tutor_fido2 + @subpage libssh_tutor_todo */ diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 04197603..a0ed6776 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -19,12 +19,13 @@ the interesting functions as you go. The libssh library provides: - - Key Exchange Methods: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 + - Key Exchange Methods: sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, mlkem768nistp256-sha256, mlkem1024nistp384-sha384, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 + - GSSAPI Key Exchange Methods: gss-group14-sha256-*, gss-group16-sha512-*, gss-nistp256-sha256-*, gss-curve25519-sha256-* - Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256 - Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc - Compression Schemes: zlib, zlib@openssh.com, none - MAC hashes: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5 - - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic + - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic, gssapi-keyex - Channels: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, auth-agent-req@openssh.com - Global Requests: tcpip-forward, forwarded-tcpip - Channel Requests: x11, pty, exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com @@ -33,7 +34,7 @@ The libssh library provides: - Thread-safe: Just don't share sessions - Non-blocking: it can be used both blocking and non-blocking - Your sockets: the app hands over the socket, or uses libssh sockets - - OpenSSL or gcrypt: builds with either + - OpenSSL, MBedTLS or gcrypt (deprecated): builds with either @section main-additional-features Additional Features @@ -170,21 +171,15 @@ The following RFC documents described SSH-2 protocol as an Internet standard. The Secure Shell (SSH) Session Channel Break Extension - RFC 4344, The Secure Shell (SSH) Transport Layer Encryption Modes - - RFC 4345, - Improved Arcfour Modes for the Secure Shell (SSH) Transport Layer Protocol It was later modified and expanded by the following RFCs. - RFC 4419, Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol - - RFC 4432, - RSA Key Exchange for the Secure Shell (SSH) Transport Layer Protocol - (not implemented in libssh) - RFC 4462, Generic Security Service Application Program Interface (GSS-API) Authentication and Key Exchange for the Secure Shell (SSH) Protocol - (only the authentication implemented in libssh) - RFC 4716, The Secure Shell (SSH) Public Key File Format (not implemented in libssh) @@ -203,7 +198,6 @@ It was later modified and expanded by the following RFCs. (not implemented in libssh) - RFC 8160, IUTF8 Terminal Mode in Secure Shell (SSH) - (not handled in libssh) - RFC 8270, Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits - RFC 8308, @@ -222,6 +216,14 @@ There are also drafts that are being currently developed and followed. - draft-miller-ssh-agent-08 SSH Agent Protocol + - draft-ietf-sshm-mlkem-hybrid-kex-09 + PQ/T Hybrid Key Exchange with ML-KEM in SSH + - draft-ietf-sshm-ntruprime-ssh-06 + Secure Shell (SSH) Key Exchange Method Using Hybrid Streamlined NTRU Prime sntrup761 and X25519 with SHA-512: sntrup761x25519-sha512 + - draft-ietf-sshm-chacha20-poly1305-02 + Secure Shell (SSH) authenticated encryption cipher: chacha20-poly1305 + - draft-ietf-sshm-strict-kex-01 + SSH Strict KEX extension Interesting cryptography documents: @@ -246,8 +248,6 @@ them like the statvfs calls in SFTP or the ssh-agent. OpenSSH's deviations and extensions - OpenSSH's pubkey certificate authentication - - - chacha20-poly1305@openssh.com authenticated encryption mode - OpenSSH private key format (openssh-key-v1) diff --git a/doc/pkcs11.dox b/doc/pkcs11.dox index c2732a81..b358432c 100644 --- a/doc/pkcs11.dox +++ b/doc/pkcs11.dox @@ -21,6 +21,9 @@ 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). +@warning The support for Engines was deprecated in OpenSSL 3.0 so this approach +is deprecated in libssh 0.11.x. + # Pre-requisites (OpenSSL 3.0.8+) The OpenSSL 3.0 is deprecating usage of low-level engines in favor of high-level @@ -78,10 +81,6 @@ 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 +For testing, the SoftHSM PKCS#11 library is used. */ 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 d770f27a..35fc5c9a 100644 --- a/doc/shell.dox +++ b/doc/shell.dox @@ -26,7 +26,7 @@ The code sample below achieves these tasks: @code int shell_session(ssh_session session) { - ssh_channel channel; + ssh_channel channel = NULL; int rc; channel = ssh_channel_new(session); @@ -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(). diff --git a/doc/that_style/LICENSE b/doc/that_style/LICENSE deleted file mode 100644 index eac42e70..00000000 --- a/doc/that_style/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Jan-Lukas Wynen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/doc/that_style/README.md b/doc/that_style/README.md deleted file mode 100644 index 75727209..00000000 --- a/doc/that_style/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# that style -A plain, more modern HTML style for Doxygen - -## Requirements -- Doxygen (tested with version 1.8.13) -- *optional*: a sass/scss compiler if you want to modify the style - -## Simple usage -Tell Doxygen about the files for that style as shown in [doxyfile.conf](doxyfile.conf). You might need to adjust the -paths depending on where you installed that style. -When you run Doxygen, all files are copied into to generated HTML folder. So you don't need to keep the originals around -unless you want to re-generate the documentation. - -## Advanced -that style uses a custom javascript to hack some nice stripes into some tables. It has to be loaded from HTML. Hence you need -to use the provided custom header. Since its default content may change when Doxygen is updated, there might be syntax error in -the generated HTML. If this is the case, you can remove the custom header (adjust your doxyfile.conf). This has no -disadvantages other than removing the stripes. - -[that_style.css](that_style.css) was generated from the scss files in the folder [sass](sass). If you want to change the style, -use those files in order to have better control. For instance, you can easily change most colors by modifying the variables -in the beginning of [that_style.scss](sass/that_style.scss). diff --git a/doc/that_style/header.html b/doc/that_style/header.html deleted file mode 100644 index 3da46393..00000000 --- a/doc/that_style/header.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - -$projectname: $title -$title - - - -$treeview -$search -$mathjax - - -$extrastylesheet - - -
- - -
- - - - - - - - - - - - - - - - - - - - - -
-
$projectname -  $projectnumber -
-
$projectbrief
-
-
$projectbrief
-
$searchbox
-
- - diff --git a/doc/that_style/img/doc.svg b/doc/that_style/img/doc.svg deleted file mode 100644 index 68e1ba01..00000000 --- a/doc/that_style/img/doc.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/doc/that_style/img/folderclosed.svg b/doc/that_style/img/folderclosed.svg deleted file mode 100644 index e53ec906..00000000 --- a/doc/that_style/img/folderclosed.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/doc/that_style/img/folderopen.svg b/doc/that_style/img/folderopen.svg deleted file mode 100644 index 1ab7b78a..00000000 --- a/doc/that_style/img/folderopen.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/doc/that_style/img/mag_glass.svg b/doc/that_style/img/mag_glass.svg deleted file mode 100644 index e21a0048..00000000 --- a/doc/that_style/img/mag_glass.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/doc/that_style/img/nav_edge_inter.svg b/doc/that_style/img/nav_edge_inter.svg deleted file mode 100644 index f04f10fd..00000000 --- a/doc/that_style/img/nav_edge_inter.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/doc/that_style/img/nav_edge_left.svg b/doc/that_style/img/nav_edge_left.svg deleted file mode 100644 index ca1adf4c..00000000 --- a/doc/that_style/img/nav_edge_left.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/doc/that_style/img/nav_edge_right.svg b/doc/that_style/img/nav_edge_right.svg deleted file mode 100644 index bb338153..00000000 --- a/doc/that_style/img/nav_edge_right.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/doc/that_style/img/splitbar_handle.svg b/doc/that_style/img/splitbar_handle.svg deleted file mode 100644 index e0dc90da..00000000 --- a/doc/that_style/img/splitbar_handle.svg +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/doc/that_style/img/sync_off.png b/doc/that_style/img/sync_off.png deleted file mode 100644 index 9d286d9c..00000000 Binary files a/doc/that_style/img/sync_off.png and /dev/null differ diff --git a/doc/that_style/img/sync_on.png b/doc/that_style/img/sync_on.png deleted file mode 100644 index b8434e80..00000000 Binary files a/doc/that_style/img/sync_on.png and /dev/null differ diff --git a/doc/that_style/js/striped_bg.js b/doc/that_style/js/striped_bg.js deleted file mode 100644 index 97ae0a8e..00000000 --- a/doc/that_style/js/striped_bg.js +++ /dev/null @@ -1,32 +0,0 @@ -// Adds extra CSS classes "even" and "odd" to .memberdecls to allow -// striped backgrounds. -function MemberDeclsStriper () { - var counter = 0; - - this.stripe = function() { - $(".memberdecls tbody").children().each(function(i) { - - // reset counter at every heading -> always start with even - if ($(this).is(".heading")) { - counter = 0; - } - - // add extra classes - if (counter % 2 == 1) { - $(this).addClass("odd"); - } - else { - $(this).addClass("even"); - } - - // advance counter at every separator - // this is the only way to reliably detect which table rows belong together - if ($(this).is('[class^="separator"]')) { - counter++; - } - }); - } -} - -// execute the function -$(document).ready(new MemberDeclsStriper().stripe); diff --git a/doc/that_style/that_style.css b/doc/that_style/that_style.css deleted file mode 100644 index 846353ca..00000000 --- a/doc/that_style/that_style.css +++ /dev/null @@ -1,1431 +0,0 @@ -@charset "UTF-8"; -/* - * My own little style - */ -body, table, div, p, dl { - font: 400 14px/22px Roboto,sans-serif; } - -h1.groupheader { - font-size: 150%; } - -.title { - font: 400 14px/28px Roboto,sans-serif; - font-size: 150%; - font-weight: bold; - margin: 10px 2px; } - -h2.groupheader { - border-bottom: 1px solid #555555; - color: black; - font-size: 200%; - font-weight: bold; - margin-top: 1.75em; - padding-top: 1em; - padding-bottom: 4px; - width: 100%; } - -tr.heading h2 { - border-bottom: 1px solid #a5a5a5; - font-size: 150%; - margin-top: 6px; - margin-bottom: 6px; - padding-top: 3px; - padding-bottom: 7px; } - -h2.groupheader a { - margin-left: 1%; } - -h3.groupheader { - font-size: 100%; } - -h1, h2, h3, h4, h5, h6 { - -webkit-transition: none; - -moz-transition: none; - -o-transition: none; - transition: none; - margin-right: 15px; } - h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { - text-shadow: none; - color: #5f082b; } - -dt { - font-weight: bold; } - -div.multicol { - -moz-column-gap: 1em; - -webkit-column-gap: 1em; - -moz-column-count: 3; - -webkit-column-count: 3; } - -p.startli, p.startdd { - margin-top: 2px; } - -p.starttd { - margin-top: 0px; } - -p.endli { - margin-bottom: 0px; } - -p.enddd { - margin-bottom: 4px; } - -p.endtd { - margin-bottom: 2px; } - -#top { - border: none; - position: relative; - z-index: 100; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 0 8px rgba(0, 0, 0, 0.3); } - -caption { - font-weight: bold; } - -span.legend { - font-size: 70%; - text-align: center; } - -h3.version { - font-size: 90%; - text-align: center; } - -div.qindex, div.navtab { - background-color: #ffffff; - border: none; - text-align: center; } - -div.qindex, div.navpath { - width: 100%; - line-height: 140%; } - -div.navtab { - margin-right: 15px; } - -a, a:visited { - color: #00549f; - font-weight: normal; - text-decoration: none; } - -.contents a:visited { - color: #4665A2; } - -a:hover { - text-decoration: underline; } - -a.qindex { - font-weight: bold; - text-transform: uppercase; } - -a.qindexHL { - font-weight: bold; - background-color: #9CAFD4; - color: #ffffff; - border: 1px double #869DCA; } - -.contents a.qindexHL:visited { - color: #ffffff; } - -a.el, a.el:visited { - font-weight: normal; - color: #00549f; } - -a.elRef, a.elRef:visited { - font-family: monospace; - color: #006bc8; } - -/* - * The main menu at the top - */ -#main-menu { - background-image: none; - background: #414141; - padding: 0; } - -.sm-dox > li:not(:last-child) > a { - background-image: none; - text-shadow: none; - color: white; - font-weight: normal; - letter-spacing: 1px; - font-size: 11pt; - text-transform: uppercase; } - -.sm-dox > li:not(:last-child) > a:hover, -.sm-dox > li:not(:last-child) > a.highlighted { - background-color: #5f082b; } - -.sm-dox a span.sub-arrow { - border-color: white transparent transparent; } - -.sm-dox ul { - border: none; - -moz-border-radius: 0 !important; - -webkit-border-radius: 0 !important; - border-radius: 0 !important; - padding: 0; - background: #414141; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - .sm-dox ul a { - background: inherit; - color: white; - font-weight: normal; - letter-spacing: 1px; - font-size: 11pt; } - .sm-dox ul a:hover { - background: #5f082b; - color: white; - font-weight: normal; - letter-spacing: 1px; - font-size: 11pt; } - .sm-dox ul a.highlighted { - background: #5f082b; - color: white; - font-weight: normal; - letter-spacing: 1px; - font-size: 11pt; } - .sm-dox ul a span.sub-arrow { - /* this sets the color of the arrow */ - border-color: white transparent transparent; } - -dl.el { - margin-left: -1cm; } - -div.ah, span.ah { - background: none; - color: black; - margin-bottom: 3px; - margin-top: 3px; - padding: 0.2em; - border: none; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; - font: 14pt monospace; - font-weight: bold; - text-transform: uppercase; } - -div.classindex ul { - list-style: none; - padding-left: 0; } - -div.classindex span.ai { - display: inline-block; } - -div.groupHeader { - margin-left: 16px; - margin-top: 12px; - font-weight: bold; } - -div.groupText { - margin-left: 16px; - font-style: italic; } - -body { - background-color: #ffffff; - color: black; - margin: 0; } - -#doc-content { - background-color: #111; - color: #ffffff; } - -div.contents { - color: black; - background-color: #ffffff; - padding: 0; - margin: 5pt; } - div.contents hr { - display: none; - visibility: hidden; } - -div.header + div.contents { - padding: 1ex; - margin: 0 5pt 5pt 5pt; } - -div.textblock { - padding: 1ex 1ex 0 1ex; } - -div.textblock + ul { - padding-bottom: 1%; } - -img.footer { - border: 0px; - vertical-align: middle; } - -/* - Basic styling for fragments shared by all themes. -*/ -div.fragment { - padding: 4px; - margin: 4px 8px 4px 2px; - color: #bebebe; - background-color: #323232; - border: 3px solid #e8e8e8; - border-radius: 2px; - overflow-y: hidden; - overflow-x: auto; - position: relative; } - -div.line { - font-family: monospace, fixed; - font-size: 13px; - min-height: 13px; - line-height: 1.0; - text-indent: -53px; - margin: 0px; - padding: 1px 0 1px 53px; - white-space: pre; - -webkit-transition: background-color; - -moz-transition: background-color; - -o-transition: background-color; - transition: background-color; - -webkit-duration: 0s; - -moz-duration: 0s; - -o-duration: 0s; - duration: 0s; } - div.line:hover { - background-color: #1a1a1a; } - div.line::after { - content: "\000A"; - white-space: pre; } - -span.lineno { - padding-right: 4px; - text-align: right; - color: black; - height: 100px; - white-space: pre; - border-right: 3px solid #1d7567; - background-color: #323232; } - -span.lineno a, span.lineno a:visited { - background-color: inherit; - color: #1e595a; } - -span.lineno a:hover { - background-color: #C8C8C8; - text-decoration: none; } - -.lineno { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - -div.fragment { - color: #bebebe; - background-color: #323232; } - -div.fragment::before { - background-color: #1a1a1a; - border-right: 1px solid #3e3e3e; } - -div.line:hover { - background-color: #1a1a1a; } - -span.lineno { - color: #969696; - background-color: #323232; - border-right: 1px solid #3e3e3e; } - -span.lineno a, span.lineno a:visited { - background-color: inherit; - color: #dcdcdc; } - -span.lineno a:hover { - background-color: #323232; } - -a.code, a.code:visited { - color: #6cc7eb; } - -a.codeRef, a.codeRef:visited { - color: #3d95e6; } - -span.keyword { - color: #98f77a; - font-weight: bold; } - -span.keywordtype { - color: #ffa0a0; } - -span.keywordflow { - color: #98f77a; - font-weight: bold; } - -span.comment { - color: #999; - font-style: oblique; } - -span.preprocessor { - color: #cd5c57; } - -span.stringliteral { - color: #64b041; } - -span.charliteral { - color: #64b041; } - -blockquote { - background-color: #F7F8FB; - border-left: 2px solid #9CAFD4; - margin: 0 24px 0 4px; - padding: 0 12px 0 16px; } - -/* - * The search box - */ -.sm-dox > li:last-child { - margin-right: 10pt; } - -#MSearchBox { - border: 2px inset black; - display: table; - width: 350px; - height: 26px; - background: white; - margin-top: 5px; } - #MSearchBox .left { - background-image: none; - display: table-cell; - width: 100%; - height: inherit; - left: 0; } - #MSearchBox .right { - background-image: none; - width: 0; - display: none; - visibility: hidden; } - -nav > #MSearchBox { - border: 2px solid #666666; - margin: 5px 10pt 0 0; - height: 22px; } - -#MSearchSelect, .left #MSearchSelect { - left: 0; - background-image: url("mag_glass.svg"); - width: 22px; - height: 22px; - padding: 22px 22px 0 0; - margin: 0 4px 0 4px; - box-sizing: border-box; } - -#MSearchField { - background-image: none; - display: table-cell; - margin: 0; - margin-left: 30px; - width: calc(100% - 34px); - height: 22px; - font: 11pt sans-serif; } - -#MSearchSelectWindow { - background-color: #414141; - padding: 0; - border: solid 1px black; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -a.SelectItem { - color: white; - padding: 3px 4px; - font: 10pt sans-serif; - letter-spacing: 1px; } - a.SelectItem:hover { - background-color: #5f082b; - color: white; } - a.SelectItem:focus, a.SelectItem:active { - color: white; } - -#MSearchResultsWindow { - background-color: white; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -table.memberdecls { - width: 100%; - border-spacing: 0px; - padding: 0px; - margin-top: 7px; - background-color: #ffffff; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -.memberdecls tbody { - background-color: #ffffff; } - -.memberdecls .odd { - background: #f6f6f6; } - -/* all but last separator show a line */ -.memberdecls tr[class^="separator"]:not(:last-child) .memSeparator { - border-bottom: 1px solid #c5c5c5; - line-height: 1px; - margin: 0; - padding: 0; } - -.memberdecls tr[class^="separator"]:last-child .memSeparator { - border-bottom: none; - line-height: 0; - margin: 0; - padding: 0; } - -table.fieldtable { - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; } - -.memberdecls td, .fieldtable tr { - background-color: inherit; } - -.fieldtable th { - display: none; - height: 0; - visibility: hidden; } - -td.fieldname { - color: #820a32; - font-family: monospace; - font-weight: bold; } - -th.markdownTableHeadLeft, th.markdownTableHeadRight, -th.markdownTableHeadCenter, th.markdownTableHeadNone { - background-color: #414141; - color: white; } - -.memItemLeft, .memItemRight, -.memTemplItemLeft, .memTemplItemRight, .memTemplParams { - font-family: monospace; - background-color: #ffffff; - border: none; - margin: 4px; - padding: 1px 0 0 8px; } - -.mdescLeft, .mdescRight { - background-color: #ffffff; - border: none; - margin: 4px; - padding: 1px 0 0 8px; } - -.mdescLeft, .mdescRight { - padding: 0px 8px 4px 8px; - color: #555; } - -.memItemLeft, .memTemplItemLeft { - white-space: nowrap; } - -.memItemRight { - width: 100%; } - -.memTemplParams { - color: black; - white-space: nowrap; - font-size: 100%; } - -/* Styles for detailed member documentation */ -.memtitle { - padding: 8px; - border: none; - margin-bottom: -1px; - background-image: none; - background-color: #f6f6f6; - line-height: 1.25; - font-weight: bold; - color: black; - float: left; - z-index: 0; - position: relative; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -.permalink { - font-size: 100%; - display: inline-block; - vertical-align: middle; } - -/* replace content of permalinks */ -.permalink a { - visibility: hidden; } - -.permalink a:after { - content: "§"; - visibility: visible; - display: block; - position: absolute; - color: black; - top: 20%; } - -.permalink a:visited { - color: black; } - -.memtemplate { - font-size: 100%; - color: black; - font-family: monospace; - font-weight: normal; - margin-left: 9px; } - -.memnav { - background-color: #EBEFF6; - border: 1px solid #A3B4D7; - text-align: center; - margin: 2px; - margin-right: 15px; - padding: 2px; } - -.mempage { - width: 100%; } - -.memitem { - font-family: monospace; - padding: 0; - margin-bottom: 10px; - margin-right: 5px; - -webkit-transition: none; - -moz-transition: none; - -ms-transition: none; - -o-transition: none; - transition: none; - display: table !important; - width: 100%; - background-color: #f6f6f6; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -.memitem.glow { - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; } - -.memname { - font-family: monospace; - font-weight: 400; - margin-left: 6px; } - -.memname td { - vertical-align: bottom; } - -.memproto, dl.reflist dt { - border: none; - padding: 6px 0px 6px 0px; - color: black; - font-weight: bold; - text-shadow: none; - background-color: #f6f6f6; - position: relative; - z-index: 1; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; } - -.overload { - font-family: "courier new",courier,monospace; - font-size: 65%; } - -.memdoc, dl.reflist dd { - border: none; - border-left: 4px solid #5f082b; - border-bottom-left-radius: 0px; - border-bottom-right-radius: 0px; - -webkit-border-bottom-left-radius: 0px; - -webkit-border-bottom-right-radius: 0px; - -moz-border-bottom-left-radius: 0px; - -moz-border-bottom-right-radius: 0px; - padding: 2px 1% 2px 1%; - margin: 1%; - background-color: #ffffff; - background-image: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; - /* allow movement of elements inside */ - display: flex; - flex-direction: column; } - -/* overrides for docs on individual pages */ -.memtitle:nth-child(2) { - width: 0; - height: 0; - display: none; - visibility: hidden; } - -.memitem:nth-child(3) { - margin: 0; - margin-top: 0.5%; - background-color: #ffffff; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; } - -.memitem:nth-child(3) .memproto { - padding: 10px; - background-color: #ffffff; - margin-bottom: 10px; } - -.memitem:nth-child(3) .memproto::after { - content: ""; - width: 99%; - height: 1px; - position: absolute; - bottom: -10px; - left: 0.5%; - background: #666; } - -.memitem:nth-child(3) .memdoc { - border: none; - padding: 0; } - -.memitem:nth-child(3) table.memname { - background-color: #f6f6f6; - border-collapse: collapse; - border-spacing: initial; - border: 1px solid #aaa; } - -.memitem:nth-child(3) table.memname tr:not(:last-child) { - border-bottom: 1px dashed #aaa; } - -dl.reflist dt { - padding: 5px; - z-index: 0; - /* cover the top shadow of dd */ - position: relative; - -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.35), 0 0 8px rgba(0, 0, 0, 0.2); } - -/* cover up the shadow at the bottom */ -dl.reflist dt::after { - content: " "; - width: 100%; - display: block; - height: 8px; - position: absolute; - background-color: #f6f6f6; - left: 0; - bottom: -8px; } - -dl.reflist dd { - border-left: 4px solid #5f082b; - padding: 2px 1% 2px 1%; - margin: 8px 8px 24px 8px; - outline: 8px solid #f6f6f6; - -moz-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); - -o-box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); - box-shadow: 0 0 4px 8px rgba(0, 0, 0, 0.35), 0 0 8px 8px rgba(0, 0, 0, 0.2); } - -dl.reflist dd p::before { - font-size: 85%; - content: "\25B6\00A0\00A0"; - display: inline-block; - width: 12pt; } - -dl.reflist dd p { - margin-top: 4px; - margin-bottom: 4px; } - -.paramkey { - text-align: right; } - -.paramtype { - font-family: monospace; - white-space: nowrap; - color: #002546; } - -.paramname { - color: black; - font-family: monospace; - white-space: nowrap; } - -.paramname em { - color: #820a32; - font-style: normal; } - -.paramname code { - color: #404040; - line-height: 14px; } - -.params, .retval, .exception, .tparams { - margin-left: 0; - padding-left: 0; - margin-bottom: -0.25em; } - -.params dt, .tparams dt { - margin-bottom: 0.5em; } - -.params .paramname, .tparams .paramname, .retval .paramname, .exception .paramname { - color: #820a32; - font-family: monospace; - font-weight: bold; - vertical-align: top; } - -.params .paramtype, .tparams .paramtype { - font-family: monospace; - font-style: italic; - vertical-align: top; } - -.params .paramdir, .tparams .paramdir { - font-family: "courier new",courier,monospace; - vertical-align: top; } - -/* line over parameters docs */ -.params, .tparams { - border-collapse: collapse; } - -.params tr, .tparams tr { - -moz-box-shadow: 0 -2px 0 -1px #606060; - -webkit-box-shadow: 0 -2px 0 -1px #606060; - -o-box-shadow: 0 -2px 0 -1px #606060; - box-shadow: 0 -2px 0 -1px #606060; } - -.params .paramname, .tparams .paramname { - border-top: 2px solid #5f082b; - padding-right: 5pt; } - -.params td, .tparams td { - padding-bottom: 1em; } - -table.mlabels { - border-spacing: 0px; } - -td.mlabels-left { - width: 100%; - padding: 0px; } - -td.mlabels-right { - vertical-align: bottom; - padding: 0px; - white-space: nowrap; } - -span.mlabels { - margin-left: 8px; } - -span.mlabel { - background-color: #444444; - border: none; - border-radius: 3px; - text-shadow: none; - color: white; - margin-right: 4px; - padding: 3px 5px; - font-size: 8pt; - white-space: nowrap; - vertical-align: middle; } - -.memdoc .definition { - position: relative; - padding-top: 0.5em; - /* move definition line to bottom of memdoc */ - order: 3; } - .memdoc .definition::before { - content: ""; - width: 33%; - height: 1px; - border-top: 1px solid black; - position: absolute; - top: 0; } - -table.directory { - border-top: 1px solid #c5c5c5; - border-bottom: 1px solid #c5c5c5; - border-collapse: collapse; - width: 100%; - font: 400 14px Roboto,sans-serif; } - table.directory tr { - background-color: white !important; } - table.directory tr.even { - background-color: #f6f6f6 !important; } - table.directory tr:hover { - background-color: #e6e6e6 !important; } - table.directory td.entry { - padding: 1.5pt 3pt 1.5pt 3pt; - white-space: normal; } - -div.directory { - border: none; } - div.directory table.directory tr { - line-height: 17pt; } - div.directory table.directory td { - margin: 0; - padding: 0.5pt 6pt 0.5pt 0; - vertical-align: middle; } - div.directory table.directory td.entry { - white-space: nowrap; } - div.directory table.directory td.desc { - width: 100%; - padding-left: 6pt; - border-left: 1px solid rgba(0, 0, 0, 0.05); } - -.directory .levels { - white-space: nowrap; - width: 100%; - text-align: right; - font-size: 9pt; } - .directory .levels span { - cursor: pointer; - padding-left: 2px; - padding-right: 2px; - color: #00549f; } - .directory .levels span:hover { - text-decoration: underline; } - -/* - * The tree view on the left - */ -.arrow { - color: black; - cursor: pointer; - font-size: 80%; - display: inline-block; - width: 16px; - height: 22px; - margin-left: 4px; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - .arrow:hover { - color: black; } - -#selected .arrow { - color: white; } - #selected .arrow:hover { - color: #d2d2d2; } - -#nav-tree { - background-image: none; - background-color: white; } - #nav-tree .item { - margin: 0; } - #nav-tree .item:hover { - background-color: #d2d2d2; } - #nav-tree .selected { - background-image: none; - background-color: #5f082b; - color: white; - text-shadow: none; } - #nav-tree .selected:hover { - background-image: none; - background-color: #5f082b; - color: white; - text-shadow: none; } - #nav-tree a { - color: black; } - -.ui-resizable-e { - background: #808080 url("splitbar_handle.svg") no-repeat center; - border-right: solid 1px #c0c0c0; - border-left: solid 1px black; } - .ui-resizable-e:hover { - background-color: #606060; } - -.icon { - font-family: monospace; - font-weight: bold; - font-size: 12px; - height: 15px; - width: 15px; - display: inline-block; - background-color: #444444; - color: white; - text-align: center; - border-radius: 3px; - margin: 0; - padding-top: 1px; - text-indent: -1px; } - -.icona { - width: 0; - height: 0; - display: none; - visibility: hidden; } - -.iconfopen { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image: url("folderopen.svg"); - background-position: 0; - background-repeat: no-repeat; - vertical-align: top; - display: inline-block; } - -.iconfclosed { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image: url("folderclosed.svg"); - background-position: 0; - background-repeat: no-repeat; - vertical-align: top; - display: inline-block; } - -.icondoc { - width: 24px; - height: 18px; - margin-bottom: 4px; - background-image: url("doc.svg"); - background-position: 0; - background-repeat: no-repeat; - vertical-align: top; - display: inline-block; } - -div.dynheader { - margin-top: 8px; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - -address { - font-style: normal; - color: #2A3D61; } - -table.doxtable caption { - caption-side: top; } - -table.doxtable { - border-collapse: collapse; - margin-top: 4px; - margin-bottom: 4px; } - -table.doxtable td, table.doxtable th { - border: 1px solid #444444; - padding: 3px 7px 2px; } - -table.doxtable th { - background-color: #444444; - color: #FFFFFF; - font-size: 110%; - padding-bottom: 4px; - padding-top: 5px; } - -/* - * The line at the bottom - */ -.navpath { - /* intermediate navelems */ - /* first navelem */ - /* last navelem */ } - .navpath ul { - font-size: 11px; - background-image: none; - height: 30px; - line-height: 30px; - color: black; - border: none; - border-top: 1px solid #808080; - overflow: hidden; - margin: 0px; - padding: 0px; } - .navpath li:not(:first-child) { - list-style-type: none; - float: left; - padding-left: 18px; - padding-right: 10px; - color: black; - background-color: white; - background-image: url("nav_edge_inter.svg"); - background-repeat: no-repeat; - background-position: left -1px; - background-size: auto 100%; } - .navpath li:first-child { - list-style-type: none; - float: left; - padding-left: 15px; - padding-right: 10px; - color: black; - background-color: white; - background-image: none; } - .navpath li:nth-last-child(2) { - list-style-type: none; - float: left; - padding-left: 10px; - padding-right: 15px; - color: white; - background-color: #5f082b; - background-image: url("nav_edge_right.svg"); - background-repeat: no-repeat; - background-position: right -1px; - background-size: auto 100%; } - .navpath li:nth-last-child(2):not(:first-child) { - list-style-type: none; - float: left; - padding-left: 15px; - padding-right: 15px; - color: white; - background-color: #5f082b; - background-image: url("nav_edge_left.svg"), url("nav_edge_right.svg"); - background-repeat: no-repeat; - background-position: -1px -1px, right -1px; - background-size: auto 100%; } - .navpath li.navelem a, .navpath .navpath li.navelem b { - height: 32px; - display: block; - text-decoration: none; - outline: none; - color: inherit; - font-family: Roboto,sans-serif; - text-shadow: none; - text-decoration: none; - font-weight: normal; } - .navpath li.navelem a:hover { - color: inherit; - text-decoration: underline; } - .navpath li.footer { - list-style-type: none; - float: right; - padding-left: 0; - padding-right: 10px; - background-color: #d5d5d5; - background-image: none; - color: black; - font-size: 8pt; } - .navpath li.footer:before { - content: ""; - width: 13px; - height: 30px; - display: inline-block; - float: left; - background-image: url("nav_edge_right.svg"); - background-repeat: no-repeat; - background-position: right 0; - background-size: auto 100%; - /* flip the element horizontally */ - -moz-transform: scaleX(-1); - -o-transform: scaleX(-1); - -webkit-transform: scaleX(-1); - transform: scaleX(-1); - filter: FlipH; - -ms-filter: "FlipH"; } - -div.summary { - -webkit-order: 2; - order: 2; - float: right; - font-size: 8pt; - padding-right: 5px; - width: 50%; - text-align: right; - margin-right: 0.5%; } - -div.summary a { - white-space: nowrap; } - -table.classindex { - margin: 10px; - white-space: nowrap; - margin-left: 1%; - margin-right: 1%; - width: 98%; - border: none; - border-top: 1px solid black; - border-bottom: 1px solid black; - border-spacing: 0.5em; - padding: 0; } - -div.ingroups { - font-size: 8pt; - width: 50%; - text-align: left; } - -div.ingroups a { - white-space: nowrap; } - -div.header { - display: -webkit-flex; - display: flex; - justify-content: space-between; - background-image: none; - background-color: #ffffff; - color: black; - margin: 5pt 5pt 0 5pt; - padding: 0 1ex 0 1ex; - align-items: center; - justify-content: center; - border-bottom: none; - position: relative; } - -div.header::after { - content: ""; - height: 2px; - width: 99%; - position: absolute; - bottom: -5px; - left: 0.5%; - background: #666; } - -div.headertitle { - -webkit-order: 1; - order: 1; - margin-right: auto; - text-align: center; } - -dl { - padding: 0; } - -dl.section { - margin-left: 0px; - padding-left: 0px; } - -dl.section > dt { - font-weight: bold; - font-family: sans-serif; } - -dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, -dl.deprecated, dl.todo, dl.test, dl.bug { - margin-left: -7px; - padding-left: 3px; } - -dl.note { - padding-left: 7px; - border: none; } - -dl.warning { - background-color: #ffe6ea; - border: 1px solid #ff0728; - border-left: 4px solid #ff0728; - padding-top: 4px; - padding-bottom: 3px; } - -dl.attention { - border-left: 4px solid #ff0728; } - -dl.pre, dl.post, dl.invariant { - background-color: #f0ffe6; - border: 1px solid #5eb82a; - border-left: 4px solid #5eb82a; } - -dl.deprecated { - background-color: #f6f6f6; - border: 1px solid black; } - -dl.todo { - border-left: 4px solid #e8d500; } - -dl.test { - border-left: 4px solid #00549f; } - -dl.bug { - background-color: #f6f6f6; - border: 1px solid #cc071e; - border-left: 4px solid #cc071e; } - -dl.section dd { - margin-bottom: 6px; } - -.memdoc dl dt a.el { - font-weight: bold; - color: black; } - -#projectlogo { - text-align: center; - vertical-align: bottom; - border-collapse: separate; } - -#projectlogo img { - border: 0px none; } - -#projectalign { - vertical-align: middle; } - -#projectname { - font: 300% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 2px 0px; } - -#projectbrief { - font: 120% Tahoma, Arial,sans-serif; - margin: 0px; - padding: 0px; } - -#projectnumber { - font: 50% Roboto,sans-serif; - margin: 0px; - padding: 0px; } - -#titlearea { - padding: 0px; - margin: 0px; - width: 100%; - border-bottom: none; } - #titlearea:nth-last-child(2) { - border-bottom: 2px solid #444444; } - -.image { - text-align: center; } - -.dotgraph, .mscgraph, .diagraph { - text-align: center; } - -.caption { - font-weight: bold; } - -div.zoom { - border: 1px solid #90A5CE; } - -dl.citelist { - margin-bottom: 5ex; } - dl.citelist dt { - color: black; - float: left; - font-weight: bold; - padding: 5px 0; - margin: 2px 10pt 2px 0; } - dl.citelist dd { - margin: 2px 0; - padding: 5px 0; } - dl.citelist .startdd { - margin-top: 0; } - -div.toc { - background-color: transparent; - border: 1px solid #414141; - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - float: right; - height: auto; - margin: 0 8px 10px 10px; - padding: 10px 15px 5px 25px; - width: auto; } - div.toc li { - background: transparent; - font: 10pt Roboto,DejaVu Sans,sans-serif; - padding-left: 0; - padding-top: 0.5ex; } - div.toc li .level1 { - margin-left: 10pt; } - div.toc li .level2 { - margin-left: 10pt; } - div.toc li .level3 { - margin-left: 10pt; } - div.toc li .level4 { - margin-left: 10pt; } - div.toc h3 { - font: bold 12px/1.2 Roboto,DejaVu Sans,sans-serif; - color: black; - border-bottom: none; - margin: 0; - letter-spacing: 1px; } - div.toc ul { - list-style: disc; - border: none; - padding: 0; } - -.inherit_header { - font-weight: bold; - color: gray; - cursor: pointer; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } - -.inherit_header td { - padding: 6px 0px 2px 5px; } - -.inherit { - display: none; } - -/* tooltip related style info */ -.ttc { - position: absolute; - display: none; } - -#powerTip { - cursor: default; - white-space: nowrap; - background-color: #ffffff; - border: 1px solid #323232; - border-radius: 0; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; - display: none; - font-size: smaller; - max-width: 80%; - opacity: 0.9; - padding: 1ex 1em 1em 1em; - position: absolute; - z-index: 2147483647; } - #powerTip div.ttdoc { - color: grey; - font-style: italic; } - #powerTip div.ttname a { - font-weight: bold; } - #powerTip div.ttname { - font-weight: bold; } - #powerTip div.ttdeci { - color: #006318; } - #powerTip div { - margin: 0px; - padding: 0px; - font: 12px/16px Roboto,sans-serif; } - #powerTip:before, #powerTip:after { - content: ""; - position: absolute; - margin: 0px; } - -@media print { - #top { - display: none; } - #side-nav { - display: none; } - #nav-path { - display: none; } - body { - overflow: visible; } - h1, h2, h3, h4, h5, h6 { - page-break-after: avoid; } - .summary { - display: none; } - .memitem { - page-break-inside: avoid; } - #doc-content { - margin-left: 0 !important; - height: auto !important; - width: auto !important; - overflow: inherit; - display: inline; - background-color: white; } } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 64458cfc..4fb842b0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -30,9 +30,11 @@ if (UNIX AND NOT WIN32) 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}) + if (WITH_SERVER) + 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_SERVER) endif (WITH_SFTP) add_executable(ssh-client ssh_client.c ${examples_SRCS}) diff --git a/examples/authentication.c b/examples/authentication.c index 7c47c8bd..f6a5dbd5 100644 --- a/examples/authentication.c +++ b/examples/authentication.c @@ -30,8 +30,8 @@ int authenticate_kbdint(ssh_session session, const char *password) err = ssh_userauth_kbdint(session, NULL, NULL); while (err == SSH_AUTH_INFO) { - const char *instruction; - const char *name; + const char *instruction = NULL; + const char *name = NULL; char buffer[128]; int i, n; @@ -48,8 +48,8 @@ int authenticate_kbdint(ssh_session session, const char *password) } for (i = 0; i < n; i++) { - const char *answer; - const char *prompt; + const char *answer = NULL; + const char *prompt = NULL; char echo; prompt = ssh_userauth_kbdint_getprompt(session, i, &echo); @@ -58,7 +58,7 @@ int authenticate_kbdint(ssh_session session, const char *password) } if (echo) { - char *p; + char *p = NULL; printf("%s", prompt); @@ -66,7 +66,6 @@ int authenticate_kbdint(ssh_session session, const char *password) return SSH_AUTH_ERROR; } - buffer[sizeof(buffer) - 1] = '\0'; if ((p = strchr(buffer, '\n'))) { *p = '\0'; } @@ -75,7 +74,7 @@ int authenticate_kbdint(ssh_session session, const char *password) return SSH_AUTH_ERROR; } - memset(buffer, 0, strlen(buffer)); + memset(buffer, 0, sizeof(buffer)); } else { if (password && strstr(prompt, "Password:")) { answer = password; @@ -143,11 +142,11 @@ int authenticate_console(ssh_session session) int rc; int method; char password[128] = {0}; - char *banner; + char *banner = NULL; // Try to authenticate rc = ssh_userauth_none(session, NULL); - if (rc == SSH_AUTH_ERROR) { + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { error(session); return rc; } @@ -156,7 +155,7 @@ int authenticate_console(ssh_session session) while (rc != SSH_AUTH_SUCCESS) { if (method & SSH_AUTH_METHOD_GSSAPI_MIC){ rc = ssh_userauth_gssapi(session); - if(rc == SSH_AUTH_ERROR) { + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { error(session); return rc; } else if (rc == SSH_AUTH_SUCCESS) { @@ -166,7 +165,7 @@ int authenticate_console(ssh_session session) // Try to authenticate with public key first if (method & SSH_AUTH_METHOD_PUBLICKEY) { rc = ssh_userauth_publickey_auto(session, NULL, NULL); - if (rc == SSH_AUTH_ERROR) { + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { error(session); return rc; } else if (rc == SSH_AUTH_SUCCESS) { @@ -206,7 +205,7 @@ int authenticate_console(ssh_session session) // Try to authenticate with keyboard interactive"; if (method & SSH_AUTH_METHOD_INTERACTIVE) { rc = authenticate_kbdint(session, NULL); - if (rc == SSH_AUTH_ERROR) { + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { error(session); return rc; } else if (rc == SSH_AUTH_SUCCESS) { @@ -221,7 +220,7 @@ int authenticate_console(ssh_session session) // Try to authenticate with password if (method & SSH_AUTH_METHOD_PASSWORD) { rc = ssh_userauth_password(session, NULL, password); - if (rc == SSH_AUTH_ERROR) { + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { error(session); return rc; } else if (rc == SSH_AUTH_SUCCESS) { diff --git a/examples/connect_ssh.c b/examples/connect_ssh.c index c9e4ef6e..b07f824d 100644 --- a/examples/connect_ssh.c +++ b/examples/connect_ssh.c @@ -21,47 +21,57 @@ clients must be made or how a client should react. #include "examples_common.h" #include -ssh_session connect_ssh(const char *host, const char *user,int verbosity){ - ssh_session session; - int auth=0; +ssh_session connect_ssh(const char *host, const char *port, const char *user, int verbosity) +{ + ssh_session session = NULL; + int auth = 0; - session=ssh_new(); - if (session == NULL) { - return NULL; - } + session = ssh_new(); + if (session == NULL) { + return NULL; + } - if(user != NULL){ - if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { - ssh_free(session); - return NULL; + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + ssh_free(session); + return NULL; + } } - } - if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { - ssh_free(session); - return NULL; - } - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); - if(ssh_connect(session)){ - fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); - ssh_disconnect(session); - ssh_free(session); - return NULL; - } - if(verify_knownhost(session)<0){ + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT_STR, port) < 0) { + ssh_free(session); + return NULL; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + ssh_free(session); + return NULL; + } + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + if (verify_knownhost(session) < 0) { + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + auth = authenticate_console(session); + if (auth == SSH_AUTH_SUCCESS) { + return session; + } else if (auth == SSH_AUTH_DENIED) { + fprintf(stderr, "Authentication failed\n"); + } else { + fprintf(stderr, + "Error while authenticating : %s\n", + ssh_get_error(session)); + } ssh_disconnect(session); ssh_free(session); return NULL; - } - auth=authenticate_console(session); - if(auth==SSH_AUTH_SUCCESS){ - return session; - } else if(auth==SSH_AUTH_DENIED){ - fprintf(stderr,"Authentication failed\n"); - } else { - fprintf(stderr,"Error while authenticating : %s\n",ssh_get_error(session)); - } - ssh_disconnect(session); - ssh_free(session); - return NULL; } diff --git a/examples/examples_common.h b/examples/examples_common.h index d3f4e1aa..6f5a1b12 100644 --- a/examples/examples_common.h +++ b/examples/examples_common.h @@ -16,11 +16,11 @@ clients must be made or how a client should react. #include /** Zero a structure */ -#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) int authenticate_console(ssh_session session); int authenticate_kbdint(ssh_session session, const char *password); int verify_knownhost(ssh_session session); -ssh_session connect_ssh(const char *hostname, const char *user, int verbosity); +ssh_session connect_ssh(const char *hostname, const char *port, const char *user, int verbosity); #endif /* EXAMPLES_COMMON_H_ */ diff --git a/examples/exec.c b/examples/exec.c index 77d3be47..9c1d8dbb 100644 --- a/examples/exec.c +++ b/examples/exec.c @@ -5,13 +5,13 @@ #include "examples_common.h" int main(void) { - ssh_session session; - ssh_channel channel; + ssh_session session = NULL; + ssh_channel channel = NULL; char buffer[256]; int rbytes, wbytes, total = 0; int rc; - session = connect_ssh("localhost", NULL, 0); + session = connect_ssh("localhost", NULL, NULL, 0); if (session == NULL) { ssh_finalize(); return 1; 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 bbe15e0e..73d70211 100644 --- a/examples/keygen2.c +++ b/examples/keygen2.c @@ -38,6 +38,7 @@ struct arguments_st { unsigned long bits; char *file; char *passphrase; + char *format; int action_list; }; @@ -96,6 +97,16 @@ static struct argp_option options[] = { .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 + }, { /* End of the options */ 0 @@ -168,6 +179,9 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) 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. */ @@ -296,6 +310,7 @@ list_fingerprint(char *file) rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); if (rc != SSH_OK) { fprintf(stderr, "Failed to get key fingerprint\n"); + ssh_key_free(key); return; } ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); @@ -335,7 +350,12 @@ int main(int argc, char *argv[]) goto end; } - if (arguments.action_list && arguments.file) { + if (arguments.file == NULL) { + fprintf(stderr, "Error: Missing argument file\n"); + goto end; + } + + if (arguments.action_list) { list_fingerprint(arguments.file); goto end; } @@ -382,8 +402,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/knownhosts.c b/examples/knownhosts.c index 0726bfa8..2857a085 100644 --- a/examples/knownhosts.c +++ b/examples/knownhosts.c @@ -38,7 +38,7 @@ int verify_knownhost(ssh_session session) char buf[10]; unsigned char *hash = NULL; size_t hlen; - ssh_key srv_pubkey; + ssh_key srv_pubkey = NULL; int rc; rc = ssh_get_server_publickey(session, &srv_pubkey); diff --git a/examples/libssh_scp.c b/examples/libssh_scp.c index adb82ef0..2b1a7627 100644 --- a/examples/libssh_scp.c +++ b/examples/libssh_scp.c @@ -26,10 +26,11 @@ program. #define BUF_SIZE 16384 #endif -static char **sources; +static char **sources = NULL; static int nsources; -static char *destination; +static char *destination = NULL; static int verbosity = 0; +static char *port = NULL; struct location { int is_ssh; @@ -49,9 +50,10 @@ enum { static void usage(const char *argv0) { fprintf(stderr, "Usage : %s [options] [[user@]host1:]file1 ... \n" " [[user@]host2:]destination\n" - "sample scp client - libssh-%s\n", - // "Options :\n", - // " -r : use RSA to verify host public key\n", + "sample scp client - libssh-%s\n" + "Options :\n" + " -P : use port to connect to remote host\n" + " -v : increase verbosity of libssh. Can be used multiple times\n", argv0, ssh_version(0)); exit(0); @@ -60,11 +62,14 @@ static void usage(const char *argv0) { static int opts(int argc, char **argv) { int i; - while((i = getopt(argc, argv, "v")) != -1) { + while((i = getopt(argc, argv, "P:v")) != -1) { switch(i) { case 'v': verbosity++; break; + case 'P': + port = optarg; + break; default: fprintf(stderr, "unknown option %c\n", optopt); usage(argv[0]); @@ -114,9 +119,14 @@ static void location_free(struct location *loc) } } -static struct location *parse_location(char *loc) { - struct location *location; - char *ptr; +static struct location *parse_location(char *loc) +{ + struct location *location = NULL; + char *ptr = NULL; + + if (loc == NULL) { + return NULL; + } location = malloc(sizeof(struct location)); if (location == NULL) { @@ -178,7 +188,7 @@ static void close_location(struct location *loc) { static int open_location(struct location *loc, int flag) { if (loc->is_ssh && flag == WRITE) { - loc->session = connect_ssh(loc->host, loc->user, verbosity); + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); if (!loc->session) { fprintf(stderr, "Couldn't connect to %s\n", loc->host); return -1; @@ -204,7 +214,7 @@ static int open_location(struct location *loc, int flag) { } return 0; } else if (loc->is_ssh && flag == READ) { - loc->session = connect_ssh(loc->host, loc->user, verbosity); + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); if (!loc->session) { fprintf(stderr, "Couldn't connect to %s\n", loc->host); return -1; diff --git a/examples/proxy.c b/examples/proxy.c index 49c39e4c..ab69b18e 100644 --- a/examples/proxy.c +++ b/examples/proxy.c @@ -35,8 +35,8 @@ clients must be made or how a client should react. static int authenticated=0; static int tries = 0; static int error = 0; -static ssh_channel chan=NULL; -static char *username; +static ssh_channel chan = NULL; +static char *username = NULL; static ssh_gssapi_creds client_creds = NULL; static int auth_password(ssh_session session, const char *user, @@ -204,11 +204,12 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; #endif /* HAVE_ARGP_H */ -int main(int argc, char **argv){ - ssh_session session; - ssh_bind sshbind; - ssh_event mainloop; - ssh_session client_session; +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; + ssh_session client_session = NULL; struct ssh_server_callbacks_struct cb = { .userdata = NULL, @@ -219,7 +220,7 @@ int main(int argc, char **argv){ char buf[BUF_SIZE]; char host[128]=""; - char *ptr; + char *ptr = NULL; int i,r, rc; sshbind=ssh_bind_new(); @@ -336,4 +337,3 @@ int main(int argc, char **argv){ ssh_finalize(); return 0; } - diff --git a/examples/sample_sftpserver.c b/examples/sample_sftpserver.c index b96f4ef7..ced99318 100644 --- a/examples/sample_sftpserver.c +++ b/examples/sample_sftpserver.c @@ -148,6 +148,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) ssh_bind sshbind = state->input; static int no_default_keys = 0; static int rsa_already_set = 0, ecdsa_already_set = 0; + static int verbosity = 0; switch (key) { @@ -176,8 +177,10 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); break; case 'v': - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, - "3"); + verbosity++; + ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); break; case ARGP_KEY_ARG: if (state->arg_num >= 1) @@ -213,10 +216,7 @@ 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; +struct channel_data_struct { sftp_session sftp; }; @@ -378,18 +378,11 @@ static void handle_session(ssh_event event, ssh_session session) 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) { + if (ssh_event_dopoll(event, 100) == 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)); + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); ssh_channel_send_eof(sdata.channel); ssh_channel_close(sdata.channel); diff --git a/examples/samplesshd-cb.c b/examples/samplesshd-cb.c index 6b272102..31e035ff 100644 --- a/examples/samplesshd-cb.c +++ b/examples/samplesshd-cb.c @@ -244,10 +244,11 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; #endif /* HAVE_ARGP_H */ -int main(int argc, char **argv){ - ssh_session session; - ssh_bind sshbind; - ssh_event mainloop; +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; struct ssh_server_callbacks_struct cb = { .userdata = NULL, .auth_none_function = auth_none, @@ -339,4 +340,3 @@ int main(int argc, char **argv){ ssh_finalize(); return 0; } - diff --git a/examples/samplesshd-kbdint.c b/examples/samplesshd-kbdint.c index d382abac..919eb338 100644 --- a/examples/samplesshd-kbdint.c +++ b/examples/samplesshd-kbdint.c @@ -174,8 +174,8 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; #endif /* HAVE_ARGP_H */ -static const char *name; -static const char *instruction; +static const char *name = NULL; +static const char *instruction = NULL; static const char *prompts[2]; static char echo[] = { 1, 0 }; @@ -279,11 +279,12 @@ static int authenticate(ssh_session session) { return 0; } -int main(int argc, char **argv){ - ssh_session session; - ssh_bind sshbind; - ssh_message message; - ssh_channel chan=0; +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_message message = NULL; + ssh_channel chan = NULL; char buf[BUF_SIZE]; int auth=0; int shell=0; @@ -411,4 +412,3 @@ int main(int argc, char **argv){ ssh_finalize(); return 0; } - diff --git a/examples/scp_download.c b/examples/scp_download.c index e6c1e796..24355e29 100644 --- a/examples/scp_download.c +++ b/examples/scp_download.c @@ -32,151 +32,163 @@ static const char *createcommand = "cd /tmp/libssh_tests && date > a && date > b && mkdir c && date > d"; static char *host = NULL; -static void usage(const char *argv0){ - fprintf(stderr,"Usage : %s [options] host\n" - "sample tiny scp downloader client - libssh-%s\n" - "This program will create files in /tmp and try to fetch them\n", -// "Options :\n", -// " -r : use RSA to verify host public key\n", - argv0, - ssh_version(0)); - exit(0); +static void usage(const char *argv0) +{ + fprintf(stderr, + "Usage : %s [options] host\n" + "sample tiny scp downloader client - libssh-%s\n" + "This program will create files in /tmp and try to fetch them\n", + 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; +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc, argv, "v")) != -1) { + switch (i) { + case 'v': + verbosity++; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } } - } - host = argv[optind]; - if(host == NULL) - usage(argv[0]); - return 0; + host = argv[optind]; + if (host == NULL) + usage(argv[0]); + return 0; } -static void create_files(ssh_session session){ - ssh_channel channel=ssh_channel_new(session); - char buffer[1]; - int rc; +static void create_files(ssh_session session) +{ + ssh_channel channel = ssh_channel_new(session); + char buffer[1]; + int rc; - if(channel == NULL){ - fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); - exit(EXIT_FAILURE); - } - if(ssh_channel_open_session(channel) != SSH_OK){ - fprintf(stderr,"Error creating channel: %s\n",ssh_get_error(session)); - ssh_channel_free(channel); - exit(EXIT_FAILURE); - } - if(ssh_channel_request_exec(channel,createcommand) != SSH_OK){ - fprintf(stderr,"Error executing command: %s\n",ssh_get_error(session)); - ssh_channel_close(channel); - ssh_channel_free(channel); - exit(EXIT_FAILURE); - } - while(!ssh_channel_is_eof(channel)){ - rc = ssh_channel_read(channel,buffer,1,1); - if (rc != 1) { - fprintf(stderr, "Error reading from channel\n"); - ssh_channel_close(channel); - ssh_channel_free(channel); - return; - } - - rc = write(1, buffer, 1); - if (rc < 0) { - fprintf(stderr, "Error writing to buffer\n"); - ssh_channel_close(channel); - ssh_channel_free(channel); - return; - } - } - ssh_channel_close(channel); - ssh_channel_free(channel); + if (channel == NULL) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + exit(EXIT_FAILURE); + } + if (ssh_channel_open_session(channel) != SSH_OK) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + if (ssh_channel_request_exec(channel, createcommand) != SSH_OK) { + fprintf(stderr, + "Error executing command: %s\n", + ssh_get_error(session)); + ssh_channel_close(channel); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + while (!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buffer, 1, 1); + if (rc != 1) { + fprintf(stderr, "Error reading from channel\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + + rc = write(1, buffer, 1); + if (rc < 0) { + fprintf(stderr, "Error writing to buffer\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + } + ssh_channel_close(channel); + ssh_channel_free(channel); } - -static int fetch_files(ssh_session session){ - int size; - char buffer[BUF_SIZE]; - int mode; - char *filename; - int r; - ssh_scp scp=ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, "/tmp/libssh_tests/*"); - if(ssh_scp_init(scp) != SSH_OK){ - fprintf(stderr,"error initializing scp: %s\n",ssh_get_error(session)); - ssh_scp_free(scp); - return -1; - } - printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); - do { - - r=ssh_scp_pull_request(scp); - switch(r){ - case SSH_SCP_REQUEST_NEWFILE: - size=ssh_scp_request_get_size(scp); - filename=strdup(ssh_scp_request_get_filename(scp)); - mode=ssh_scp_request_get_permissions(scp); - printf("downloading file %s, size %d, perms 0%o\n",filename,size,mode); - free(filename); - ssh_scp_accept_request(scp); - r=ssh_scp_read(scp,buffer,sizeof(buffer)); - if(r==SSH_ERROR){ - fprintf(stderr,"Error reading scp: %s\n",ssh_get_error(session)); - ssh_scp_close(scp); - ssh_scp_free(scp); - return -1; - } - printf("done\n"); - break; - case SSH_ERROR: - fprintf(stderr,"Error: %s\n",ssh_get_error(session)); - ssh_scp_close(scp); - ssh_scp_free(scp); - return -1; - case SSH_SCP_REQUEST_WARNING: - fprintf(stderr,"Warning: %s\n",ssh_scp_request_get_warning(scp)); - break; - case SSH_SCP_REQUEST_NEWDIR: - filename=strdup(ssh_scp_request_get_filename(scp)); - mode=ssh_scp_request_get_permissions(scp); - printf("downloading directory %s, perms 0%o\n",filename,mode); - free(filename); - ssh_scp_accept_request(scp); - break; - case SSH_SCP_REQUEST_ENDDIR: - printf("End of directory\n"); - break; - case SSH_SCP_REQUEST_EOF: - printf("End of requests\n"); - goto end; - } - } while (1); - end: - ssh_scp_close(scp); - ssh_scp_free(scp); - return 0; +static int fetch_files(ssh_session session) +{ + int size; + char buffer[BUF_SIZE]; + int mode; + char *filename = NULL; + int r; + ssh_scp scp = ssh_scp_new(session, + SSH_SCP_READ | SSH_SCP_RECURSIVE, + "/tmp/libssh_tests/*"); + if (ssh_scp_init(scp) != SSH_OK) { + fprintf(stderr, "error initializing scp: %s\n", ssh_get_error(session)); + ssh_scp_free(scp); + return -1; + } + printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); + do { + r = ssh_scp_pull_request(scp); + switch (r) { + case SSH_SCP_REQUEST_NEWFILE: + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading file %s, size %d, perms 0%o\n", + filename, + size, + mode); + free(filename); + ssh_scp_accept_request(scp); + r = ssh_scp_read(scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + } + printf("done\n"); + break; + case SSH_ERROR: + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + case SSH_SCP_REQUEST_WARNING: + fprintf(stderr, "Warning: %s\n", ssh_scp_request_get_warning(scp)); + break; + case SSH_SCP_REQUEST_NEWDIR: + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading directory %s, perms 0%o\n", filename, mode); + free(filename); + ssh_scp_accept_request(scp); + break; + case SSH_SCP_REQUEST_ENDDIR: + printf("End of directory\n"); + break; + case SSH_SCP_REQUEST_EOF: + printf("End of requests\n"); + goto end; + } + } while (1); +end: + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; } -int main(int argc, char **argv){ - ssh_session session; - if(opts(argc,argv)<0) - return EXIT_FAILURE; - session=connect_ssh(host,NULL,verbosity); - if(session == NULL) - return EXIT_FAILURE; - create_files(session); - fetch_files(session); - ssh_disconnect(session); - ssh_free(session); - ssh_finalize(); - return 0; +int main(int argc, char **argv) +{ + ssh_session session = NULL; + if (opts(argc, argv) < 0) + return EXIT_FAILURE; + session = connect_ssh(host, NULL, NULL, verbosity); + if (session == NULL) + return EXIT_FAILURE; + create_files(session); + fetch_files(session); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 0; } diff --git a/examples/senddata.c b/examples/senddata.c index 21181fb9..5be65af3 100644 --- a/examples/senddata.c +++ b/examples/senddata.c @@ -5,60 +5,60 @@ #define LIMIT 0x100000000UL -int main(void) { - ssh_session session; - ssh_channel channel; - char buffer[1024*1024]; - int rc; - uint64_t total=0; - uint64_t lastshown=4096; - session = connect_ssh("localhost", NULL, 0); - if (session == NULL) { - return 1; - } +int main(void) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + char buffer[1024 * 1024] = {0}; + int rc; + uint64_t total = 0; + uint64_t lastshown = 4096; + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + return 1; + } - channel = ssh_channel_new(session); - if (channel == NULL) { - ssh_disconnect(session); - return 1; - } + channel = ssh_channel_new(session); + if (channel == NULL) { + ssh_disconnect(session); + return 1; + } - rc = ssh_channel_open_session(channel); - if (rc < 0) { - ssh_channel_close(channel); - ssh_disconnect(session); - return 1; - } + rc = ssh_channel_open_session(channel); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } - rc = ssh_channel_request_exec(channel, "cat > /dev/null"); - if (rc < 0) { - ssh_channel_close(channel); - ssh_disconnect(session); - return 1; - } + rc = ssh_channel_request_exec(channel, "cat > /dev/null"); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { + total += rc; + if (total / 2 >= lastshown) { + printf("written %llx\n", (long long unsigned int)total); + lastshown = total; + } + if (total > LIMIT) + break; + } - while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { - total += rc; - if(total/2 >= lastshown){ - printf("written %llx\n", (long long unsigned int) total); - lastshown=total; + if (rc < 0) { + printf("error : %s\n", ssh_get_error(session)); + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; } - if(total > LIMIT) - break; - } - - if (rc < 0) { - printf("error : %s\n",ssh_get_error(session)); - ssh_channel_close(channel); - ssh_disconnect(session); - return 1; - } - ssh_channel_send_eof(channel); - ssh_channel_close(channel); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); - ssh_disconnect(session); + ssh_disconnect(session); - return 0; + return 0; } diff --git a/examples/ssh_X11_client.c b/examples/ssh_X11_client.c index b3fa0e2d..195b259b 100644 --- a/examples/ssh_X11_client.c +++ b/examples/ssh_X11_client.c @@ -70,6 +70,7 @@ #include #include +#include #include #include #include @@ -231,7 +232,7 @@ static void _logging_callback(int priority, const char *function, milliseconds = _current_timestamp(); - fprintf(fp, "[%s.%jd, %d] %s: %s\n", buf, milliseconds, priority, + fprintf(fp, "[%s.%" PRId64 ", %d] %s: %s\n", buf, milliseconds, priority, function, buffer); fclose(fp); } @@ -343,10 +344,11 @@ static void delete_item(ssh_channel channel) static node_t *search_item(ssh_channel channel) { - node_t *current = node; + node_t *current = NULL; pthread_mutex_lock(&mutex); + current = node; while (current != NULL) { if (current->channel == channel) { pthread_mutex_unlock(&mutex); diff --git a/examples/ssh_client.c b/examples/ssh_client.c index cc249183..1b516830 100644 --- a/examples/ssh_client.c +++ b/examples/ssh_client.c @@ -39,8 +39,6 @@ #include #include -#include - #include "examples_common.h" #define MAXCMD 10 @@ -53,7 +51,7 @@ static struct termios terminal; static char *pcap_file = NULL; -static char *proxycommand; +static char *proxycommand = NULL; static int auth_callback(const char *prompt, char *buf, @@ -88,22 +86,24 @@ static void add_cmd(char *cmd) static void usage(void) { - fprintf(stderr, - "Usage : ssh [options] [login@]hostname\n" - "sample client - libssh-%s\n" - "Options :\n" - " -l user : log in as user\n" - " -p port : connect to port\n" - " -r : use RSA to verify host public key\n" - " -F file : parse configuration file instead of default one\n" + fprintf( + stderr, + "Usage : ssh [options] [login@]hostname\n" + "sample client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -o option : set configuration option (e.g., -o Compression=yes)\n" + " -r : use RSA to verify host public key\n" + " -F file : parse configuration file instead of default one\n" #ifdef WITH_PCAP - " -P file : create a pcap debugging file\n" + " -P file : create a pcap debugging file\n" #endif #ifndef _WIN32 - " -T proxycommand : command to execute as a socket proxy\n" + " -T proxycommand : command to execute as a socket proxy\n" #endif - "\n", - ssh_version(0)); + "\n", + ssh_version(0)); exit(0); } @@ -112,8 +112,8 @@ static int opts(int argc, char **argv) { int i; - while((i = getopt(argc,argv,"T:P:F:")) != -1) { - switch(i){ + while ((i = getopt(argc, argv, "T:P:F:")) != -1) { + switch (i) { case 'P': pcap_file = optarg; break; @@ -159,16 +159,14 @@ static void cfmakeraw(struct termios *termios_p) static void do_cleanup(int i) { - /* unused variable */ - (void) i; + (void)i; - tcsetattr(0, TCSANOW, &terminal); + tcsetattr(0, TCSANOW, &terminal); } static void do_exit(int i) { - /* unused variable */ - (void) i; + (void)i; do_cleanup(0); exit(0); @@ -179,7 +177,7 @@ static int signal_delayed = 0; #ifdef SIGWINCH static void sigwindowchanged(int i) { - (void) i; + (void)i; signal_delayed = 1; } #endif @@ -213,18 +211,18 @@ static void select_loop(ssh_session session,ssh_channel channel) /* stdin */ connector_in = ssh_connector_new(session); ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); - ssh_connector_set_in_fd(connector_in, 0); + ssh_connector_set_in_fd(connector_in, STDIN_FILENO); ssh_event_add_connector(event, connector_in); /* stdout */ connector_out = ssh_connector_new(session); - ssh_connector_set_out_fd(connector_out, 1); + ssh_connector_set_out_fd(connector_out, STDOUT_FILENO); ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); ssh_event_add_connector(event, connector_out); /* stderr */ connector_err = ssh_connector_new(session); - ssh_connector_set_out_fd(connector_err, 2); + ssh_connector_set_out_fd(connector_err, STDERR_FILENO); ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); ssh_event_add_connector(event, connector_err); @@ -251,9 +249,9 @@ static void select_loop(ssh_session session,ssh_channel channel) static void shell(ssh_session session) { - ssh_channel channel; + ssh_channel channel = NULL; struct termios terminal_local; - int interactive=isatty(0); + int interactive = isatty(0); channel = ssh_channel_new(session); if (channel == NULL) { @@ -339,7 +337,7 @@ static void batch_shell(ssh_session session) static int client(ssh_session session) { int auth = 0; - char *banner; + char *banner = NULL; int state; if (user) { @@ -357,10 +355,8 @@ static int client(ssh_session session) } /* Parse configuration file if specified: The command-line options will * overwrite items loaded from configuration file */ - if (config_file != NULL) { - ssh_options_parse_config(session, config_file); - } else { - ssh_options_parse_config(session, NULL); + if (ssh_options_parse_config(session, config_file) < 0) { + return -1; } if (ssh_connect(session)) { @@ -423,7 +419,7 @@ static void cleanup_pcap(void) int main(int argc, char **argv) { - ssh_session session; + ssh_session session = NULL; ssh_init(); session = ssh_new(); diff --git a/examples/ssh_server.c b/examples/ssh_server.c index ef444458..fb1541d9 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -45,31 +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 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}; char username[128] = "myuser"; @@ -144,14 +123,6 @@ static struct argp_option options[] = { .doc = "Set expected password.", .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', @@ -164,70 +135,53 @@ static struct argp_option options[] = { }; /* Parse a single option. */ -static error_t parse_opt (int key, char *arg, struct argp_state *state) { +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 'u': - strncpy(username, arg, sizeof(username) - 1); - break; - case 'P': - strncpy(password, arg, sizeof(password) - 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; + 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); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); + break; + case 'u': + strncpy(username, arg, sizeof(username) - 1); + break; + case 'P': + strncpy(password, arg, sizeof(password) - 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); + } + break; + default: + return ARGP_ERR_UNKNOWN; } return 0; } @@ -235,29 +189,20 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { /* Our argp parser. */ static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; #else -static int parse_opt(int argc, char **argv, ssh_bind sshbind) { - int no_default_keys = 0; - int rsa_already_set = 0; - int ecdsa_already_set = 0; +static int +parse_opt(int argc, char **argv, ssh_bind sshbind) +{ int key; - while((key = getopt(argc, argv, "a: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 == 'k') { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); - /* 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; } else if (key == 'r') { 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_HOSTKEY, optarg); - ecdsa_already_set = 1; } else if (key == 'a') { strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1); } else if (key == 'u') { @@ -280,7 +225,6 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { " -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 (deprecated alias for 'k').\n" @@ -303,12 +247,6 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, argv[optind]); - if (!no_default_keys) { - set_default_keys(sshbind, - rsa_already_set, - ecdsa_already_set); - } - return 0; } #endif /* HAVE_ARGP_H */ @@ -339,49 +277,74 @@ struct session_data_struct { int authenticated; }; -static int data_function(ssh_session session, ssh_channel channel, void *data, - uint32_t len, int is_stderr, void *userdata) { - struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; +static int +data_function(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; - (void) session; - (void) channel; - (void) is_stderr; + (void)session; + (void)channel; + (void)is_stderr; if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { return 0; } - return write(cdata->child_stdin, (char *) data, len); + return write(cdata->child_stdin, (char *)data, len); } -static int pty_request(ssh_session session, ssh_channel channel, - const char *term, int cols, int rows, int py, int px, - void *userdata) { +static int +pty_request(ssh_session session, + ssh_channel channel, + const char *term, + int cols, + int rows, + int py, + int px, + void *userdata) +{ struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + int rc; - (void) session; - (void) channel; - (void) term; + (void)session; + (void)channel; + (void)term; cdata->winsize->ws_row = rows; cdata->winsize->ws_col = cols; cdata->winsize->ws_xpixel = px; cdata->winsize->ws_ypixel = py; - if (openpty(&cdata->pty_master, &cdata->pty_slave, NULL, NULL, - cdata->winsize) != 0) { + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { fprintf(stderr, "Failed to open pty\n"); return SSH_ERROR; } return SSH_OK; } -static int pty_resize(ssh_session session, ssh_channel channel, int cols, - int rows, int py, int px, void *userdata) { +static int +pty_resize(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; - (void) session; - (void) channel; + (void)session; + (void)channel; cdata->winsize->ws_row = rows; cdata->winsize->ws_col = cols; @@ -395,30 +358,36 @@ static int pty_resize(ssh_session session, ssh_channel channel, int cols, return SSH_ERROR; } -static int exec_pty(const char *mode, const char *command, - struct channel_data_struct *cdata) { - switch(cdata->pid = fork()) { - case -1: - close(cdata->pty_master); - close(cdata->pty_slave); - fprintf(stderr, "Failed to fork\n"); - return SSH_ERROR; - case 0: - close(cdata->pty_master); - if (login_tty(cdata->pty_slave) != 0) { - exit(1); - } - execl("/bin/sh", "sh", mode, command, NULL); - exit(0); - default: - close(cdata->pty_slave); - /* pty fd is bi-directional */ - cdata->child_stdout = cdata->child_stdin = cdata->pty_master; +static int +exec_pty(const char *mode, + const char *command, + struct channel_data_struct *cdata) +{ + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + return SSH_ERROR; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; } return SSH_OK; } -static int exec_nopty(const char *command, struct channel_data_struct *cdata) { +static int +exec_nopty(const char *command, struct channel_data_struct *cdata) +{ int in[2], out[2], err[2]; /* Do the plumbing to be able to talk with the child process. */ @@ -432,23 +401,24 @@ static int exec_nopty(const char *command, struct channel_data_struct *cdata) { goto stderr_failed; } - switch(cdata->pid = fork()) { - case -1: - goto fork_failed; - case 0: - /* Finish the plumbing in the child process. */ - close(in[1]); - close(out[0]); - close(err[0]); - dup2(in[0], STDIN_FILENO); - dup2(out[1], STDOUT_FILENO); - dup2(err[1], STDERR_FILENO); - close(in[0]); - close(out[1]); - close(err[1]); - /* exec the requested command. */ - execl("/bin/sh", "sh", "-c", command, NULL); - exit(0); + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); } close(in[0]); @@ -474,15 +444,18 @@ static int exec_nopty(const char *command, struct channel_data_struct *cdata) { return SSH_ERROR; } -static int exec_request(ssh_session session, ssh_channel channel, - const char *command, void *userdata) { - struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; - +static int +exec_request(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; - (void) session; - (void) channel; + (void)session; + (void)channel; - if(cdata->pid > 0) { + if (cdata->pid > 0) { return SSH_ERROR; } @@ -492,14 +465,15 @@ static int exec_request(ssh_session session, ssh_channel channel, return exec_nopty(command, cdata); } -static int shell_request(ssh_session session, ssh_channel channel, - void *userdata) { - struct channel_data_struct *cdata = (struct channel_data_struct *) userdata; +static int +shell_request(ssh_session session, ssh_channel channel, void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; - (void) session; - (void) channel; + (void)session; + (void)channel; - if(cdata->pid > 0) { + if (cdata->pid > 0) { return SSH_ERROR; } @@ -510,20 +484,28 @@ static int shell_request(ssh_session session, ssh_channel channel, return SSH_OK; } -static int subsystem_request(ssh_session session, ssh_channel channel, - const char *subsystem, void *userdata) { - /* subsystem requests behave simillarly to exec requests. */ +static int +subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + /* subsystem requests behave similarly to exec requests. */ if (strcmp(subsystem, "sftp") == 0) { return exec_request(session, channel, SFTP_SERVER_PATH, userdata); } return SSH_ERROR; } -static int auth_password(ssh_session session, const char *user, - const char *pass, void *userdata) { - struct session_data_struct *sdata = (struct session_data_struct *) userdata; +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; + (void)session; if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) { sdata->authenticated = 1; @@ -534,16 +516,26 @@ static int auth_password(ssh_session session, const char *user, return SSH_AUTH_DENIED; } -static int auth_publickey(ssh_session session, - const char *user, - struct ssh_key_struct *pubkey, - char signature_state, - void *userdata) +static int +auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) { - struct session_data_struct *sdata = (struct session_data_struct *) userdata; - - (void) user; - (void) session; + 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; if (signature_state == SSH_PUBLICKEY_STATE_NONE) { return SSH_AUTH_SUCCESS; @@ -553,45 +545,159 @@ 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 (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + 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); + + i++; + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + q = &p[i]; + 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; } -static ssh_channel channel_open(ssh_session session, void *userdata) { - struct session_data_struct *sdata = (struct session_data_struct *) userdata; +static int kbdint_check_response(ssh_session session) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp("omnitrix", answer); + if (cmp != 0) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp("000", answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Most powerful weapon in the galaxy"; + const char *prompts[2] = {"Name of the weapon: ", "Destruct Code: "}; + char echo[] = {1, 0}; + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; + } +} + +static ssh_channel +channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; sdata->channel = ssh_channel_new(session); return sdata->channel; } -static int process_stdout(socket_t fd, int revents, void *userdata) { +static int +process_stdout(socket_t fd, int revents, void *userdata) +{ char buf[BUF_SIZE]; int n = -1; - ssh_channel channel = (ssh_channel) userdata; + ssh_channel channel = (ssh_channel)userdata; if (channel != NULL && (revents & POLLIN) != 0) { n = read(fd, buf, BUF_SIZE); @@ -603,10 +709,12 @@ static int process_stdout(socket_t fd, int revents, void *userdata) { return n; } -static int process_stderr(socket_t fd, int revents, void *userdata) { +static int +process_stderr(socket_t fd, int revents, void *userdata) +{ char buf[BUF_SIZE]; int n = -1; - ssh_channel channel = (ssh_channel) userdata; + ssh_channel channel = (ssh_channel)userdata; if (channel != NULL && (revents & POLLIN) != 0) { n = read(fd, buf, BUF_SIZE); @@ -618,7 +726,9 @@ static int process_stderr(socket_t fd, int revents, void *userdata) { return n; } -static void handle_session(ssh_event event, ssh_session session) { +static void +handle_session(ssh_event event, ssh_session session) +{ int n; int rc = 0; @@ -662,14 +772,15 @@ static void handle_session(ssh_event event, ssh_session session) { struct ssh_server_callbacks_struct server_cb = { .userdata = &sdata, .auth_password_function = auth_password, + .auth_kbdint_function = auth_kbdint, .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); + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE); } else - ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE); ssh_callbacks_init(&server_cb); ssh_callbacks_init(&channel_cb); @@ -731,8 +842,8 @@ static void handle_session(ssh_event event, ssh_session session) { ssh_channel_close(sdata.channel); } } - } while(ssh_channel_is_open(sdata.channel) && - (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + } while (ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); close(cdata.pty_master); close(cdata.child_stdin); @@ -765,12 +876,14 @@ static void handle_session(ssh_event event, ssh_session session) { #ifdef WITH_FORK /* SIGCHLD handler for cleaning up dead children. */ -static void sigchld_handler(int signo) { - (void) signo; +static void sigchld_handler(int signo) +{ + (void)signo; while (waitpid(-1, NULL, WNOHANG) > 0); } #else -static void *session_thread(void *arg) { +static void *session_thread(void *arg) +{ ssh_session session = arg; ssh_event event; @@ -789,9 +902,10 @@ static void *session_thread(void *arg) { } #endif -int main(int argc, char **argv) { - ssh_bind sshbind; - ssh_session session; +int main(int argc, char **argv) +{ + ssh_bind sshbind = NULL; + ssh_session session = NULL; int rc; #ifdef WITH_FORK struct sigaction sa; @@ -829,7 +943,8 @@ int main(int argc, char **argv) { } #endif /* HAVE_ARGP_H */ - if(ssh_bind_listen(sshbind) < 0) { + rc = ssh_bind_listen(sshbind); + if (rc < 0) { fprintf(stderr, "%s\n", ssh_get_error(sshbind)); ssh_bind_free(sshbind); ssh_finalize(); @@ -844,34 +959,36 @@ int main(int argc, char **argv) { } /* Blocks until there is a new incoming connection. */ - if(ssh_bind_accept(sshbind, session) != SSH_ERROR) { + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { #ifdef WITH_FORK ssh_event event; - 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"); + pid_t pid = fork(); + switch (pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + 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 pthread_t tid; diff --git a/examples/sshd_direct-tcpip.c b/examples/sshd_direct-tcpip.c index 84389b28..9bb09111 100644 --- a/examples/sshd_direct-tcpip.c +++ b/examples/sshd_direct-tcpip.c @@ -361,7 +361,7 @@ my_fd_data_function(UNUSED_PARAM(socket_t fd), { struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; ssh_channel channel = event_fd_data->channel; - ssh_session session; + ssh_session session = NULL; int len, i, wr; char buf[BUF_SIZE]; int blocking; @@ -455,8 +455,8 @@ open_tcp_socket(ssh_message msg) { struct sockaddr_in sin; int forwardsock = -1; - struct hostent *host; - const char *dest_hostname; + struct hostent *host = NULL; + const char *dest_hostname = NULL; int dest_port; forwardsock = socket(AF_INET, SOCK_STREAM, 0); @@ -499,8 +499,8 @@ message_callback(UNUSED_PARAM(ssh_session session), UNUSED_PARAM(void *userdata)) { ssh_channel channel; - int socket_fd, *pFd; - struct ssh_channel_callbacks_struct *cb_chan; + int socket_fd, *pFd = NULL; + struct ssh_channel_callbacks_struct *cb_chan = NULL; struct event_fd_data_struct *event_fd_data; _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message type: %d", @@ -526,7 +526,7 @@ message_callback(UNUSED_PARAM(ssh_session session), } pFd = malloc(sizeof *pFd); - cb_chan = malloc(sizeof *cb_chan); + cb_chan = calloc(1, sizeof *cb_chan); event_fd_data = malloc(sizeof *event_fd_data); if (pFd == NULL || cb_chan == NULL || event_fd_data == NULL) { SAFE_FREE(pFd); @@ -655,8 +655,8 @@ static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; int main(int argc, char **argv) { - ssh_session session; - ssh_bind sshbind; + ssh_session session = NULL; + ssh_bind sshbind = NULL; struct ssh_server_callbacks_struct cb = { .userdata = NULL, .auth_password_function = auth_password, diff --git a/examples/sshnetcat.c b/examples/sshnetcat.c index 59b0a289..3dba51e8 100644 --- a/examples/sshnetcat.c +++ b/examples/sshnetcat.c @@ -39,223 +39,237 @@ clients must be made or how a client should react. #define BUF_SIZE 4096 #endif -char *host; -const char *desthost="localhost"; -const char *port="22"; +char *host = NULL; +const char *desthost = "localhost"; +const char *port = "22"; #ifdef WITH_PCAP #include -char *pcap_file=NULL; +char *pcap_file = NULL; #endif static void usage(void) { - fprintf(stderr,"Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); - exit(1); + fprintf(stderr, + "Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); + exit(1); } -static int opts(int argc, char **argv){ +static int opts(int argc, char **argv) +{ int i; - while((i=getopt(argc,argv,"P:"))!=-1){ - switch(i){ + while ((i = getopt(argc, argv, "P:")) != -1) { + switch (i) { #ifdef WITH_PCAP - case 'P': - pcap_file=optarg; - break; + case 'P': + pcap_file = optarg; + break; #endif - default: - fprintf(stderr,"unknown option %c\n",optopt); - usage(); + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(); } } - if(optind < argc) - host=argv[optind++]; - if(optind < argc) - desthost=argv[optind++]; - if(optind < argc) - port=argv[optind++]; - if(host==NULL) + if (optind < argc) + host = argv[optind++]; + if (optind < argc) + desthost = argv[optind++]; + if (optind < argc) + port = argv[optind++]; + if (host == NULL) usage(); return 0; } -static void select_loop(ssh_session session,ssh_channel channel){ - fd_set fds; - struct timeval timeout; - char buffer[BUF_SIZE]; - /* channels will be set to the channels to poll. - * outchannels will contain the result of the poll - */ - ssh_channel channels[2], outchannels[2]; - int lus; - int eof=0; - int maxfd; - int ret; - while(channel){ - do{ +static void select_loop(ssh_session session, ssh_channel channel) +{ + fd_set fds; + struct timeval timeout; + char buffer[BUF_SIZE]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof = 0; + int maxfd; + int ret; + while (channel) { + do { int fd; ZERO_STRUCT(fds); - FD_ZERO(&fds); - if(!eof) - FD_SET(0,&fds); - timeout.tv_sec=30; - timeout.tv_usec=0; + FD_ZERO(&fds); + if (!eof) + FD_SET(0, &fds); + timeout.tv_sec = 30; + timeout.tv_usec = 0; fd = ssh_get_fd(session); if (fd == -1) { - fprintf(stderr, "Error getting the session file descriptor: %s\n", - ssh_get_error(session)); + fprintf(stderr, + "Error getting the session file descriptor: %s\n", + ssh_get_error(session)); return; } FD_SET(fd, &fds); maxfd = fd + 1; - channels[0]=channel; // set the first channel we want to read from - channels[1]=NULL; - ret=ssh_select(channels,outchannels,maxfd,&fds,&timeout); - if(ret==EINTR) - continue; - if(FD_ISSET(0,&fds)){ - lus=read(0,buffer,sizeof(buffer)); - if(lus) - ssh_channel_write(channel,buffer,lus); - else { - eof=1; - ssh_channel_send_eof(channel); - } - } - if(channel && ssh_channel_is_closed(channel)){ - ssh_channel_free(channel); - channel=NULL; - channels[0]=NULL; - } - if(outchannels[0]){ - while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,0)){ - lus = ssh_channel_read(channel,buffer,sizeof(buffer),0); - if(lus==-1){ - fprintf(stderr, "Error reading channel: %s\n", - ssh_get_error(session)); - return; - } - if(lus==0){ - ssh_channel_free(channel); - channel=channels[0]=NULL; - } else { - ret = write(1, buffer, lus); - if (ret < 0) { - fprintf(stderr, "Error writing to stdin: %s", - strerror(errno)); - return; - } - } - } - while(channel && ssh_channel_is_open(channel) && ssh_channel_poll(channel,1)){ /* stderr */ - lus = ssh_channel_read(channel, buffer, sizeof(buffer), 1); - if(lus==-1){ - fprintf(stderr, "Error reading channel: %s\n", - ssh_get_error(session)); - return; - } - if(lus==0){ - ssh_channel_free(channel); - channel=channels[0]=NULL; - } else { - ret = write(2, buffer, lus); - if (ret < 0) { - fprintf(stderr, "Error writing to stderr: %s", - strerror(errno)); - return; - } + channels[0] = channel; // set the first channel we want to read from + channels[1] = NULL; + ret = ssh_select(channels, outchannels, maxfd, &fds, &timeout); + if (ret == EINTR) + continue; + if (FD_ISSET(0, &fds)) { + lus = read(0, buffer, sizeof(buffer)); + if (lus) + ssh_channel_write(channel, buffer, lus); + else { + eof = 1; + ssh_channel_send_eof(channel); + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + channels[0] = NULL; + } + if (outchannels[0]) { + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 0)) { + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; } - } - } - if(channel && ssh_channel_is_closed(channel)){ - ssh_channel_free(channel); - channel=NULL; - } - } while (ret==EINTR || ret==SSH_EINTR); - - } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(1, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stdin: %s", + strerror(errno)); + return; + } + } + } + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 1)) { /* stderr */ + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(2, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stderr: %s", + strerror(errno)); + return; + } + } + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + } + } while (ret == EINTR || ret == SSH_EINTR); + } } -static void forwarding(ssh_session session){ +static void forwarding(ssh_session session) +{ ssh_channel channel; int r; channel = ssh_channel_new(session); r = ssh_channel_open_forward(channel, desthost, atoi(port), "localhost", 22); - if(r<0) { - printf("error forwarding port : %s\n",ssh_get_error(session)); + if (r < 0) { + printf("error forwarding port : %s\n", ssh_get_error(session)); return; } - select_loop(session,channel); + select_loop(session, channel); } -static int client(ssh_session session){ - int auth=0; - char *banner; - int state; +static int client(ssh_session session) +{ + int auth = 0; + char *banner = NULL; + int state; - if (ssh_options_set(session, SSH_OPTIONS_HOST ,host) < 0) - return -1; - ssh_options_parse_config(session, NULL); + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) + return -1; + ssh_options_parse_config(session, NULL); - if(ssh_connect(session)){ - fprintf(stderr,"Connection failed : %s\n",ssh_get_error(session)); - return -1; - } - state=verify_knownhost(session); - if (state != 0) - return -1; - ssh_userauth_none(session, NULL); - banner=ssh_get_issue_banner(session); - if(banner){ - printf("%s\n",banner); - free(banner); - } - auth=authenticate_console(session); - if(auth != SSH_AUTH_SUCCESS){ - return -1; - } - forwarding(session); - return 0; + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + state = verify_knownhost(session); + if (state != 0) + return -1; + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + forwarding(session); + return 0; } #ifdef WITH_PCAP ssh_pcap_file pcap; void set_pcap(ssh_session session); -void set_pcap(ssh_session session){ - if(!pcap_file) - return; - pcap=ssh_pcap_file_new(); - if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ - printf("Error opening pcap file\n"); - ssh_pcap_file_free(pcap); - pcap=NULL; - return; - } - ssh_set_pcap_file(session,pcap); +void set_pcap(ssh_session session) +{ + if (!pcap_file) + return; + pcap = ssh_pcap_file_new(); + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); } void cleanup_pcap(void); void cleanup_pcap(void) { - ssh_pcap_file_free(pcap); - pcap = NULL; + ssh_pcap_file_free(pcap); + pcap = NULL; } #endif -int main(int argc, char **argv){ - ssh_session session; +int main(int argc, char **argv) +{ + ssh_session session = NULL; session = ssh_new(); - if(ssh_options_getopt(session, &argc, argv)) { - fprintf(stderr, "error parsing command line :%s\n", - ssh_get_error(session)); - usage(); + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "error parsing command line :%s\n", + ssh_get_error(session)); + usage(); } - opts(argc,argv); + opts(argc, argv); #ifdef WITH_PCAP set_pcap(session); #endif diff --git a/include/libssh/CMakeLists.txt b/include/libssh/CMakeLists.txt index 93445680..109e6837 100644 --- a/include/libssh/CMakeLists.txt +++ b/include/libssh/CMakeLists.txt @@ -29,6 +29,13 @@ if (WITH_SERVER) endif (WITH_SFTP) endif (WITH_SERVER) +if (WITH_FIDO2) + set(libssh_HDRS + ${libssh_HDRS} + sk_api.h + ) +endif (WITH_FIDO2) + install( FILES ${libssh_HDRS} diff --git a/include/libssh/auth.h b/include/libssh/auth.h index b358b7a2..309930d5 100644 --- a/include/libssh/auth.h +++ b/include/libssh/auth.h @@ -52,42 +52,45 @@ typedef struct ssh_kbdint_struct* ssh_kbdint; ssh_kbdint ssh_kbdint_new(void); void ssh_kbdint_clean(ssh_kbdint kbd); void ssh_kbdint_free(ssh_kbdint kbd); +int ssh_userauth_gssapi_keyex(ssh_session session); /** @internal * States of authentication in the client-side. They describe * what was the last response from the server */ enum ssh_auth_state_e { - /** No authentication asked */ - SSH_AUTH_STATE_NONE=0, - /** Last authentication response was a partial success */ - SSH_AUTH_STATE_PARTIAL, - /** Last authentication response was a success */ - SSH_AUTH_STATE_SUCCESS, - /** Last authentication response was failed */ - SSH_AUTH_STATE_FAILED, - /** Last authentication was erroneous */ - SSH_AUTH_STATE_ERROR, - /** Last state was a keyboard-interactive ask for info */ - SSH_AUTH_STATE_INFO, - /** Last state was a public key accepted for authentication */ - SSH_AUTH_STATE_PK_OK, - /** We asked for a keyboard-interactive authentication */ - SSH_AUTH_STATE_KBDINT_SENT, - /** We have sent an userauth request with gssapi-with-mic */ - SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, - /** We are exchanging tokens until authentication */ - SSH_AUTH_STATE_GSSAPI_TOKEN, - /** We have sent the MIC and expecting to be authenticated */ - SSH_AUTH_STATE_GSSAPI_MIC_SENT, - /** We have offered a pubkey to check if it is supported */ - SSH_AUTH_STATE_PUBKEY_OFFER_SENT, - /** We have sent pubkey and signature expecting to be authenticated */ - SSH_AUTH_STATE_PUBKEY_AUTH_SENT, - /** We have sent a password expecting to be authenticated */ - SSH_AUTH_STATE_PASSWORD_AUTH_SENT, - /** We have sent a request without auth information (method 'none') */ - SSH_AUTH_STATE_AUTH_NONE_SENT, + /** No authentication asked */ + SSH_AUTH_STATE_NONE = 0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT, + /** We have sent an userauth request with gssapi-with-mic */ + SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, + /** We are exchanging tokens until authentication */ + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, + /** We have offered a pubkey to check if it is supported */ + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + /** We have sent a request without auth information (method 'none') */ + SSH_AUTH_STATE_AUTH_NONE_SENT, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT, }; /** @internal diff --git a/include/libssh/bignum.h b/include/libssh/bignum.h index 6b5dc1a2..37e87bc0 100644 --- a/include/libssh/bignum.h +++ b/include/libssh/bignum.h @@ -31,6 +31,7 @@ extern "C" { bignum ssh_make_string_bn(ssh_string string); ssh_string ssh_make_bignum_string(bignum num); +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len); void ssh_print_bignum(const char *which, const_bignum num); #ifdef __cplusplus diff --git a/include/libssh/bind.h b/include/libssh/bind.h index cd9199b6..a848003e 100644 --- a/include/libssh/bind.h +++ b/include/libssh/bind.h @@ -54,6 +54,8 @@ struct ssh_bind_struct { char *pubkey_accepted_key_types; char* moduli_file; int rsa_min_size; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; }; struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct diff --git a/include/libssh/bind_config.h b/include/libssh/bind_config.h index 5f2dccce..54346d8e 100644 --- a/include/libssh/bind_config.h +++ b/include/libssh/bind_config.h @@ -52,6 +52,7 @@ enum ssh_bind_config_opcode_e { BIND_CFG_MATCH, BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, BIND_CFG_HOSTKEY_ALGORITHMS, + BIND_CFG_REQUIRED_RSA_SIZE, BIND_CFG_MAX /* Keep this one last in the list */ }; diff --git a/include/libssh/blf.h b/include/libssh/blf.h index 201821a2..71928a7d 100644 --- a/include/libssh/blf.h +++ b/include/libssh/blf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ /* * Blowfish - a fast block cipher designed by Bruce Schneier * @@ -13,10 +13,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products + * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR diff --git a/include/libssh/buffer.h b/include/libssh/buffer.h index 1fce7b76..108225f1 100644 --- a/include/libssh/buffer.h +++ b/include/libssh/buffer.h @@ -42,10 +42,6 @@ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len); int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, uint32_t len); -int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, - const char *format, - size_t argc, - va_list ap); int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, const char *format, size_t argc, @@ -78,6 +74,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); +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer); + #ifdef __cplusplus } #endif diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 34016595..e4ecf726 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -113,6 +113,17 @@ typedef void (*ssh_status_callback) (ssh_session session, float status, typedef void (*ssh_global_request_callback) (ssh_session session, ssh_message message, void *userdata); +/** + * @brief SSH connect status callback. These are functions that report the + * status of the connection i,e. a function indicating the completed percentage + * of the connection + * steps. + * @param userdata Userdata to be passed to the callback function. + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + */ +typedef void (*ssh_connect_status_callback)(void *userdata, float status); + /** * @brief Handles an SSH new channel open X11 request. This happens when the server * sends back an X11 connection attempt. This is a client-side API @@ -181,7 +192,7 @@ struct ssh_callbacks_struct { * This function gets called during connection time to indicate the * percentage of connection steps completed. */ - void (*connect_status_function)(void *userdata, float status); + ssh_connect_status_callback connect_status_function; /** * This function will be called each time a global request is received. */ @@ -209,36 +220,41 @@ typedef struct ssh_callbacks_struct *ssh_callbacks; * @param user User that wants to authenticate * @param password Password used for authentication * @param userdata Userdata to be passed to the callback function. - * @returns SSH_AUTH_SUCCESS Authentication is accepted. - * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. - * @returns SSH_AUTH_DENIED Authentication failed. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. */ typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, void *userdata); /** - * @brief SSH authentication callback. Tries to authenticates user with the "none" method - * which is anonymous or passwordless. + * @brief SSH authentication callback. Tries to authenticates user with the + * "none" method which is anonymous or passwordless. * @param session Current session handler * @param user User that wants to authenticate * @param userdata Userdata to be passed to the callback function. - * @returns SSH_AUTH_SUCCESS Authentication is accepted. - * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. - * @returns SSH_AUTH_DENIED Authentication failed. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. */ typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); /** - * @brief SSH authentication callback. Tries to authenticates user with the "gssapi-with-mic" method + * @brief SSH authentication callback. Tries to authenticates user with the + * "gssapi-with-mic" method * @param session Current session handler * @param user Username of the user (can be spoofed) * @param principal Authenticated principal of the user, including realm. * @param userdata Userdata to be passed to the callback function. - * @returns SSH_AUTH_SUCCESS Authentication is accepted. - * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. - * @returns SSH_AUTH_DENIED Authentication failed. - * @warning Implementations should verify that parameter user matches in some way the principal. - * user and principal can be different. Only the latter is guaranteed to be safe. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + * @warning Implementations should verify that parameter user matches in some + * way the principal. user and principal can be different. Only the latter is + * guaranteed to be safe. */ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, void *userdata); @@ -248,17 +264,29 @@ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *us * @param session Current session handler * @param user User that wants to authenticate * @param pubkey public key used for authentication - * @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe), - * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be - * replied with a SSH_AUTH_DENIED. + * @param signature_state `SSH_PUBLICKEY_STATE_NONE` if the key is not signed + * (simple public key probe), `SSH_PUBLICKEY_STATE_VALID` if the signature is + * valid. Others values should be replied with a `SSH_AUTH_DENIED`. * @param userdata Userdata to be passed to the callback function. - * @returns SSH_AUTH_SUCCESS Authentication is accepted. - * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. - * @returns SSH_AUTH_DENIED Authentication failed. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. */ typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, char signature_state, void *userdata); +/** + * @brief SSH authentication callback. Tries to authenticates user with the "keyboard-interactive" method + * @param message Current message + * @param session Current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_INFO More info required for authentication. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. +*/ +typedef int (*ssh_auth_kbdint_callback) (ssh_message message, ssh_session session, void *userdata); /** * @brief Handles an SSH service request @@ -268,7 +296,6 @@ typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, * @returns 0 if the request is to be allowed * @returns -1 if the request should not be allowed */ - typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); /** @@ -281,8 +308,9 @@ typedef int (*ssh_service_request_callback) (ssh_session session, const char *se */ typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); -/* +/** * @brief handle the beginning of a GSSAPI authentication, server side. + * Callback should select the oid and also acquire the server credential. * @param session current session handler * @param user the username of the client * @param n_oid number of available oids @@ -295,35 +323,57 @@ typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session se typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, int n_oid, ssh_string *oids, void *userdata); -/* +/** * @brief handle the 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. - * @returns SSH_OK if the token was generated correctly or accept_sec_context + * @returns `SSH_OK` if the token was generated correctly or accept_sec_context * returned GSS_S_COMPLETE - * @returns SSH_ERROR in case of error + * @returns `SSH_ERROR` in case of error * @warning It is not necessary to fill this callback in if libssh is linked * with libgssapi. */ typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, ssh_string input_token, ssh_string *output_token, void *userdata); -/* +/** * @brief Verify and authenticates a MIC, server side. * @param session current session handler * @param[in] mic input mic to be verified provided by client * @param[in] mic_buffer buffer of data to be signed. * @param[in] mic_buffer_size size of mic_buffer - * @returns SSH_OK if the MIC was authenticated correctly - * @returns SSH_ERROR in case of error + * @returns `SSH_OK` if the MIC was authenticated correctly + * @returns `SSH_ERROR` in case of error * @warning It is not necessary to fill this callback in if libssh is linked * with libgssapi. */ typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); +/** + * @brief Handles an SSH new channel open "direct-tcpip" request. This + * happens when the client forwards an incoming TCP connection on a port it + * wants to forward to the destination. This is a server-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_direct_tcpip_callback)( + ssh_session session, + const char *destination_address, + int destination_port, + const char *originator_address, + int originator_port, + void *userdata); /** * This structure can be used to implement a libssh server, with appropriate callbacks. @@ -365,14 +415,27 @@ struct ssh_server_callbacks_struct { */ ssh_channel_open_request_session_callback channel_open_request_session_function; /** This function will be called when a new gssapi authentication is attempted. + * This should select the oid and acquire credential for the server. */ ssh_gssapi_select_oid_callback gssapi_select_oid_function; /** This function will be called when a gssapi token comes in. */ ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; - /* This function will be called when a MIC needs to be verified. + /** This function will be called when a MIC needs to be verified. */ ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; + /** + * This function will be called when an incoming "direct-tcpip" + * request is received. + */ + ssh_channel_open_request_direct_tcpip_callback + channel_open_request_direct_tcpip_function; + + /** This function gets called when a client tries to authenticate through + * keyboard interactive method. + */ + ssh_auth_kbdint_callback auth_kbdint_function; + }; typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; @@ -398,7 +461,7 @@ typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; * * @param cb The callback structure itself. * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. */ LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); @@ -529,14 +592,17 @@ typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; } \ } while(0) -/** @brief Prototype for a packet callback, to be called when a new packet arrives +/** @brief Prototype for a packet callback, to be called when a new packet + * arrives * @param session The current session of the packet * @param type packet type (see ssh2.h) - * @param packet buffer containing the packet, excluding size, type and padding fields + * @param packet buffer containing the packet, excluding size, type and padding + * fields * @param user user argument to the callback * and are called each time a packet shows up - * @returns SSH_PACKET_USED Packet was parsed and used - * @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue + * @returns `SSH_PACKET_USED` Packet was parsed and used + * @returns `SSH_PACKET_NOT_USED` Packet was not used or understood, processing + * must continue */ typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); @@ -595,7 +661,7 @@ typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; * * @param cb The callback structure itself. * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. */ LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); @@ -947,7 +1013,7 @@ typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; * * @param cb The callback structure itself. * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * @warning this function will not replace existing callbacks but set the * new one atop of them. */ @@ -966,7 +1032,7 @@ LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, * * @param cb The callback structure itself. * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_set_channel_callbacks */ @@ -983,7 +1049,7 @@ LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, * * @param cb The callback structure to remove * - * @returns SSH_OK on success, SSH_ERROR on error. + * @returns `SSH_OK` on success, `SSH_ERROR` on error. */ LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb); @@ -1016,7 +1082,7 @@ struct ssh_threads_callbacks_struct { * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which * contains the different callbacks to be set. * - * @returns Always returns SSH_OK. + * @returns Always returns `SSH_OK`. * * @see ssh_threads_callbacks_struct * @see SSH_THREADS_PTHREAD @@ -1056,6 +1122,7 @@ 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. @@ -1073,7 +1140,216 @@ LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); */ LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); -/** @} */ +/** + * @brief SSH proxyjump before connection callback. Called before calling + * ssh_connect() + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_before_connection_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump verify knownhost callback. Verify the host. + * If not specified default function will be used. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_verify_knownhost_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump user authentication callback. Authenticate the user. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_authenticate_callback)(ssh_session session, + void *userdata); + +struct ssh_jump_callbacks_struct { + void *userdata; + ssh_jump_before_connection_callback before_connection; + ssh_jump_verify_knownhost_callback verify_knownhost; + ssh_jump_authenticate_callback authenticate; +}; + +/* Security key callbacks */ + +/* + * Forward declarations for structs that have been defined in sk_api.h. + * If you need to work with the fields inside them, please include + * libssh/sk_api.h + */ +struct sk_enroll_response; +struct sk_sign_response; +struct sk_resident_key; +struct sk_option; + +#define LIBSSH_SK_API_VERSION_MAJOR 0x000a0000 + +/** + * @brief FIDO2/U2F SK API version callback. + * + * Returns the version of the FIDO2/U2F API that the callbacks implement. + * This callback allows custom callback implementations to specify their + * SK API version for compatibility checking with libssh's security key + * interface. + * + * @details Version compatibility is determined by comparing the major version + * portion (upper 16 bits) of the returned value with SSH_SK_VERSION_MAJOR. + * + * For compatibility, implementations should return a version where: + * (returned_version & SSH_SK_VERSION_MAJOR_MASK) == SSH_SK_VERSION_MAJOR + * + * This ensures that the callbacks' SK API matches the major version expected + * by libssh, while allowing minor version differences for backward + * compatibility. + * + * @see LIBSSH_SK_API_VERSION_MAJOR Current expected major API version + * @see SSH_SK_VERSION_MAJOR_MASK Mask for extracting major version (0xffff0000) + */ +typedef uint32_t (*sk_api_version_callback)(void); + +/** + * @brief FIDO2/U2F key enrollment callback. + * + * Enrolls a new FIDO2/U2F security key credential (private key generation). + * This callback handles the creation of new FIDO2/U2F credentials, including + * both resident and non-resident keys. + * + * @param[in] alg The cryptographic algorithm to use + * @param[in] challenge Random challenge data for enrollment + * @param[in] challenge_len Length of the challenge data + * @param[in] application Application identifier (relying party ID) + * @param[in] flags Enrollment flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of enrollment options (device path, user ID, etc.) + * @param[out] enroll_response Enrollment response containing public key, + * key handle, signature, and attestation data + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_enroll_callback)(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +/** + * @brief FIDO2/U2F security key signing callback. + * + * Signs data using a FIDO2 security key credential. This callback performs + * cryptographic signing operations using previously enrolled FIDO2/U2F + * credentials. + * + * @param[in] alg The cryptographic algorithm used by the key + * @param[in] data Data to be signed + * @param[in] data_len Length of the data to sign + * @param[in] application Application identifier (relying party ID) + * @param[in] key_handle Key handle identifying the credential + * @param[in] key_handle_len Length of the key handle + * @param[in] flags Signing flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of signing options (device path, etc.) + * @param[out] sign_response Signature response containing signature data, + * flags, and counter information + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_sign_callback)(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +/** + * @brief FIDO2 security key resident keys loading callback. + * + * Enumerates and loads all resident keys (discoverable credentials) stored + * on FIDO2 devices. Resident keys are credentials stored directly on + * the device itself and can be discovered without prior knowledge + * of key handles. + * + * @param[in] pin PIN for accessing resident keys (required for most operations) + * @param[in] options Array of options (device path, etc.) + * @param[out] resident_keys Array of resident key structures containing key + * data, application IDs, user information, and metadata + * @param[out] num_keys_found Number of resident keys found and loaded + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_load_resident_keys_callback)( + const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +/** + * @brief FIDO2/U2F security key callbacks structure. + * + * This structure contains callbacks for FIDO2/U2F operations. + * It allows applications to provide custom implementations of FIDO2/U2F + * operations to override the default libfido2-based implementation. + * + * @warning These callbacks will only be called if libssh was built with + * FIDO2/U2F support enabled. (WITH_FIDO2 = ON). + */ +struct ssh_sk_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + + /** + * This callback returns the SK API version used by the callback + * implementation. + * + * @see sk_api_version_callback for detailed documentation + */ + sk_api_version_callback api_version; + + /** + * This callback enrolls a new FIDO2/U2F credential, generating + * a new key pair and optionally storing it on the device itself + * (resident keys). + * + * @see sk_enroll_callback for detailed documentation + */ + sk_enroll_callback enroll; + + /** + * This callback performs cryptographic signing operations using a + * previously enrolled FIDO2/U2F credential. + * + * @see sk_sign_callback for detailed documentation + */ + sk_sign_callback sign; + + /** + * This callback enumerates and loads all resident keys (discoverable + * credentials) stored on the FIDO2 device. + * + * @see sk_load_resident_keys_callback for detailed documentation + */ + sk_load_resident_keys_callback load_resident_keys; +}; + +typedef struct ssh_sk_callbacks_struct *ssh_sk_callbacks; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void); + #ifdef __cplusplus } #endif diff --git a/include/libssh/channels.h b/include/libssh/channels.h index cb2bea43..7a3535ec 100644 --- a/include/libssh/channels.h +++ b/include/libssh/channels.h @@ -80,7 +80,12 @@ struct ssh_channel_struct { ssh_buffer stdout_buffer; ssh_buffer stderr_buffer; void *userarg; - int exit_status; + struct { + bool status; + uint32_t code; + char *signal; + bool core_dumped; + } exit; enum ssh_channel_request_state_e request_state; struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ diff --git a/include/libssh/config.h b/include/libssh/config.h index 21702391..87cc25be 100644 --- a/include/libssh/config.h +++ b/include/libssh/config.h @@ -24,6 +24,7 @@ #ifndef LIBSSH_CONFIG_H_ #define LIBSSH_CONFIG_H_ +#include "libssh/libssh.h" enum ssh_config_opcode_e { /* Unknown opcode */ @@ -65,7 +66,15 @@ enum ssh_config_opcode_e { SOC_IDENTITIESONLY, SOC_CONTROLMASTER, SOC_CONTROLPATH, + SOC_CERTIFICATE, + SOC_REQUIRED_RSA_SIZE, + SOC_ADDRESSFAMILY, + SOC_GSSAPIKEYEXCHANGE, + SOC_GSSAPIKEXALGORITHMS, SOC_MAX /* Keep this one last in the list */ }; +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword); +int ssh_config_parse_line_cli(ssh_session session, const char *line); + #endif /* LIBSSH_CONFIG_H_ */ diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h index a7dd42a2..f5d1fee8 100644 --- a/include/libssh/config_parser.h +++ b/include/libssh/config_parser.h @@ -30,6 +30,9 @@ extern "C" { #endif +#include "libssh/libssh.h" +#include + char *ssh_config_get_cmd(char **str); char *ssh_config_get_token(char **str); @@ -49,14 +52,32 @@ 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); + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure + */ +int ssh_config_parse_proxy_jump(ssh_session session, + const char *s, + bool do_parsing); #ifdef __cplusplus } diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 32016827..dd7fa2e8 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -45,10 +45,11 @@ #ifdef HAVE_OPENSSL_ECDH_H #include #endif +#include "libssh/curve25519.h" #include "libssh/dh.h" #include "libssh/ecdh.h" #include "libssh/kex.h" -#include "libssh/curve25519.h" +#include "libssh/sntrup761.h" #define DIGEST_MAX_LEN 64 @@ -56,39 +57,59 @@ #define AES_GCM_IVLEN 12 enum ssh_key_exchange_e { - /* diffie-hellman-group1-sha1 */ - SSH_KEX_DH_GROUP1_SHA1=1, - /* diffie-hellman-group14-sha1 */ - SSH_KEX_DH_GROUP14_SHA1, + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1 = 1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, #ifdef WITH_GEX - /* diffie-hellman-group-exchange-sha1 */ - SSH_KEX_DH_GEX_SHA1, - /* diffie-hellman-group-exchange-sha256 */ - SSH_KEX_DH_GEX_SHA256, + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GEX_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GEX_SHA256, #endif /* WITH_GEX */ - /* ecdh-sha2-nistp256 */ - SSH_KEX_ECDH_SHA2_NISTP256, - /* ecdh-sha2-nistp384 */ - SSH_KEX_ECDH_SHA2_NISTP384, - /* ecdh-sha2-nistp521 */ - SSH_KEX_ECDH_SHA2_NISTP521, - /* curve25519-sha256@libssh.org */ - SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, - /* curve25519-sha256 */ - SSH_KEX_CURVE25519_SHA256, - /* diffie-hellman-group16-sha512 */ - SSH_KEX_DH_GROUP16_SHA512, - /* diffie-hellman-group18-sha512 */ - SSH_KEX_DH_GROUP18_SHA512, - /* diffie-hellman-group14-sha256 */ - SSH_KEX_DH_GROUP14_SHA256, + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256, + /* ecdh-sha2-nistp384 */ + SSH_KEX_ECDH_SHA2_NISTP384, + /* ecdh-sha2-nistp521 */ + SSH_KEX_ECDH_SHA2_NISTP521, + /* curve25519-sha256@libssh.org */ + SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, + /* curve25519-sha256 */ + SSH_KEX_CURVE25519_SHA256, + /* diffie-hellman-group16-sha512 */ + SSH_KEX_DH_GROUP16_SHA512, + /* diffie-hellman-group18-sha512 */ + SSH_KEX_DH_GROUP18_SHA512, + /* diffie-hellman-group14-sha256 */ + SSH_KEX_DH_GROUP14_SHA256, + /* sntrup761x25519-sha512@openssh.com */ + SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM, + /* sntrup761x25519-sha512 */ + SSH_KEX_SNTRUP761X25519_SHA512, + /* mlkem768x25519-sha256 */ + SSH_KEX_MLKEM768X25519_SHA256, + /* mlkem768nistp256-sha256 */ + SSH_KEX_MLKEM768NISTP256_SHA256, +#ifdef HAVE_MLKEM1024 + /* mlkem1024nistp384-sha384 */ + SSH_KEX_MLKEM1024NISTP384_SHA384, +#endif /* HAVE_MLKEM1024 */ + /* gss-group14-sha256-* */ + SSH_GSS_KEX_DH_GROUP14_SHA256, + /* gss-group16-sha512-* */ + SSH_GSS_KEX_DH_GROUP16_SHA512, + /* gss-nistp256-sha256-* */ + SSH_GSS_KEX_ECDH_NISTP256_SHA256, + /* gss-curve25519-sha256-* */ + SSH_GSS_KEX_CURVE25519_SHA256, }; enum ssh_cipher_e { SSH_NO_CIPHER=0, -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH SSH_BLOWFISH_CBC, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ SSH_3DES_CBC, SSH_AES128_CBC, SSH_AES192_CBC, @@ -105,6 +126,9 @@ struct dh_ctx; struct ssh_crypto_struct { bignum shared_secret; + ssh_string hybrid_client_init; + ssh_string hybrid_server_reply; + ssh_string hybrid_shared_secret; struct dh_ctx *dh_ctx; #ifdef WITH_GEX size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */ @@ -125,9 +149,28 @@ struct ssh_crypto_struct { ssh_string ecdh_server_pubkey; #endif #ifdef HAVE_CURVE25519 +#ifdef HAVE_LIBCRYPTO + EVP_PKEY *curve25519_privkey; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_t curve25519_privkey; +#else ssh_curve25519_privkey curve25519_privkey; +#endif ssh_curve25519_pubkey curve25519_client_pubkey; ssh_curve25519_pubkey curve25519_server_pubkey; +#endif +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY *mlkem_privkey; +#else + unsigned char *mlkem_privkey; + size_t mlkem_privkey_len; +#endif + ssh_string mlkem_client_pubkey; + ssh_string mlkem_ciphertext; +#ifdef HAVE_SNTRUP761 + ssh_sntrup761_privkey sntrup761_privkey; + ssh_sntrup761_pubkey sntrup761_client_pubkey; + ssh_sntrup761_ciphertext sntrup761_ciphertext; #endif ssh_string dh_server_signature; /* information used by dh_handshake. */ size_t session_id_len; @@ -223,9 +266,8 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, 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 */ + +void compress_cleanup(struct ssh_crypto_struct *crypto); #ifdef __cplusplus } diff --git a/include/libssh/curve25519.h b/include/libssh/curve25519.h index a55f52c7..e5691157 100644 --- a/include/libssh/curve25519.h +++ b/include/libssh/curve25519.h @@ -50,6 +50,10 @@ int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned c typedef unsigned char ssh_curve25519_pubkey[CURVE25519_PUBKEY_SIZE]; typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; +int ssh_curve25519_init(ssh_session session); +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_build_k(ssh_session session); int ssh_client_curve25519_init(ssh_session session); void ssh_client_curve25519_remove_callbacks(ssh_session session); diff --git a/include/libssh/ecdh.h b/include/libssh/ecdh.h index 4c4c54eb..2f763528 100644 --- a/include/libssh/ecdh.h +++ b/include/libssh/ecdh.h @@ -48,6 +48,7 @@ extern "C" { extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; /* Backend-specific functions. */ +int ssh_ecdh_init(ssh_session session); int ssh_client_ecdh_init(ssh_session session); void ssh_client_ecdh_remove_callbacks(ssh_session session); int ecdh_build_k(ssh_session session); diff --git a/include/libssh/ed25519.h b/include/libssh/ed25519.h index 72a86c0b..a6bcdaf3 100644 --- a/include/libssh/ed25519.h +++ b/include/libssh/ed25519.h @@ -29,12 +29,41 @@ * @{ */ +/** @internal + * @brief ED25519 public key. + * Ed25519 public key consist of 32 bytes. + */ #define ED25519_PK_LEN 32 + +/** @internal + * @brief ED25519 secret key. + * Ed25519 secret key consist of 64 bytes. + */ #define ED25519_SK_LEN 64 + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consist of 64 bytes. + */ #define ED25519_SIG_LEN 64 +/** @internal + * @brief ED25519 public key. + * The public key consists of 32 bytes and can be used for signature + * verification. + */ typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; + +/** @internal + * @brief ED25519 private key. + * The private key consists of 64 bytes and should be kept secret. + */ typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consists of 64 bytes. + */ typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; #ifdef __cplusplus @@ -46,7 +75,7 @@ extern "C" { * @param[out] pk generated public key * @param[out] sk generated secret key * @return 0 on success, -1 on error. - * */ + */ int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk); /** @internal diff --git a/include/libssh/gssapi.h b/include/libssh/gssapi.h index b0c74c73..fd1216f2 100644 --- a/include/libssh/gssapi.h +++ b/include/libssh/gssapi.h @@ -22,21 +22,53 @@ #define GSSAPI_H_ #include "config.h" +#ifdef WITH_GSSAPI #include "session.h" +#include /* all OID begin with the tag identifier + length */ #define SSH_OID_TAG 06 +#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-," \ + "gss-group16-sha512-," \ + "gss-nistp256-sha256-," \ + "gss-curve25519-sha256-" + typedef struct ssh_gssapi_struct *ssh_gssapi; #ifdef __cplusplus extern "C" { #endif +/** current state of an GSSAPI authentication */ +enum ssh_gssapi_state_e { + SSH_GSSAPI_STATE_NONE, /* no status */ + SSH_GSSAPI_STATE_RCV_TOKEN, /* Expecting a token */ + SSH_GSSAPI_STATE_RCV_MIC, /* Expecting a MIC */ +}; + +struct ssh_gssapi_struct{ + enum ssh_gssapi_state_e state; /* current state */ + gss_cred_id_t server_creds; /* credentials of server */ + gss_cred_id_t client_creds; /* creds delegated by the client */ + gss_ctx_id_t ctx; /* the authentication context */ + gss_name_t client_name; /* Identity of the client */ + char *user; /* username of client */ + char *canonic_user; /* canonic form of the client's username */ + struct { + gss_name_t server_name; /* identity of server */ + OM_uint32 flags; /* flags used for init context */ + gss_OID oid; /* mech being used for authentication */ + gss_cred_id_t creds; /* creds used to initialize context */ + gss_cred_id_t client_deleg_creds; /* delegated creds (const, not freeable) */ + } client; +}; + #ifdef WITH_SERVER int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +int ssh_gssapi_server_oids(gss_OID_set *selected); #endif /* WITH_SERVER */ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); @@ -44,10 +76,28 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client); SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); +int ssh_gssapi_init(ssh_session session); +void ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat); int ssh_gssapi_auth_mic(ssh_session session); +void ssh_gssapi_free(ssh_session session); +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids); +char *ssh_gssapi_name_to_char(gss_name_t name); +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host); +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags); + +char *ssh_gssapi_oid_hash(ssh_string oid); +char *ssh_gssapi_kex_mechs(ssh_session session); +int ssh_gssapi_check_client_config(ssh_session session); +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context); +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf); #ifdef __cplusplus } #endif +#endif /* WITH_GSSAPI */ #endif /* GSSAPI_H */ diff --git a/include/libssh/hybrid_mlkem.h b/include/libssh/hybrid_mlkem.h new file mode 100644 index 00000000..ca9b6a20 --- /dev/null +++ b/include/libssh/hybrid_mlkem.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * 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 HYBRID_MLKEM_H_ +#define HYBRID_MLKEM_H_ + +#include "libssh/mlkem.h" +#include "libssh/wrapper.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NISTP256_SHARED_SECRET_SIZE 32 +#define NISTP384_SHARED_SECRET_SIZE 48 + +int ssh_client_hybrid_mlkem_init(ssh_session session); +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_hybrid_mlkem_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* HYBRID_MLKEM_H_ */ diff --git a/include/libssh/kex-gss.h b/include/libssh/kex-gss.h new file mode 100644 index 00000000..65ae2fe4 --- /dev/null +++ b/include/libssh/kex-gss.h @@ -0,0 +1,36 @@ +/* + * kex-gss.h - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ +#ifndef KEX_GSS_H_ +#define KEX_GSS_H_ + +#include "config.h" +#ifdef WITH_GSSAPI + +int ssh_client_gss_kex_init(ssh_session session); +void ssh_server_gss_kex_init(ssh_session session); +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet); +void ssh_client_gss_kex_remove_callbacks(ssh_session session); +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session); + +#endif /* WITH_GSSAPI */ +#endif /* KEX_GSS_H_ */ diff --git a/include/libssh/kex.h b/include/libssh/kex.h index 6da6693c..435ecc88 100644 --- a/include/libssh/kex.h +++ b/include/libssh/kex.h @@ -31,6 +31,9 @@ struct ssh_kex_struct { char *methods[SSH_KEX_METHODS]; }; +/* crypto.h needs ssh_kex_struct so it is included below the struct definition */ +#include "libssh/crypto.h" + #ifdef __cplusplus extern "C" { #endif @@ -40,6 +43,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit); 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); @@ -51,10 +55,10 @@ 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); -const char *ssh_kex_get_supported_method(uint32_t algo); -const char *ssh_kex_get_default_methods(uint32_t algo); -const char *ssh_kex_get_fips_methods(uint32_t algo); -const char *ssh_kex_get_description(uint32_t algo); +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type); +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_description(enum ssh_kex_types_e type); char *ssh_client_select_hostkeys(ssh_session session); int ssh_send_rekex(ssh_session session); int server_set_kex(ssh_session session); @@ -63,6 +67,7 @@ int ssh_make_sessionid(ssh_session session); 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); +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto); #ifdef __cplusplus } diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h index 79a5fd5c..f9aea0d7 100644 --- a/include/libssh/libcrypto.h +++ b/include/libssh/libcrypto.h @@ -40,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 @@ -126,6 +121,15 @@ typedef BN_CTX* bignum_CTX; 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); + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +int pki_load_pkcs11_provider(void); +#else +ENGINE *pki_get_engine(void); +#endif +#endif /* WITH_PKCS11_PROVIDER */ + #endif /* HAVE_LIBCRYPTO */ #endif /* LIBCRYPTO_H_ */ diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h index 966fb044..ce0beefd 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 @@ -99,9 +98,9 @@ int ssh_gcry_rand_range(bignum rnd, bignum max); #define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max); #define bignum_dup(orig, dest) do { \ if (*(dest) == NULL) { \ - *(dest) = gcry_mpi_copy(orig); \ + *(dest) = gcry_mpi_copy((const gcry_mpi_t)orig); \ } else { \ - gcry_mpi_set(*(dest), orig); \ + gcry_mpi_set(*(dest), (const gcry_mpi_t)orig); \ } \ } while(0) /* Helper functions for data conversions. */ diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h index a4ee010b..71ebcccd 100644 --- a/include/libssh/libmbedcrypto.h +++ b/include/libssh/libmbedcrypto.h @@ -42,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 @@ -130,7 +129,7 @@ int ssh_mbedcry_hex2bn(bignum *dest, char *data); *(dest) = bignum_new(); \ } \ if (*(dest) != NULL) { \ - mbedtls_mpi_copy(orig, *(dest)); \ + mbedtls_mpi_copy(*(dest), orig); \ } \ } while(0) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 03c3a93f..aa7b9ef1 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-2023 by Aris Adamantiadis and the libssh team + * Copyright (c) 2003-2026 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 @@ -49,9 +49,10 @@ #endif #endif +#include #include +#include #include -#include #ifdef _MSC_VER typedef int mode_t; @@ -106,6 +107,7 @@ typedef struct ssh_session_struct* ssh_session; typedef struct ssh_string_struct* ssh_string; typedef struct ssh_event_struct* ssh_event; typedef struct ssh_connector_struct * ssh_connector; +typedef struct ssh_pki_ctx_struct *ssh_pki_ctx; typedef void* ssh_gssapi_creds; /* Socket type */ @@ -150,13 +152,14 @@ enum ssh_auth_e { }; /* auth flags */ -#define SSH_AUTH_METHOD_UNKNOWN 0x0000u -#define SSH_AUTH_METHOD_NONE 0x0001u -#define SSH_AUTH_METHOD_PASSWORD 0x0002u -#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u -#define SSH_AUTH_METHOD_HOSTBASED 0x0008u -#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u -#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_GSSAPI_KEYEX 0x0040u /* messages */ enum ssh_requests_e { @@ -295,7 +298,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 @@ -368,53 +372,67 @@ enum ssh_control_master_options_e { SSH_CONTROL_MASTER_AUTOASK }; +enum ssh_address_family_options_e { + SSH_ADDRESS_FAMILY_ANY, + SSH_ADDRESS_FAMILY_INET, + SSH_ADDRESS_FAMILY_INET6 +}; + enum ssh_options_e { - SSH_OPTIONS_HOST, - SSH_OPTIONS_PORT, - SSH_OPTIONS_PORT_STR, - SSH_OPTIONS_FD, - SSH_OPTIONS_USER, - SSH_OPTIONS_SSH_DIR, - SSH_OPTIONS_IDENTITY, - SSH_OPTIONS_ADD_IDENTITY, - SSH_OPTIONS_KNOWNHOSTS, - SSH_OPTIONS_TIMEOUT, - SSH_OPTIONS_TIMEOUT_USEC, - SSH_OPTIONS_SSH1, - SSH_OPTIONS_SSH2, - SSH_OPTIONS_LOG_VERBOSITY, - SSH_OPTIONS_LOG_VERBOSITY_STR, - SSH_OPTIONS_CIPHERS_C_S, - SSH_OPTIONS_CIPHERS_S_C, - SSH_OPTIONS_COMPRESSION_C_S, - SSH_OPTIONS_COMPRESSION_S_C, - SSH_OPTIONS_PROXYCOMMAND, - SSH_OPTIONS_BINDADDR, - SSH_OPTIONS_STRICTHOSTKEYCHECK, - SSH_OPTIONS_COMPRESSION, - SSH_OPTIONS_COMPRESSION_LEVEL, - SSH_OPTIONS_KEY_EXCHANGE, - SSH_OPTIONS_HOSTKEYS, - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, - SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, - SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, - SSH_OPTIONS_HMAC_C_S, - SSH_OPTIONS_HMAC_S_C, - SSH_OPTIONS_PASSWORD_AUTH, - SSH_OPTIONS_PUBKEY_AUTH, - SSH_OPTIONS_KBDINT_AUTH, - SSH_OPTIONS_GSSAPI_AUTH, - SSH_OPTIONS_GLOBAL_KNOWNHOSTS, - SSH_OPTIONS_NODELAY, - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, - SSH_OPTIONS_PROCESS_CONFIG, - SSH_OPTIONS_REKEY_DATA, - SSH_OPTIONS_REKEY_TIME, - SSH_OPTIONS_RSA_MIN_SIZE, - SSH_OPTIONS_IDENTITY_AGENT, - SSH_OPTIONS_IDENTITIES_ONLY, - SSH_OPTIONS_CONTROL_MASTER, - SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + SSH_OPTIONS_PROCESS_CONFIG, + SSH_OPTIONS_REKEY_DATA, + SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, + SSH_OPTIONS_IDENTITIES_ONLY, + SSH_OPTIONS_CONTROL_MASTER, + SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_CERTIFICATE, + SSH_OPTIONS_PROXYJUMP, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + SSH_OPTIONS_PKI_CONTEXT, + SSH_OPTIONS_ADDRESS_FAMILY, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + SSH_OPTIONS_NEXT_IDENTITY, }; enum { @@ -452,8 +470,19 @@ LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); LIBSSH_API int ssh_channel_close(ssh_channel channel); +#define SSH_CHANNEL_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_channel_free(x); \ + (x) = NULL; \ + } \ + } while (0) LIBSSH_API void ssh_channel_free(ssh_channel channel); -LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped); +SSH_DEPRECATED LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); @@ -477,6 +506,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); @@ -684,6 +715,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) @@ -697,9 +734,17 @@ 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 uint32_t ssh_key_get_sk_flags(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_application(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_user_id(const ssh_key key); + +SSH_DEPRECATED LIBSSH_API int +ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); + +LIBSSH_API int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey); -LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, - ssh_key *pkey); LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, @@ -710,6 +755,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, @@ -720,6 +772,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); @@ -775,10 +834,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, @@ -808,6 +865,7 @@ LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) LIBSSH_API void ssh_string_free(ssh_string str); LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API ssh_string ssh_string_from_data(const void *data, size_t len); LIBSSH_API size_t ssh_string_len(ssh_string str); LIBSSH_API ssh_string ssh_string_new(size_t size); LIBSSH_API const char *ssh_string_get_char(ssh_string str); @@ -815,6 +873,7 @@ LIBSSH_API char *ssh_string_to_char(ssh_string str); #define SSH_STRING_FREE_CHAR(x) \ do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) LIBSSH_API void ssh_string_free_char(char *s); +LIBSSH_API int ssh_string_cmp(ssh_string s1, ssh_string s2); LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, int verify); @@ -839,6 +898,7 @@ LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); +LIBSSH_API const char *ssh_get_supported_methods(enum ssh_kex_types_e type); LIBSSH_API ssh_buffer ssh_buffer_new(void); LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); @@ -851,6 +911,105 @@ 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); +/* SSHSIG hashes data independently from the key used, so we use a new enum + to avoid confusion. See + https://gitlab.com/jas/ietf-sshsig-format/-/blob/cc70a225cbd695d5a6f20aaebdb4b92b0818e43a/ietf-sshsig-format.md#L137 + */ +enum sshsig_digest_e { + SSHSIG_DIGEST_SHA2_256 = 0, + SSHSIG_DIGEST_SHA2_512 = 1, +}; + +LIBSSH_API int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature); +LIBSSH_API int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key); + +/* PKI context API */ + +enum ssh_pki_options_e { + SSH_PKI_OPTION_RSA_KEY_SIZE, + + /* Security Key options */ + SSH_PKI_OPTION_SK_APPLICATION, + SSH_PKI_OPTION_SK_FLAGS, + SSH_PKI_OPTION_SK_USER_ID, + SSH_PKI_OPTION_SK_CHALLENGE, + SSH_PKI_OPTION_SK_CALLBACKS, +}; + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +LIBSSH_API ssh_pki_ctx ssh_pki_ctx_new(void); + +LIBSSH_API int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value); + +LIBSSH_API int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata); + +#define SSH_SK_OPTION_NAME_DEVICE_PATH "device" +#define SSH_SK_OPTION_NAME_USER_ID "user" + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required); + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context); + +LIBSSH_API int +ssh_pki_ctx_get_sk_attestation_buffer(const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer); + +LIBSSH_API void ssh_pki_ctx_free(ssh_pki_ctx context); + +#define SSH_PKI_CTX_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_pki_ctx_free(x); \ + x = NULL; \ + } \ + } while (0) + +/* Security key resident keys API */ + +LIBSSH_API int +ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result); + #ifndef LIBSSH_LEGACY_0_4 #include "libssh/legacy.h" #endif diff --git a/include/libssh/libsshpp.hpp b/include/libssh/libsshpp.hpp index 602c7aec..553a7777 100644 --- a/include/libssh/libsshpp.hpp +++ b/include/libssh/libsshpp.hpp @@ -498,8 +498,22 @@ class Channel { return_throwable; } - int getExitStatus(){ - return ssh_channel_get_exit_status(channel); + /* + * @deprecated Please use getExitState() + */ + int getExitStatus() { + uint32_t exit_status = (uint32_t)-1; + ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + return exit_status; + } + void_throwable getExitState(uint32_t & pexit_code, + char **pexit_signal, + int & pcore_dumped) { + ssh_throw(ssh_channel_get_exit_state(channel, + &pexit_code, + pexit_signal, + &pcore_dumped)); + return_throwable; } Session &getSession(){ return *session; @@ -587,9 +601,12 @@ class Channel { 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); diff --git a/include/libssh/messages.h b/include/libssh/messages.h index 160306cc..9dd6b06c 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; + struct ssh_key_struct *server_pubkey; char *sigtype; enum ssh_publickey_state_e signature_state; char kbdint_response; diff --git a/include/libssh/misc.h b/include/libssh/misc.h index fe86d251..2a241276 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -33,8 +33,10 @@ # endif /* _MSC_VER */ #else -# include +#include +#include #endif /* _WIN32 */ +#include #ifdef __cplusplus extern "C" { @@ -42,8 +44,9 @@ extern "C" { /* in misc.c */ /* gets the user home dir. */ -char *ssh_get_user_home_dir(void); +char *ssh_get_user_home_dir(ssh_session session); char *ssh_get_local_username(void); +char *ssh_get_local_hostname(void); int ssh_file_readaccess_ok(const char *file); int ssh_dir_writeable(const char *path); @@ -65,6 +68,12 @@ struct ssh_iterator { const void *data; }; +struct ssh_jump_info_struct { + char *hostname; + char *username; + int port; +}; + struct ssh_timestamp { long seconds; long useconds; @@ -100,13 +109,14 @@ const void *_ssh_list_pop_head(struct ssh_list *list); #define ssh_list_pop_head(type, ssh_list)\ ((type)_ssh_list_pop_head(ssh_list)) +#define SSH_LIST_FREE(x) \ + do { if ((x) != NULL) { ssh_list_free(x); (x) = NULL; } } while(0) + 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); -int ssh_match_group(const char *group, const char *object); - void uint64_inc(unsigned char *counter); void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len); @@ -122,6 +132,14 @@ 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); + +void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); +bool ssh_libssh_proxy_jumps(void); + +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + #ifdef __cplusplus } #endif diff --git a/include/libssh/mlkem.h b/include/libssh/mlkem.h new file mode 100644 index 00000000..2b76a760 --- /dev/null +++ b/include/libssh/mlkem.h @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_H_ +#define MLKEM_H_ + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "libssh/session.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mlkem_type_info { + size_t pubkey_size; + size_t ciphertext_size; +#ifdef HAVE_GCRYPT_MLKEM + size_t privkey_size; + enum gcry_kem_algos alg; +#elif defined(HAVE_OPENSSL_MLKEM) + const char *name; +#else + size_t privkey_size; +#endif +}; + +extern const struct mlkem_type_info MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 +extern const struct mlkem_type_info MLKEM1024_INFO; +#endif + +#define MLKEM_SHARED_SECRET_SIZE 32 + +typedef unsigned char ssh_mlkem_shared_secret[MLKEM_SHARED_SECRET_SIZE]; + +const struct mlkem_type_info * +kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type); + +int ssh_mlkem_init(ssh_session session); + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_H_ */ diff --git a/include/libssh/mlkem_native.h b/include/libssh/mlkem_native.h new file mode 100644 index 00000000..d5fd8334 --- /dev/null +++ b/include/libssh/mlkem_native.h @@ -0,0 +1,127 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_NATIVE_H_ +#define MLKEM_NATIVE_H_ + +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPrivateKey +with const generics +- $2400size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPrivateKey_d9_s { + uint8_t value[2400U]; +} libcrux_ml_kem_types_MlKemPrivateKey_d9; + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPublicKey +with const generics +- $1184size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPublicKey_30_s { + uint8_t value[1184U]; +} libcrux_ml_kem_types_MlKemPublicKey_30; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768KeyPair_s { + libcrux_ml_kem_types_MlKemPrivateKey_d9 sk; + libcrux_ml_kem_types_MlKemPublicKey_30 pk; +} libcrux_ml_kem_mlkem768_MlKem768KeyPair; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s { + uint8_t value[1088U]; +} libcrux_ml_kem_mlkem768_MlKem768Ciphertext; + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_types_MlKemCiphertext[[$1088size_t]], +uint8_t[32size_t] + +*/ +typedef struct tuple_c2_s { + libcrux_ml_kem_mlkem768_MlKem768Ciphertext fst; + uint8_t snd[32U]; +} tuple_c2; + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]); + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_NATIVE_H_ */ diff --git a/include/libssh/options.h b/include/libssh/options.h index 9050d3be..63b207fa 100644 --- a/include/libssh/options.h +++ b/include/libssh/options.h @@ -25,6 +25,7 @@ extern "C" { #endif +int ssh_config_parse(ssh_session session, FILE *fp, bool global); 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, @@ -33,6 +34,8 @@ int ssh_options_set_algo(ssh_session session, char **place); int ssh_options_apply(ssh_session session); +char *ssh_options_get_algo(ssh_session session, enum ssh_kex_types_e algo); + #ifdef __cplusplus } #endif diff --git a/include/libssh/packet.h b/include/libssh/packet.h index 7f10a709..531d7e4b 100644 --- a/include/libssh/packet.h +++ b/include/libssh/packet.h @@ -58,6 +58,7 @@ extern "C" { SSH_PACKET_CALLBACK(ssh_packet_unimplemented); SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_debug_callback); SSH_PACKET_CALLBACK(ssh_packet_dh_reply); SSH_PACKET_CALLBACK(ssh_packet_newkeys); SSH_PACKET_CALLBACK(ssh_packet_service_accept); @@ -67,6 +68,7 @@ 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); diff --git a/include/libssh/pki.h b/include/libssh/pki.h index 096a645f..e22c05f8 100644 --- a/include/libssh/pki.h +++ b/include/libssh/pki.h @@ -46,11 +46,24 @@ #define MAX_PUBKEY_SIZE 0x100000 /* 1M */ #define MAX_PRIVKEY_SIZE 0x400000 /* 4M */ +#define RSA_MIN_KEY_SIZE 1024 +#define RSA_MIN_FIPS_KEY_SIZE 2048 +#define RSA_DEFAULT_KEY_SIZE 3072 + #define SSH_KEY_FLAG_EMPTY 0x0 #define SSH_KEY_FLAG_PUBLIC 0x0001 #define SSH_KEY_FLAG_PRIVATE 0x0002 #define SSH_KEY_FLAG_PKCS11_URI 0x0004 +/* Constants matching the Lightweight Secure Shell Signature Format */ +/* https://datatracker.ietf.org/doc/draft-josefsson-sshsig-format */ +#define SSHSIG_VERSION 0x01 +#define SSHSIG_MAGIC_PREAMBLE "SSHSIG" +#define SSHSIG_MAGIC_PREAMBLE_LEN (sizeof(SSHSIG_MAGIC_PREAMBLE) - 1) +#define SSHSIG_BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----" +#define SSHSIG_END_SIGNATURE "-----END SSH SIGNATURE-----" +#define SSHSIG_LINE_LENGTH 76 + struct ssh_key_struct { enum ssh_keytypes_e type; int flags; @@ -60,22 +73,31 @@ struct ssh_key_struct { gcry_sexp_t rsa; gcry_sexp_t ecdsa; #elif defined(HAVE_LIBMBEDCRYPTO) - mbedtls_pk_context *rsa; + mbedtls_pk_context *pk; mbedtls_ecdsa_context *ecdsa; #elif defined(HAVE_LIBCRYPTO) - /* This holds either ENGINE key for PKCS#11 support or just key in - * high-level format */ + /* This holds either ENGINE/PROVIDER key for PKCS#11 support + * or just key in high-level format */ EVP_PKEY *key; + /* keep this around for FIPS mode so we can parse the public keys. We won't + * be able to use them nor use the private keys though */ uint8_t *ed25519_pubkey; - uint8_t *ed25519_privkey; #endif /* HAVE_LIBGCRYPT */ #ifndef HAVE_LIBCRYPTO ed25519_pubkey *ed25519_pubkey; ed25519_privkey *ed25519_privkey; #endif /* HAVE_LIBCRYPTO */ ssh_string sk_application; - void *cert; + ssh_buffer cert; enum ssh_keytypes_e cert_type; + + /* Security Key specific private data */ + uint8_t sk_flags; + ssh_string sk_key_handle; + ssh_string sk_reserved; + + /* Resident key specific metadata */ + ssh_string sk_user_id; }; struct ssh_signature_struct { @@ -127,6 +149,11 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name); ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ (kt) <= SSH_KEYTYPE_ED25519_CERT01)) +#define is_sk_key_type(kt) \ + ((kt) == SSH_KEYTYPE_SK_ECDSA || (kt) == SSH_KEYTYPE_SK_ED25519 || \ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 || \ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01) + /* SSH Signature Functions */ ssh_signature ssh_signature_new(void); void ssh_signature_free(ssh_signature sign); @@ -153,6 +180,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, diff --git a/include/libssh/pki_context.h b/include/libssh/pki_context.h new file mode 100644 index 00000000..6fb48bcf --- /dev/null +++ b/include/libssh/pki_context.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_CONTEXT_H +#define PKI_CONTEXT_H + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" + +/** + * @brief Security key context structure + * + * Context structure containing all parameters and callbacks + * needed for FIDO2/U2F security key operations. + */ +struct ssh_pki_ctx_struct { + /** @brief Desired RSA modulus size in bits + * + * Specified size of RSA keys to generate. If set to 0, defaults to 3072 + * bits. Must be greater than or equal to 1024, as anything below is + * considered insecure. + */ + int rsa_key_size; + + /** @brief Security key callbacks + * + * Provides enroll/sign/load_resident_keys operations. + */ + const struct ssh_sk_callbacks_struct *sk_callbacks; + + /** @brief Application identifier string for the security key credential + * + * FIDO2 relying party identifier, typically "ssh:user@hostname" format. + * This is required for all security key operations. + */ + char *sk_application; + + /** @brief FIDO2 operation flags + * + * Bitfield controlling authenticator behavior. Combine with bitwise OR: + * - SSH_SK_USER_PRESENCE_REQD (0x01): Require user touch + * - SSH_SK_USER_VERIFICATION_REQD (0x04): Require PIN/biometric + * - SSH_SK_FORCE_OPERATION (0x10): Override duplicate detection + * - SSH_SK_RESIDENT_KEY (0x20): Create discoverable credential + */ + uint8_t sk_flags; + + /** @brief PIN callback for authenticator user verification (optional) + * + * Callback invoked to obtain a PIN or perform user verification when + * SSH_SK_USER_VERIFICATION_REQD is set or the authenticator requires it. + * If NULL, no interactive PIN retrieval is performed. + */ + ssh_auth_callback sk_pin_callback; + + /** @brief User supplied pointer passed to callbacks (optional) + * + * Generic pointer set by the application and forwarded to + * interactive callbacks (e.g. PIN callback) to allow applications to + * carry state context. + */ + void *sk_userdata; + + /** @brief Custom challenge data for enrollment (optional) + * + * Buffer containing challenge data signed by the authenticator. + * If NULL, a random 32-byte challenge is automatically generated. + */ + ssh_buffer sk_challenge_buffer; + + /** @brief Options to be passed to the sk_callbacks (optional) + * + * NULL-terminated array of sk_option pointers owned by this context. + */ + struct sk_option **sk_callbacks_options; + + /** @brief The buffer used to store attestation information returned in a + * key enrollment operation + */ + ssh_buffer sk_attestation_buffer; +}; + +/* Internal PKI context functions */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context); + +#endif /* PKI_CONTEXT_H */ diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h index c63e129d..2a6f8966 100644 --- a/include/libssh/pki_priv.h +++ b/include/libssh/pki_priv.h @@ -61,6 +61,7 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, enum ssh_keytypes_e type); /* SSH Key Functions */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote); 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_ecdsa(ssh_key key, int parameter); @@ -92,7 +93,7 @@ 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_rsa(ssh_key key, @@ -106,7 +107,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, @@ -143,11 +143,13 @@ int pki_ed25519_key_cmp(const ssh_key k1, enum ssh_keycmp_e what); 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, ssh_string pubkey, ssh_string privkey); +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey); /* PKI Container OpenSSH */ ssh_key ssh_pki_openssh_pubkey_import(const char *text_key); @@ -162,6 +164,16 @@ 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); + +/* Security Key Helper Functions */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, const ssh_key key); +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key); +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out); + #ifdef __cplusplus } #endif diff --git a/include/libssh/pki_sk.h b/include/libssh/pki_sk.h new file mode 100644 index 00000000..f6c33300 --- /dev/null +++ b/include/libssh/pki_sk.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_SK_H +#define PKI_SK_H + +#include "libssh/libssh.h" +#include "libssh/pki.h" + +#include + +#define SSH_SK_MAX_USER_ID_LEN 64 + +/** + * @brief Enroll a new security key using a U2F/FIDO2 authenticator + * + * Creates a new security key credential configured according to the parameters + * in the PKI context. This function handles key enrollment for both ECDSA and + * Ed25519 algorithms, generates appropriate challenges, and returns the + * enrolled key with optional attestation data. + * + * The PKI context must be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. Required + * options include SSH_PKI_OPTION_SK_APPLICATION, SSH_PKI_OPTION_SK_USER_ID, and + * SSH_PKI_OPTION_SK_CALLBACKS. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key_type The type of key to enroll (SSH_KEYTYPE_SK_ECDSA or + * SSH_KEYTYPE_SK_ED25519) + * @param[out] enrolled_key_result Pointer to store the enrolled ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see ssh_pki_ctx_get_sk_attestation_buffer() + */ +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result); + +/** + * @brief Sign arbitrary data using a security key and a PKI context + * + * This function performs signing operations configured according to the + * parameters in the PKI context and returns a properly formatted + * ssh_signature. The caller must free the signature when it is no longer + * needed. + * + * The PKI context should be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. The security + * key must have been previously enrolled or loaded. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key The security key to use for signing + * @param[in] data The data to sign + * @param[in] data_len Length of data to sign + * + * @return A valid ssh_signature on success, NULL on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see pki_sk_enroll_key() + * @see ssh_signature_free() + */ +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const uint8_t *data, + size_t data_len); + +#endif /* PKI_SK_H */ diff --git a/include/libssh/poll.h b/include/libssh/poll.h index 8e30676e..478764b6 100644 --- a/include/libssh/poll.h +++ b/include/libssh/poll.h @@ -157,6 +157,7 @@ void ssh_poll_ctx_free(ssh_poll_ctx ctx); int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +bool ssh_poll_is_locked(ssh_poll_handle p); int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 4ec3b2a7..56b84c5d 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -30,10 +30,11 @@ #define _LIBSSH_PRIV_H #include +#include #include #include #include -#include +#include #if !defined(HAVE_STRTOULL) # if defined(HAVE___STRTOULL) @@ -47,6 +48,10 @@ # endif #endif /* !defined(HAVE_STRTOULL) */ +#ifdef HAVE_TERMIOS_H +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -160,6 +165,9 @@ int ssh_gettimeofday(struct timeval *__p, void *__t); #define gettimeofday ssh_gettimeofday +struct tm *ssh_localtime(const time_t *timer, struct tm *result); +# define localtime_r ssh_localtime + #define _XCLOSESOCKET closesocket # ifdef HAVE_IO_H @@ -270,6 +278,10 @@ void ssh_log_common(struct ssh_common_struct *common, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(4, 5); +void _ssh_remove_legacy_log_cb(void); + +/* log.c */ +void _ssh_reset_log_cb(void); /* ERROR HANDLING */ @@ -304,6 +316,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, @@ -320,7 +333,13 @@ int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); /* match.c */ int match_pattern_list(const char *string, const char *pattern, size_t len, int dolower); -int match_hostname(const char *host, const char *pattern, unsigned int len); +int match_hostname(const char *host, const char *pattern, size_t len); +#ifndef _WIN32 +int match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family); +#endif +int match_group(const char *group, const char *object); /* connector.c */ int ssh_connector_set_event(ssh_connector connector, ssh_event event); @@ -338,17 +357,48 @@ int ssh_connector_remove_event(ssh_connector connector); #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) /** Zero a structure */ -#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) /** Zero a structure given a pointer to the structure */ -#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((x), 0, sizeof(*(x))); } while(0) /** Get the size of an array */ #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) -#ifndef HAVE_EXPLICIT_BZERO -void explicit_bzero(void *s, size_t n); -#endif /* !HAVE_EXPLICIT_BZERO */ +/** Securely zero memory in a way that won't be optimized away */ +#if defined(HAVE_MEMSET_EXPLICIT) +#define ssh_burn(ptr, len) memset_explicit((ptr), '\0', (len)) +#elif defined(HAVE_EXPLICIT_BZERO) +#define ssh_burn(ptr, len) explicit_bzero((ptr), (len)) +#elif defined(HAVE_MEMSET_S) +#define ssh_burn(ptr, len) memset_s((ptr), (len), '\0', (len)) +#elif defined(HAVE_SECURE_ZERO_MEMORY) +#define ssh_burn(ptr, len) SecureZeroMemory((ptr), (len)) +#else +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + __asm__ volatile("" : : "g"(ptr) : "memory"); \ + } while (0) +#else +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + } while (0) +#endif +#endif + +void burn_free(void *ptr, size_t len); + +/** Free memory space after zeroing it */ +#define BURN_FREE(x, len) \ + do { \ + if ((x) != NULL) { \ + burn_free((x), (len)); \ + (x) = NULL; \ + } \ + } while (0) /** * This is a hack to fix warnings. The idea is to use this everywhere that we @@ -369,6 +419,7 @@ void explicit_bzero(void *s, size_t n); */ #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) +#ifndef __VA_NARG__ /** * Get the argument count of variadic arguments */ @@ -400,6 +451,7 @@ void explicit_bzero(void *s, size_t n); 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#endif #define CLOSE_SOCKET(s) do { if ((s) != SSH_INVALID_SOCKET) { _XCLOSESOCKET(s); (s) = SSH_INVALID_SOCKET;} } while(0) @@ -452,6 +504,13 @@ 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); + +/** The default maximum file size for a configuration file */ +#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 + #ifdef __cplusplus } #endif diff --git a/include/libssh/server.h b/include/libssh/server.h index 4033dac7..ee800567 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -36,28 +36,31 @@ extern "C" { #endif enum ssh_bind_options_e { - SSH_BIND_OPTIONS_BINDADDR, - SSH_BIND_OPTIONS_BINDPORT, - SSH_BIND_OPTIONS_BINDPORT_STR, - SSH_BIND_OPTIONS_HOSTKEY, - SSH_BIND_OPTIONS_DSAKEY, /* 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, /* deprecated */ - SSH_BIND_OPTIONS_IMPORT_KEY, - SSH_BIND_OPTIONS_KEY_EXCHANGE, - SSH_BIND_OPTIONS_CIPHERS_C_S, - SSH_BIND_OPTIONS_CIPHERS_S_C, - SSH_BIND_OPTIONS_HMAC_C_S, - SSH_BIND_OPTIONS_HMAC_S_C, - SSH_BIND_OPTIONS_CONFIG_DIR, - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - SSH_BIND_OPTIONS_PROCESS_CONFIG, - SSH_BIND_OPTIONS_MODULI, - SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + 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, /* deprecated */ + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_IMPORT_KEY_STR, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, }; typedef struct ssh_bind_struct* ssh_bind; @@ -117,7 +120,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 occurred. + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. * * @code * struct ssh_callbacks_struct cb = { @@ -171,7 +174,7 @@ LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); * @param ssh_bind_o The ssh server bind to accept a connection. * @param session A preallocated ssh session * @see ssh_new - * @return SSH_OK when a connection is established + * @return `SSH_OK` when a connection is established */ LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); @@ -185,7 +188,7 @@ LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); * inbound connection * @see ssh_new * @see ssh_bind_accept - * @return SSH_OK when a connection is established + * @return `SSH_OK` when a connection is established */ LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, socket_t fd); @@ -197,7 +200,7 @@ LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); * * @param session A connected ssh session * @see ssh_bind_accept - * @return SSH_OK if the key exchange was successful + * @return `SSH_OK` if the key exchange was successful */ LIBSSH_API int ssh_handle_key_exchange(ssh_session session); @@ -214,14 +217,16 @@ LIBSSH_API int ssh_handle_key_exchange(ssh_session session); * @see ssh_handle_key_exchange * @see ssh_options_set * - * @return SSH_OK if initialization succeeds. + * @return `SSH_OK` if initialization succeeds. */ - 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); @@ -242,6 +247,7 @@ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); * SSH_AUTH_METHOD_HOSTBASED * SSH_AUTH_METHOD_INTERACTIVE * SSH_AUTH_METHOD_GSSAPI_MIC + * SSH_AUTH_METHOD_GSSAPI_KEYEX */ LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); @@ -253,7 +259,7 @@ LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); * * @param[in] banner The server's banner. * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. */ LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); @@ -292,7 +298,7 @@ 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 occurred. + * @return The password or NULL if an error occurred. * * @see ssh_message_get() * @see ssh_message_type() diff --git a/include/libssh/session.h b/include/libssh/session.h index b3b3e4e6..da39df2a 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -58,16 +58,17 @@ enum ssh_dh_state_e { }; enum ssh_pending_call_e { - SSH_PENDING_CALL_NONE = 0, - SSH_PENDING_CALL_CONNECT, - SSH_PENDING_CALL_AUTH_NONE, - SSH_PENDING_CALL_AUTH_PASSWORD, - SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, - SSH_PENDING_CALL_AUTH_PUBKEY, - SSH_PENDING_CALL_AUTH_AGENT, - SSH_PENDING_CALL_AUTH_KBDINT_INIT, - SSH_PENDING_CALL_AUTH_KBDINT_SEND, - SSH_PENDING_CALL_AUTH_GSSAPI_MIC + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND, + SSH_PENDING_CALL_AUTH_GSSAPI_MIC, + SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX, }; /* libssh calls may block an undefined amount of time */ @@ -84,6 +85,15 @@ enum ssh_pending_call_e { * 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 +/* The scp on server can not handle quoted paths. Skip the mitigation for + * CVE-2019-14889 when using scp */ +#define SSH_SESSION_FLAG_SCP_QUOTING_BROKEN 0x0040 + /* codes to use with ssh_handle_packets*() */ /* Infinite timeout */ #define SSH_TIMEOUT_INFINITE -1 @@ -114,6 +124,8 @@ enum ssh_pending_call_e { /* server-sig-algs extension */ #define SSH_EXT_SIG_RSA_SHA256 0x02 #define SSH_EXT_SIG_RSA_SHA512 0x04 +/* Host-bound public key authentication extension */ +#define SSH_EXT_PUBLICKEY_HOSTBOUND 0x08 /* members that are common to ssh_session and ssh_bind */ struct ssh_common_struct { @@ -134,6 +146,7 @@ struct ssh_session_struct { uint32_t send_seq; uint32_t recv_seq; struct ssh_timestamp last_rekey_time; + bool proxy_root; int connected; /* !=0 when the user got a session handle */ @@ -189,6 +202,8 @@ struct ssh_session_struct { */ bool first_kex_follows_guess_wrong; + ssh_string gssapi_key_exchange_mic; + ssh_buffer in_hashbuf; ssh_buffer out_hashbuf; struct ssh_crypto_struct *current_crypto; @@ -231,17 +246,22 @@ struct ssh_session_struct { struct { struct ssh_list *identity; struct ssh_list *identity_non_exp; + struct ssh_iterator *identity_it; + struct ssh_list *certificate; + struct ssh_list *certificate_non_exp; + struct ssh_list *proxy_jumps; + struct ssh_list *proxy_jumps_user_cb; + char *proxy_jumps_str; char *username; char *host; char *bindaddr; /* bind the client to an ip addr */ + char *homedir; char *sshdir; char *knownhosts; char *global_knownhosts; char *wanted_methods[SSH_KEX_METHODS]; char *pubkey_accepted_types; char *ProxyCommand; - char *custombanner; - char *moduli_file; char *agent_socket; unsigned long timeout; /* seconds */ unsigned long timeout_usec; @@ -251,6 +271,8 @@ struct ssh_session_struct { char compressionlevel; char *gss_server_identity; char *gss_client_identity; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; int gss_delegate_creds; int flags; int exp_flags; @@ -263,10 +285,22 @@ struct ssh_session_struct { bool identities_only; int control_master; char *control_path; + int address_family; } opts; + + /* server options */ + struct { + char *custombanner; + char *moduli_file; + } server_opts; + /* counters */ ssh_counter socket_counter; ssh_counter raw_counter; + + /* PKI context structure containing various parameters to configure PKI + * operations */ + struct ssh_pki_ctx_struct *pki_context; }; /** @internal diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index e210a1be..b7d6ad94 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -74,9 +74,27 @@ typedef struct sftp_file_struct* sftp_file; typedef struct sftp_message_struct* sftp_message; typedef struct sftp_packet_struct* sftp_packet; typedef struct sftp_request_queue_struct* sftp_request_queue; + +/** + * @brief SFTP session handle. + * + * This type represents an active SFTP session associated with an SSH channel. + * It is created and destroyed via the libssh SFTP API and is internally + * managed by libssh. It is used by applications to perform SFTP operations + * such as file access and directory management. + * + * The internal structure of this type is opaque and must not be accessed + * directly by applications. + * + * @see sftp_new + * @see sftp_free + */ 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; +typedef struct sftp_name_id_map_struct *sftp_name_id_map; struct sftp_session_struct { ssh_session session; @@ -90,6 +108,7 @@ struct sftp_session_struct { void **handles; sftp_ext ext; sftp_packet read_packet; + sftp_limits_t limits; }; struct sftp_packet_struct { @@ -200,6 +219,32 @@ struct sftp_statvfs_struct { uint64_t f_namemax; /** maximum filename length */ }; +/** + * @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 SFTP names map structure to store the mapping between ids and names. + * + * This is mainly for the use of sftp_get_users_groups_by_id() function. + */ +struct sftp_name_id_map_struct { + /** @brief Count of name-id pairs in the map */ + uint32_t count; + + /** @brief Array of ids, ids[i] mapped to names[i] */ + uint32_t *ids; + + /** @brief Array of names, names[i] mapped to ids[i] */ + char **names; +}; + /** * @brief Creates a new sftp session. * @@ -207,24 +252,30 @@ struct sftp_statvfs_struct { * with the server inside of the provided ssh session. This function call is * usually followed by the sftp_init(), which initializes SFTP protocol itself. * - * @param session The ssh session to use. + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. * * @return A new sftp session or NULL on error. * * @see sftp_free() * @see sftp_init() + * @see ssh_set_blocking() */ LIBSSH_API sftp_session sftp_new(ssh_session session); /** * @brief Start a new sftp session with an existing channel. * - * @param session The ssh session to use. + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. * @param channel An open session channel with subsystem already allocated * * @return A new sftp session or NULL on error. * * @see sftp_free() + * @see ssh_set_blocking() */ LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); @@ -476,13 +527,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 +576,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 +602,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. @@ -565,6 +630,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. * @@ -605,8 +893,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); @@ -699,6 +986,29 @@ LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char */ LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); +/** + * @brief This request is like setstat (excluding mode and size) but sets file + * attributes on symlinks themselves. + * + * 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 symbolic link which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr); + /** * @brief Change the file owner and group * @@ -842,6 +1152,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. * @@ -864,6 +1192,92 @@ 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); + +/** + * @brief Get the specified user's home directory + * + * This calls the "home-directory" extension. You should check if the extension + * is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "home-directory", "1"); + * @endcode + * + * @param sftp The sftp session handle. + * + * @param username username of the user whose home directory is requested. + * + * @return On success, a newly allocated string containing the + * absolute real-path of the home directory of the user. + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_home_directory(sftp_session sftp, const char *username); + +/** + * @brief Create a new sftp_name_id_map struct. + * + * @param count The number of ids/names to store in the map. + * + * @return A pointer to the newly allocated sftp_name_id_map + * struct. + */ +LIBSSH_API sftp_name_id_map sftp_name_id_map_new(uint32_t count); + +/** + * @brief Free the memory of an allocated `sftp_name_id_map` struct. + * + * @param map A pointer to the `sftp_name_id_map` struct to free. + */ +LIBSSH_API void sftp_name_id_map_free(sftp_name_id_map map); + +/** + * @brief Retrieves usernames and group names based on provided user and group + * IDs. + * + * The retrieved names are stored in the `names` field of the + * `sftp_name_id_map` structure. In case a uid or gid is not found, an empty + * string is stored. + * + * This calls the "users-groups-by-id@openssh.com" extension. + * You should check if the extension is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, + * "users-groups-by-id@openssh.com", "1"); + * @endcode + * + * @param sftp The SFTP session handle. + * + * @param users_map A pointer to a `sftp_name_id_map` struct with the user + * IDs. Can be NULL if only group names are needed. + * + * @param groups_map A pointer to a `sftp_name_id_map` struct with the group + * IDs. Can be NULL if only user names are needed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @note The caller needs to free the memory used for + * the maps later using `sftp_name_id_map_free()`. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map); + #ifdef WITH_SERVER /** * @brief Create a new sftp server session. diff --git a/include/libssh/sftp_priv.h b/include/libssh/sftp_priv.h index 70987b1e..4b578257 100644 --- a/include/libssh/sftp_priv.h +++ b/include/libssh/sftp_priv.h @@ -21,6 +21,8 @@ #ifndef SFTP_PRIV_H #define SFTP_PRIV_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -54,6 +56,61 @@ int sftp_reply_version(sftp_client_message client_msg); */ 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); + +/** + * @brief Receive the response of an sftp request + * + * In blocking mode, if the response hasn't arrived at the time of call, this + * function waits for the response to arrive. + * + * @param sftp The sftp session via which the request was sent. + * + * @param id The request identifier of the request whose + * corresponding response is required. + * + * @param blocking Flag to indicate the operating mode. true indicates + * blocking mode and false indicates non-blocking mode + * + * @param msg_ptr Pointer to the location to store the response message. + * In case of success, the message is allocated + * dynamically and must be freed (using + * sftp_message_free()) by the caller after usage. In case + * of failure, this is left untouched. + * + * @returns SSH_OK on success + * @returns SSH_ERROR on failure with the sftp and ssh errors set + * @returns SSH_AGAIN in case of non-blocking mode if the response hasn't + * arrived yet. + * + * @warning In blocking mode, this may block indefinitely for an invalid request + * identifier. + */ +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr); + +/* + * 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 diff --git a/include/libssh/sftpserver.h b/include/libssh/sftpserver.h index d7ed6e49..01b17c7f 100644 --- a/include/libssh/sftpserver.h +++ b/include/libssh/sftpserver.h @@ -29,6 +29,10 @@ extern "C" { #endif #include + +#include "libssh/libssh.h" +#include "libssh/sftp.h" + /** * @defgroup libssh_sftp_server The libssh SFTP server API * diff --git a/include/libssh/sk_api.h b/include/libssh/sk_api.h new file mode 100644 index 00000000..f85f4b8b --- /dev/null +++ b/include/libssh/sk_api.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2019 Google LLC + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file is a copy of the OpenSSH project's sk-api.h file pulled from + * https://github.com/openssh/openssh-portable/commit/a9cbe10da2be5be76755af0cea029db0f9c1f263 + * with only the flags, algorithms, error codes, and struct definitions. The + * function declarations and other OpenSSH-specific code have been removed. + */ + +#ifndef SK_API_H +#define SK_API_H 1 + +#include +#include + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +/* Algorithms */ + +/** ECDSA with P-256 curve */ +#define SSH_SK_ECDSA 0x00 + +/** Ed25519 - FIDO2 only */ +#define SSH_SK_ED25519 0x01 + +/* Error codes */ + +/** General unspecified failure */ +#define SSH_SK_ERR_GENERAL -1 + +/** Requested algorithm/feature/option not supported */ +#define SSH_SK_ERR_UNSUPPORTED -2 + +/** PIN (or other user verification) required but either missing or invalid */ +#define SSH_SK_ERR_PIN_REQUIRED -3 + +/** No suitable security key / authenticator device was found */ +#define SSH_SK_ERR_DEVICE_NOT_FOUND -4 + +/** Attempt to create a resident key that already exists (duplicate) */ +#define SSH_SK_ERR_CREDENTIAL_EXISTS -5 + +/** + * @brief Response structure for FIDO2/U2F key enrollment operations + * + * Contains all data returned by a FIDO2/U2F authenticator after successful + * enrollment of a new credential. + */ +struct sk_enroll_response { + /** @brief FIDO2/U2F authenticator flags from the enrollment operation + * + * Contains flags indicating authenticator capabilities and state during + * enrollment, such as user presence (UP), user verification + * (UV), and resident key. + */ + uint8_t flags; + + /** @brief Public key data in standard format + * + * For ECDSA (P-256): 65 bytes in SEC1 uncompressed point format + * (0x04 prefix + 32-byte X coordinate + 32-byte Y coordinate) + * For Ed25519: 32 bytes containing the raw public key (FIDO2 only) + */ + uint8_t *public_key; + + /** @brief Length of public_key buffer in bytes + * + * Expected values: 65 for ECDSA P-256, 32 for Ed25519 + */ + size_t public_key_len; + + /** @brief Opaque credential handle/ID used to identify this key + * + * Authenticator-generated binary data that uniquely identifies this + * credential. Used in subsequent sign operations to specify which + * key to use. Format and contents are authenticator-specific. + */ + uint8_t *key_handle; + + /** @brief Length of key_handle buffer in bytes + * + * Length varies by authenticator. + */ + size_t key_handle_len; + + /** @brief Enrollment signature over the enrollment data + * + * FIDO2/U2F authenticator signature proving the credential was created + * by this specific authenticator. Used for enrollment verification. + * Format depends on algorithm. + */ + uint8_t *signature; + + /** @brief Length of signature buffer in bytes + * + * Length varies by algorithm. + */ + size_t signature_len; + + /** @brief X.509 attestation certificate + * + * Certificate that attests to the authenticity of the authenticator + * and the enrollment operation. Used to verify the authenticator's + * identity and manufacturer. + */ + uint8_t *attestation_cert; + + /** @brief Length of attestation_cert buffer in bytes */ + size_t attestation_cert_len; + + /** @brief FIDO2/U2F authenticator data from enrollment + * + * CBOR-encoded authenticator data containing RP ID hash, flags, + * counter, and attested credential data. Used for attestation + * verification according to the FIDO2 specification. + */ + uint8_t *authdata; + + /** @brief Length of authdata buffer in bytes + * + * Length varies depending on credential data and extensions. + */ + size_t authdata_len; +}; + +/** + * @brief Response structure for FIDO2/U2F key signing operations + * + * Contains signature components and metadata returned by a FIDO2/U2F + * authenticator after a successful signing operation. + */ +struct sk_sign_response { + /** @brief FIDO2/U2F authenticator flags from the signing operation + * + * Contains flags indicating authenticator state during signing, + * including user presence (UP) and user verification (UV) flags. + * Used to verify that proper user interaction occurred while signing. + */ + uint8_t flags; + + /** @brief Authenticator signature counter value + * + * Monotonically increasing counter maintained by the authenticator. + * Incremented on each successful signing operation. Used to detect + * cloned or duplicated authenticators. + */ + uint32_t counter; + + /** @brief R component of ECDSA signature or Ed25519 signature */ + uint8_t *sig_r; + + /** @brief Length of sig_r buffer in bytes */ + size_t sig_r_len; + + /** @brief S component of ECDSA signature */ + uint8_t *sig_s; + + /** @brief Length of sig_s buffer in bytes */ + size_t sig_s_len; +}; + +/** + * @brief Structure representing a resident/discoverable credential + * + * Represents a FIDO2 resident key (discoverable credential) that is + * stored on the authenticator and can be discovered without providing + * a credential ID. + */ +struct sk_resident_key { + /** @brief Cryptographic algorithm identifier for this key + * + * SSH_SK_ECDSA (0x00): ECDSA with P-256 curve + * SSH_SK_ED25519 (0x01): Ed25519 signature algorithm + */ + uint32_t alg; + + /** @brief Slot/index number of this key on the authenticator + * + * Zero-based index indicating the position of this resident key + * in the authenticator's internal storage. Used for key management + * and identification when multiple resident keys exist. + */ + size_t slot; + + /** @brief Relying Party (application) identifier string + * + * The RP ID (typically a domain name) that this resident key + * is associated with. Determines which application/service + * this key can be used for. + */ + char *application; + + /** @brief Embedded enrollment response containing key material + * + * Contains the same data as returned during initial enrollment, + * including public key, key handle, and associated metadata. + */ + struct sk_enroll_response key; + + /** @brief Flags associated with this resident key + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence for operations + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * (PIN/biometric) + */ + uint8_t flags; + + /** @brief User identifier associated with this resident key + * + * Binary user ID that was provided during key enrollment. + * Used to identify which user account this key belongs to. + */ + uint8_t *user_id; + + /** @brief Length of user_id buffer in bytes + * + * Length of the user identifier. + */ + size_t user_id_len; +}; + +/** + * @brief Configuration option structure for FIDO2/U2F operations + * + * Represents a single configuration parameter that can be passed + * to FIDO2/U2F middleware. + */ +struct sk_option { + /** @brief Option name/key identifier */ + char *name; + + /** @brief Option value as bytes */ + char *value; + + /** @brief Indicates if this option is required for the operation + * + * Non-zero if this option must be processed and cannot be ignored. + * Zero if this option is advisory and can be skipped if the + * middleware does not support it. + */ + uint8_t required; +}; + +/** Current SK API version */ +#define SSH_SK_VERSION_MAJOR 0x000a0000 +#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000 + +#endif /* SK_API_H */ diff --git a/include/libssh/sk_common.h b/include/libssh/sk_common.h new file mode 100644 index 00000000..7c18448d --- /dev/null +++ b/include/libssh/sk_common.h @@ -0,0 +1,213 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_COMMON_H +#define SK_COMMON_H + +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" + +#include + +#define SK_MAX_USER_ID_LEN 64 + +#define SK_NOT_SUPPORTED_MSG \ + "Security Key functionality is not supported in this build of libssh. " \ + "Please enable support by building using the WITH_FIDO2 build option." + +/** + * @brief Convert security key error code to human-readable string + * + * Converts a security key error code to a descriptive string representation + * that can be used for logging user-facing error messages. + * + * @param[in] sk_err The security key error code to convert. + * + * @return Constant string describing the error. Never returns NULL. + * Returns "Unknown error" for unrecognized error codes. + * + * @note The returned string is statically allocated and should not be freed. + */ +const char *ssh_sk_err_to_string(int sk_err); + +/** + * @brief Securely clear the contents of an sk_enroll_response structure + * + * Overwrites sensitive data within the enrollment response structure with + * zeros to prevent information leakage. This function only clears and frees the + * contents and does not free the structure itself. + * + * @param[in] enroll_response The enrollment response structure to clear. + * Can be NULL (no operation performed). + * + * @note This function only frees the memory for the contents and does not free + * memory for the structure itself. Use sk_enroll_response_free() for complete + * cleanup, which also performs secure clearing internally. + */ +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response); + +/** + * @brief Securely free an sk_enroll_response structure + * + * Performs secure clearing of sensitive data within the enrollment response + * structure before freeing the allocated memory. This function internally + * calls sk_enroll_response_burn() before deallocation. + * + * @param[in] enroll_response The enrollment response structure to free. + * Can be NULL (no operation performed). + * + * @note Developers do not need to call sk_enroll_response_burn() before + * calling this function, as secure clearing is performed automatically. + */ +void sk_enroll_response_free(struct sk_enroll_response *enroll_response); + +/** + * @brief Free an sk_sign_response structure + * + * Frees the memory allocated for a sign response structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] sign_response The sign response structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_sign_response_free(struct sk_sign_response *sign_response); + +/** + * @brief Free an sk_resident_key structure + * + * Frees the memory allocated for a resident key structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] resident_key The resident key structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_resident_key_free(struct sk_resident_key *resident_key); + +/** + * @brief Free an sk_option array and all its contents + * + * Frees a NULL-terminated array of sk_option structures, including all + * allocated memory for option names and values within each structure. + * + * @param[in] options NULL-terminated array of sk_option pointers to free. + * Can be NULL (no operation performed). + * + * @note The options array must be NULL-terminated for proper freeing. + * Each sk_option structure and its name/value strings will be freed. + */ +void sk_options_free(struct sk_option **options); + +/** + * @brief Validate options and extract values for specific keys + * + * Validates that all required options are supported and extracts values + * for the specified keys. This function is primarily intended for use + * by the SK callback implementations. + * + * @param[in] options NULL-terminated array of sk_option pointers to validate. + * @param[in] keys NULL-terminated array of supported option keys. + * @param[out] values Pointer to array that will be allocated and filled with + * copied values (same order as keys). The caller must free + * this array and all contained strings when done. + * + * @return SSH_OK on success, SSH_ERROR if unsupported required options found + * or memory allocation fails. + * + * @note The values array is allocated by this function and contains copies + * of the option values. The caller must free both the array and all + * non-NULL string values within it. Values for keys not found in + * options will be set to NULL. + */ +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values); + +/** + * @brief Duplicate an array of sk_option structures + * + * Creates a deep copy of an array of security key options. Each option + * structure and its string fields are duplicated. + * + * @param[in] options The array of options to duplicate. Must be + * NULL-terminated array of struct sk_option pointers. + * Can be NULL. + * + * @return A newly allocated array of duplicated options on success, + * NULL on failure or if options is NULL. + * The returned array should be freed with SK_OPTIONS_FREE(). + */ +struct sk_option **sk_options_dup(const struct sk_option **options); + +/** + * @brief Check version compatibility of security key callbacks + * + * Validates that the provided security key callbacks use an SK API + * version whose major portion is the same as the major version that libssh + * supports. + * + * @param[in] callbacks Pointer to the sk_callbacks structure to check. + * + * @return true if the callbacks are compatible, false otherwise. + */ +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks); + +/* Convenience macros for secure freeing with NULL checks and pointer reset */ +#define SK_ENROLL_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_enroll_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_SIGN_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_sign_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_RESIDENT_KEY_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_resident_key_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_OPTIONS_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_options_free(x); \ + x = NULL; \ + } \ + } while (0) + +#endif /* SK_COMMON_H */ diff --git a/include/libssh/sk_usbhid.h b/include/libssh/sk_usbhid.h new file mode 100644 index 00000000..025c3daf --- /dev/null +++ b/include/libssh/sk_usbhid.h @@ -0,0 +1,37 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_USBHID_H +#define SK_USBHID_H + +/** + * @brief Get the USB-HID security key callbacks. + * + * This function returns a pointer to the implementation of + * security key callbacks for FIDO2/U2F devices using the USB-HID + * protocol. + * + * @return Pointer to the ssh_sk_callbacks_struct + * + * @see ssh_sk_callbacks_struct + */ +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void); + +#endif /* SK_USBHID_H */ diff --git a/include/libssh/sntrup761.h b/include/libssh/sntrup761.h new file mode 100644 index 00000000..aa05a9fd --- /dev/null +++ b/include/libssh/sntrup761.h @@ -0,0 +1,82 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SNTRUP761_H_ +#define SNTRUP761_H_ + +#include "config.h" +#include "curve25519.h" +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_CURVE25519 +#define HAVE_SNTRUP761 1 +#endif + +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func(void *ctx, size_t length, uint8_t *dst); + +void sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +typedef unsigned char ssh_sntrup761_pubkey[SNTRUP761_PUBLICKEY_SIZE]; +typedef unsigned char ssh_sntrup761_privkey[SNTRUP761_SECRETKEY_SIZE]; +typedef unsigned char ssh_sntrup761_ciphertext[SNTRUP761_CIPHERTEXT_SIZE]; + +int ssh_client_sntrup761x25519_init(ssh_session session); +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_sntrup761x25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SNTRUP761_H_ */ diff --git a/include/libssh/socket.h b/include/libssh/socket.h index cdd3c837..f929c27c 100644 --- a/include/libssh/socket.h +++ b/include/libssh/socket.h @@ -33,14 +33,15 @@ void ssh_socket_cleanup(void); ssh_socket ssh_socket_new(ssh_session session); void ssh_socket_reset(ssh_socket s); void ssh_socket_free(ssh_socket s); -void ssh_socket_set_fd(ssh_socket s, socket_t fd); +int ssh_socket_set_fd(ssh_socket s, socket_t fd); socket_t ssh_socket_get_fd(ssh_socket s); void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p); int ssh_socket_unix(ssh_socket s, const char *path); +#if WITH_EXEC 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 +int ssh_socket_connect_proxyjump(ssh_socket s); void ssh_socket_close(ssh_socket s); int ssh_socket_write(ssh_socket s,const void *buffer, uint32_t len); int ssh_socket_is_open(ssh_socket s); diff --git a/include/libssh/ssh2.h b/include/libssh/ssh2.h index 35214330..dc395438 100644 --- a/include/libssh/ssh2.h +++ b/include/libssh/ssh2.h @@ -16,14 +16,23 @@ #define SSH2_MSG_KEXDH_REPLY 31 #define SSH2_MSG_KEX_ECDH_INIT 30 #define SSH2_MSG_KEX_ECDH_REPLY 31 -#define SSH2_MSG_ECMQV_INIT 30 -#define SSH2_MSG_ECMQV_REPLY 31 +#define SSH2_MSG_KEX_HYBRID_INIT 30 +#define SSH2_MSG_KEX_HYBRID_REPLY 31 #define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 #define SSH2_MSG_KEX_DH_GEX_GROUP 31 #define SSH2_MSG_KEX_DH_GEX_INIT 32 #define SSH2_MSG_KEX_DH_GEX_REPLY 33 #define SSH2_MSG_KEX_DH_GEX_REQUEST 34 + +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 + #define SSH2_MSG_USERAUTH_REQUEST 50 #define SSH2_MSG_USERAUTH_FAILURE 51 #define SSH2_MSG_USERAUTH_SUCCESS 52 diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h index 36589cff..9214a928 100644 --- a/include/libssh/wrapper.h +++ b/include/libssh/wrapper.h @@ -72,33 +72,34 @@ 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, size_t 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); +int md5(const unsigned char *digest, size_t len, unsigned char *hash); SHACTX sha1_init(void); -void sha1_update(SHACTX c, const void *data, size_t len); -void sha1_final(unsigned char *md,SHACTX c); -void sha1(const unsigned char *digest,size_t 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, size_t len); -void sha256_final(unsigned char *md,SHA256CTX c); -void sha256(const unsigned char *digest, size_t 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, size_t len); -void sha384_final(unsigned char *md,SHA384CTX c); -void sha384(const unsigned char *digest, size_t 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, size_t len); -void sha512_final(unsigned char *md,SHA512CTX c); -void sha512(const unsigned char *digest, size_t len, unsigned char *hash); - -void evp(int nid, unsigned char *digest, size_t len, unsigned char *hash, unsigned int *hlen); -EVPCTX evp_init(int nid); -void evp_update(EVPCTX ctx, const void *data, size_t len); -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); +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); @@ -129,6 +130,7 @@ int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, 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); +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote); #endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ #ifdef __cplusplus diff --git a/libssh.pc.cmake b/libssh.pc.cmake index f288b94d..970db5e2 100644 --- a/libssh.pc.cmake +++ b/libssh.pc.cmake @@ -8,3 +8,4 @@ Description: The SSH Library Version: @PROJECT_VERSION@ Libs: -L${libdir} -lssh Cflags: -I${includedir} +Requires.private: @LIBSSH_PC_REQUIRES_PRIVATE@ diff --git a/src/ABI/current b/src/ABI/current index b617d997..91f3b438 100644 --- a/src/ABI/current +++ b/src/ABI/current @@ -1 +1 @@ -4.9.0 \ No newline at end of file +4.11.0 \ No newline at end of file diff --git a/src/ABI/libssh-4.10.0.symbols b/src/ABI/libssh-4.10.0.symbols new file mode 100644 index 00000000..34949837 --- /dev/null +++ b/src/ABI/libssh-4.10.0.symbols @@ -0,0 +1,445 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +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_state +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_pty_size_modes +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_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +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_request_no_more_sessions +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/ABI/libssh-4.10.1.symbols b/src/ABI/libssh-4.10.1.symbols new file mode 100644 index 00000000..34949837 --- /dev/null +++ b/src/ABI/libssh-4.10.1.symbols @@ -0,0 +1,445 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +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_state +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_pty_size_modes +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_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +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_request_no_more_sessions +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/ABI/libssh-4.10.2.symbols b/src/ABI/libssh-4.10.2.symbols new file mode 100644 index 00000000..34949837 --- /dev/null +++ b/src/ABI/libssh-4.10.2.symbols @@ -0,0 +1,445 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +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_state +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_pty_size_modes +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_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +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_request_no_more_sessions +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/ABI/libssh-4.10.3.symbols b/src/ABI/libssh-4.10.3.symbols new file mode 100644 index 00000000..34949837 --- /dev/null +++ b/src/ABI/libssh-4.10.3.symbols @@ -0,0 +1,445 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +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_state +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_pty_size_modes +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_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +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_request_no_more_sessions +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/ABI/libssh-4.10.4.symbols b/src/ABI/libssh-4.10.4.symbols new file mode 100644 index 00000000..34949837 --- /dev/null +++ b/src/ABI/libssh-4.10.4.symbols @@ -0,0 +1,445 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +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_state +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_pty_size_modes +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_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +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_request_no_more_sessions +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/ABI/libssh-4.11.0.symbols b/src/ABI/libssh-4.11.0.symbols new file mode 100644 index 00000000..0b1a917e --- /dev/null +++ b/src/ABI/libssh-4.11.0.symbols @@ -0,0 +1,465 @@ +_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_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +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_expand_path +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_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +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_state +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_pty_size_modes +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_supported_methods +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_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +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_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +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_request_no_more_sessions +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_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +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 +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/ABI/libssh-4.9.1.symbols b/src/ABI/libssh-4.9.1.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.1.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/ABI/libssh-4.9.2.symbols b/src/ABI/libssh-4.9.2.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.2.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/ABI/libssh-4.9.3.symbols b/src/ABI/libssh-4.9.3.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.3.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/ABI/libssh-4.9.4.symbols b/src/ABI/libssh-4.9.4.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.4.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/ABI/libssh-4.9.5.symbols b/src/ABI/libssh-4.9.5.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.5.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/ABI/libssh-4.9.6.symbols b/src/ABI/libssh-4.9.6.symbols new file mode 100644 index 00000000..a26e2c5e --- /dev/null +++ b/src/ABI/libssh-4.9.6.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 24f85dc0..a46f585b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,50 +9,20 @@ set(LIBSSH_LINK_LIBRARIES ${LIBSSH_REQUIRED_LIBRARIES} ) -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 (MBEDTLS_CRYPTO_LIBRARY) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${MBEDTLS_INCLUDE_DIR} - ) - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${MBEDTLS_CRYPTO_LIBRARY} - ) -endif (MBEDTLS_CRYPTO_LIBRARY) +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () -if (GCRYPT_LIBRARIES) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${GCRYPT_INCLUDE_DIR} - ) +if (TARGET MbedTLS::mbedcrypto) + list(APPEND LIBSSH_LINK_LIBRARIES MbedTLS::mbedcrypto) +endif () - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${GCRYPT_LIBRARIES}) -endif() +if (TARGET libgcrypt::libgcrypt) + list(APPEND LIBSSH_LINK_LIBRARIES ${GCRYPT_LIBRARIES}) +endif () if (WITH_ZLIB) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${ZLIB_LIBRARY} - ) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) endif (WITH_ZLIB) if (WITH_GSSAPI AND GSSAPI_FOUND) @@ -86,11 +56,24 @@ if (MINGW AND Threads_FOUND) ) endif() -# This needs to be last for mingw to build +if (HAVE_LIBFIDO2) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${LIBFIDO2_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${LIBFIDO2_LIBRARIES} + ) +endif (HAVE_LIBFIDO2) + +# 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) @@ -116,10 +99,13 @@ set(libssh_SRCS connector.c crypto_common.c curve25519.c + sntrup761.c dh.c ecdh.c error.c getpass.c + gzip.c + hybrid_mlkem.c init.c kdf.c kex.c @@ -130,12 +116,14 @@ set(libssh_SRCS match.c messages.c misc.c + mlkem.c options.c packet.c packet_cb.c packet_crypt.c pcap.c pki.c + pki_context.c pki_container_openssh.c poll.c session.c @@ -143,6 +131,7 @@ set(libssh_SRCS socket.c string.c threads.c + ttyopts.c wrapper.c external/bcrypt_pbkdf.c external/blowfish.c @@ -201,6 +190,20 @@ if (WITH_GCRYPT) chachapoly.c ) endif (NOT HAVE_GCRYPT_CHACHA_POLY) + + if (HAVE_GCRYPT_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_gcrypt.c + ) + endif(HAVE_GCRYPT_CURVE25519) + + if (HAVE_GCRYPT_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_gcrypt.c + ) + endif (HAVE_GCRYPT_MLKEM) elseif (WITH_MBEDTLS) set(libssh_SRCS ${libssh_SRCS} @@ -217,6 +220,7 @@ elseif (WITH_MBEDTLS) external/fe25519.c external/ge25519.c external/sc25519.c + external/sntrup761.c ) if (NOT (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H)) set(libssh_SRCS @@ -226,17 +230,24 @@ elseif (WITH_MBEDTLS) chachapoly.c ) endif() - + if (HAVE_MBEDTLS_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_mbedcrypto.c + ) + endif(HAVE_MBEDTLS_CURVE25519) else (WITH_GCRYPT) set(libssh_SRCS ${libssh_SRCS} threads/libcrypto.c pki_crypto.c ecdh_crypto.c + curve25519_crypto.c getrandom_crypto.c md_crypto.c libcrypto.c dh_crypto.c + external/sntrup761.c ) if (NOT HAVE_OPENSSL_EVP_CHACHA20) set(libssh_SRCS @@ -246,12 +257,20 @@ else (WITH_GCRYPT) chachapoly.c ) endif (NOT HAVE_OPENSSL_EVP_CHACHA20) + if (HAVE_OPENSSL_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_crypto.c + ) + endif (HAVE_OPENSSL_MLKEM) endif (WITH_GCRYPT) if (WITH_SFTP) set(libssh_SRCS ${libssh_SRCS} sftp.c + sftp_common.c + sftp_aio.c ) if (WITH_SERVER) @@ -278,29 +297,52 @@ if (WITH_GEX) ) endif (WITH_GEX) -if (WITH_ZLIB) - set(libssh_SRCS - ${libssh_SRCS} - gzip.c - ) -endif(WITH_ZLIB) - if (WITH_GSSAPI AND GSSAPI_FOUND) set(libssh_SRCS ${libssh_SRCS} gssapi.c + kex-gss.c ) endif (WITH_GSSAPI AND GSSAPI_FOUND) if (NOT WITH_NACL) - if (NOT HAVE_LIBCRYPTO) + if (NOT (HAVE_LIBCRYPTO OR HAVE_MBEDTLS_CURVE25519 OR HAVE_GCRYPT_CURVE25519)) set(libssh_SRCS ${libssh_SRCS} + curve25519_fallback.c external/curve25519_ref.c ) endif() endif (NOT WITH_NACL) +if (NOT HAVE_MLKEM1024) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_native.c + external/libcrux_mlkem768_sha3.c + ) + if (WITH_WERROR_DECLARATION_AFTER_STATEMENT_FLAG) + set_source_files_properties(external/libcrux_mlkem768_sha3.c + PROPERTIES + COMPILE_FLAGS -Wno-error=declaration-after-statement) + endif() +endif() + +if (WITH_FIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_common.c + pki_sk.c + ) + + if (HAVE_LIBFIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_usbhid.c + ) + endif (HAVE_LIBFIDO2) +endif (WITH_FIDO2) + # Set the path to the default map file set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map") @@ -344,6 +386,7 @@ endif () target_include_directories(ssh PUBLIC $ + $ $ PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) @@ -367,6 +410,8 @@ endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) set_target_properties(ssh PROPERTIES + C_STANDARD + 99 VERSION ${LIBRARY_VERSION} SOVERSION @@ -383,6 +428,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 @@ -407,6 +456,7 @@ if (BUILD_STATIC_LIB) target_include_directories(ssh-static PUBLIC $ + $ $ PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) target_link_libraries(ssh-static @@ -434,6 +484,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 1c79c6eb..bb1669c3 100644 --- a/src/agent.c +++ b/src/agent.c @@ -67,87 +67,94 @@ (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ ((x) == SSH2_AGENT_FAILURE)) -static uint32_t atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) { - char *b = buf; - uint32_t pos = 0; - ssize_t res; - ssh_pollfd_t pfd; - ssh_channel channel = agent->channel; - socket_t fd; - - /* Using a socket ? */ - if (channel == NULL) { - fd = ssh_socket_get_fd(agent->sock); - pfd.fd = fd; - pfd.events = do_read ? POLLIN : POLLOUT; - - while (n > pos) { - if (do_read) { - res = recv(fd, b + pos, n - pos, 0); - } else { - res = send(fd, b + pos, n - pos, 0); - } - switch (res) { - case -1: - if (errno == EINTR) { - continue; - } +static uint32_t +atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) +{ + char *b = buf; + uint32_t pos = 0; + ssize_t res; + ssh_pollfd_t pfd; + ssh_channel channel = agent->channel; + socket_t fd; + + /* Using a socket ? */ + if (channel == NULL) { + fd = ssh_socket_get_fd(agent->sock); + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = recv(fd, b + pos, n - pos, 0); + } else { + res = send(fd, b + pos, n - pos, 0); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } #ifdef EWOULDBLOCK - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { #else - if (errno == EAGAIN) { + if (errno == EAGAIN) { #endif - (void) ssh_poll(&pfd, 1, -1); - continue; - } - return 0; - case 0: - /* read returns 0 on end-of-file */ - errno = do_read ? 0 : EPIPE; - return pos; - default: - pos += (uint32_t) res; + (void)ssh_poll(&pfd, 1, -1); + continue; + } + return 0; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (uint32_t)res; + } } - } - return pos; + return pos; } else { - /* using an SSH channel */ - while (n > pos){ - if (do_read) - res = ssh_channel_read(channel,b + pos, n-pos, 0); - else - res = ssh_channel_write(channel, b+pos, n-pos); - if (res == SSH_AGAIN) - continue; - if (res == SSH_ERROR) - return 0; - pos += (uint32_t)res; - } - return pos; + /* using an SSH channel */ + while (n > pos) { + if (do_read) { + res = ssh_channel_read(channel, b + pos, n - pos, 0); + } else { + res = ssh_channel_write(channel, b + pos, n - pos); + } + if (res == SSH_AGAIN) { + continue; + } + if (res == SSH_ERROR) { + return 0; + } + pos += (uint32_t)res; + } + return pos; } } -ssh_agent ssh_agent_new(struct ssh_session_struct *session) { - ssh_agent agent = NULL; +ssh_agent ssh_agent_new(struct ssh_session_struct *session) +{ + ssh_agent agent = NULL; - agent = malloc(sizeof(struct ssh_agent_struct)); - if (agent == NULL) { - return NULL; - } - ZERO_STRUCTP(agent); + agent = calloc(1, sizeof(struct ssh_agent_struct)); + if (agent == NULL) { + return NULL; + } - agent->count = 0; - agent->sock = ssh_socket_new(session); - if (agent->sock == NULL) { - SAFE_FREE(agent); - return NULL; - } - agent->channel = NULL; - return agent; + agent->count = 0; + agent->sock = ssh_socket_new(session); + if (agent->sock == NULL) { + SAFE_FREE(agent); + return NULL; + } + agent->channel = NULL; + return agent; } -static void agent_set_channel(struct ssh_agent_struct *agent, ssh_channel channel){ - agent->channel = channel; +static void agent_set_channel(struct ssh_agent_struct *agent, + ssh_channel channel) +{ + agent->channel = channel; } /** @@ -168,15 +175,19 @@ static void agent_set_channel(struct ssh_agent_struct *agent, ssh_channel channe * @returns SSH_OK in case of success * SSH_ERROR in case of an error */ -int ssh_set_agent_channel(ssh_session session, ssh_channel channel){ - if (!session) - return SSH_ERROR; - if (!session->agent){ - ssh_set_error(session, SSH_REQUEST_DENIED, "Session has no active agent"); - return SSH_ERROR; - } - agent_set_channel(session->agent, channel); - return SSH_OK; +int ssh_set_agent_channel(ssh_session session, ssh_channel channel) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } + agent_set_channel(session->agent, channel); + return SSH_OK; } /** @brief sets the SSH agent socket. @@ -187,64 +198,71 @@ int ssh_set_agent_channel(ssh_session session, ssh_channel channel){ * @returns SSH_OK in case of success * SSH_ERROR in case of an error */ -int ssh_set_agent_socket(ssh_session session, socket_t fd){ - if (!session) - return SSH_ERROR; - if (!session->agent){ - ssh_set_error(session, SSH_REQUEST_DENIED, "Session has no active agent"); - return SSH_ERROR; - } +int ssh_set_agent_socket(ssh_session session, socket_t fd) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } - ssh_socket_set_fd(session->agent->sock, fd); - return SSH_OK; + return ssh_socket_set_fd(session->agent->sock, fd); } /** * @} */ -void ssh_agent_close(struct ssh_agent_struct *agent) { - if (agent == NULL) { - return; - } +void ssh_agent_close(struct ssh_agent_struct *agent) +{ + if (agent == NULL) { + return; + } - ssh_socket_close(agent->sock); + ssh_socket_close(agent->sock); } -void ssh_agent_free(ssh_agent agent) { - if (agent) { - if (agent->ident) { - SSH_BUFFER_FREE(agent->ident); - } - if (agent->sock) { - ssh_agent_close(agent); - ssh_socket_free(agent->sock); +void ssh_agent_free(ssh_agent agent) +{ + if (agent) { + if (agent->ident) { + SSH_BUFFER_FREE(agent->ident); + } + if (agent->sock) { + ssh_agent_close(agent); + ssh_socket_free(agent->sock); + } + SAFE_FREE(agent); } - SAFE_FREE(agent); - } } -static int agent_connect(ssh_session session) { - const char *auth_sock = NULL; +static int agent_connect(ssh_session session) +{ + const char *auth_sock = NULL; - if (session == NULL || session->agent == NULL) { - return -1; - } + if (session == NULL || session->agent == NULL) { + return -1; + } - if (session->agent->channel != NULL) - return 0; + if (session->agent->channel != NULL) { + return 0; + } - auth_sock = session->opts.agent_socket ? - session->opts.agent_socket : 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) { - return -1; + if (auth_sock && *auth_sock) { + if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { + return -1; + } + return 0; } - return 0; - } - return -1; + return -1; } #if 0 @@ -268,61 +286,66 @@ static int agent_decode_reply(struct ssh_session_struct *session, int type) { #endif static int agent_talk(struct ssh_session_struct *session, - struct ssh_buffer_struct *request, struct ssh_buffer_struct *reply) { - uint32_t len = 0; - uint8_t 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: %" 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_TRACE, "atomicio sending request failed: %s", - strerror(errno)); - return -1; - } - } else { - SSH_LOG(SSH_LOG_TRACE, - "atomicio sending request length failed: %s", - ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); - return -1; - } + struct ssh_buffer_struct *request, + struct ssh_buffer_struct *reply) +{ + 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: %" 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_TRACE, + "atomicio sending request failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "atomicio sending request length failed: %s", + 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_TRACE, "atomicio read response length failed: %s", - strerror(errno)); - return -1; - } + /* wait for response, read the length of the response packet */ + if (atomicio(session->agent, payload, 4, 1) != 4) { + SSH_LOG(SSH_LOG_TRACE, + "atomicio read response length failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } - len = PULL_BE_U32(payload, 0); - if (len > 256 * 1024) { - ssh_set_error(session, SSH_FATAL, - "Authentication response too long: %" PRIu32, len); - return -1; - } - SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); + len = PULL_BE_U32(payload, 0); + if (len > 256 * 1024) { + ssh_set_error(session, + SSH_FATAL, + "Authentication response too long: %" PRIu32, + len); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); - payload = ssh_buffer_allocate(reply, len); - if (payload == NULL) { - SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); - return -1; - } + payload = ssh_buffer_allocate(reply, len); + if (payload == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); + return -1; + } - if (atomicio(session->agent, payload, len, 1) != len) { - SSH_LOG(SSH_LOG_DEBUG, - "Error reading response from authentication socket."); - /* Rollback the unused space */ - ssh_buffer_pass_bytes_end(reply, len); - return -1; - } + if (atomicio(session->agent, payload, len, 1) != len) { + SSH_LOG(SSH_LOG_DEBUG, + "Error reading response from authentication socket."); + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(reply, len); + return -1; + } - return 0; + return 0; } uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) @@ -422,8 +445,9 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, /* caller has to free comment */ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, - char **comment) { - struct ssh_key_struct *key; + char **comment) +{ + struct ssh_key_struct *key = NULL; struct ssh_string_struct *blob = NULL; struct ssh_string_struct *tmp = NULL; int rc; @@ -470,35 +494,37 @@ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, return key; } -int ssh_agent_is_running(ssh_session session) { - if (session == NULL || session->agent == NULL) { - return 0; - } +int ssh_agent_is_running(ssh_session session) +{ + if (session == NULL || session->agent == NULL) { + return 0; + } - if (ssh_socket_is_open(session->agent->sock)) { - return 1; - } else { - if (agent_connect(session) < 0) { - return 0; + if (ssh_socket_is_open(session->agent->sock)) { + return 1; } else { - return 1; + if (agent_connect(session) < 0) { + return 0; + } else { + return 1; + } } - } - return 0; + return 0; } ssh_string ssh_agent_sign_data(ssh_session session, const ssh_key pubkey, struct ssh_buffer_struct *data) { - ssh_buffer request; - ssh_buffer reply; - ssh_string key_blob; - ssh_string sig_blob; + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + ssh_string key_blob = NULL; + ssh_string sig_blob = NULL; unsigned int type = 0; unsigned int flags = 0; uint32_t dlen; + size_t request_len; int rc; request = ssh_buffer_new(); @@ -524,11 +550,14 @@ ssh_string ssh_agent_sign_data(ssh_session session, * - 2 x uint32_t * - 1 x ssh_string (uint8_t + data) */ - rc = ssh_buffer_allocate_size(request, - sizeof(uint8_t) * 2 + - sizeof(uint32_t) * 2 + - ssh_string_len(key_blob)); + request_len = sizeof(uint8_t) * 2 + + sizeof(uint32_t) * 2 + + ssh_string_len(key_blob); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_allocate_size(request, (uint32_t)request_len); if (rc < 0) { + SSH_STRING_FREE(key_blob); SSH_BUFFER_FREE(request); return NULL; } diff --git a/src/auth.c b/src/auth.c index c29a7106..8dae696d 100644 --- a/src/auth.c +++ b/src/auth.c @@ -32,19 +32,19 @@ #include #endif -#include "libssh/priv.h" -#include "libssh/crypto.h" -#include "libssh/ssh2.h" -#include "libssh/buffer.h" #include "libssh/agent.h" -#include "libssh/misc.h" -#include "libssh/packet.h" -#include "libssh/session.h" -#include "libssh/keys.h" #include "libssh/auth.h" -#include "libssh/pki.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" #include "libssh/gssapi.h" +#include "libssh/keys.h" #include "libssh/legacy.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" /** * @defgroup libssh_auth The SSH authentication functions @@ -88,6 +88,7 @@ static int ssh_auth_response_termination(void *user) case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: case SSH_AUTH_STATE_GSSAPI_TOKEN: case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: @@ -118,9 +119,14 @@ static const char *ssh_auth_get_current_method(ssh_session session) case SSH_AUTH_METHOD_INTERACTIVE: method = "keyboard interactive"; break; +#ifdef WITH_GSSAPI case SSH_AUTH_METHOD_GSSAPI_MIC: method = "gssapi"; break; + case SSH_AUTH_METHOD_GSSAPI_KEYEX: + method = "gssapi-keyex"; + break; +#endif default: break; } @@ -175,6 +181,7 @@ static int ssh_userauth_get_response(ssh_session session) case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: case SSH_AUTH_STATE_GSSAPI_TOKEN: case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: @@ -195,8 +202,9 @@ static int ssh_userauth_get_response(ssh_session session) * * This banner should be shown to user prior to authentication */ -SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) { - ssh_string banner; +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) +{ + ssh_string banner = NULL; (void)type; (void)user; @@ -268,9 +276,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { if (strstr(auth_methods, "hostbased") != NULL) { session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED; } +#ifdef WITH_GSSAPI if (strstr(auth_methods, "gssapi-with-mic") != NULL) { session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC; } + if (strstr(auth_methods, "gssapi-keyex") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_KEYEX; + } +#endif end: session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; @@ -462,6 +475,110 @@ int ssh_userauth_none(ssh_session session, const char *username) return SSH_AUTH_ERROR; } +/** + * @internal + * + * @brief Adds the server's public key to the authentication request. + * + * This function is used internally when the hostbound public key authentication + * extension is enabled. It export the server's public key and adds it to the + * authentication buffer. + * + * @param[in] session The SSH session. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int add_hostbound_pubkey(ssh_session session) +{ + int rc; + ssh_string server_pubkey_s = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Invalid session or server public key"); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(session->current_crypto->server_pubkey, + &server_pubkey_s); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_s); + if (rc < 0) { + goto error; + } + +error: + SSH_STRING_FREE(server_pubkey_s); + return rc; +} + +/** + * @internal + * + * @brief Build a public key authentication request. + * + * This helper function creates a SSH2_MSG_USERAUTH_REQUEST message for public + * key authentication and adds the server's public key if the hostbound + * extension is enabled. + * + * @param[in] session The SSH session. + * @param[in] username The username, may be NULL. + * @param[in] auth_type Authentication type (0 for key offer, 1 for actual + * auth). + * @param[in] sig_type_c The signature algorithm name. + * @param[in] pubkey_s The public key string. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int build_pubkey_auth_request(ssh_session session, + const char *username, + int has_signature, + const char *sig_type_c, + ssh_string pubkey_s) +{ + int rc; + const char *auth_method = "publickey"; + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + auth_method = "publickey-hostbound-v00@openssh.com"; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, + "bsssbsS", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + auth_method, + has_signature, /* private key? */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); + if (rc < 0) { + return SSH_ERROR; + } + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + rc = add_hostbound_pubkey(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + return SSH_OK; +} + /** * @brief Try to authenticate with the given public key. * @@ -508,29 +625,42 @@ int ssh_userauth_try_publickey(ssh_session session, return SSH_AUTH_ERROR; } - switch(session->pending_call_state) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Wrong state (%d) during pending SSH call", - session->pending_call_state); - return SSH_AUTH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; } /* Check if the given public key algorithm is allowed */ sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); if (sig_type_c == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, + ssh_set_error(session, + SSH_REQUEST_DENIED, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } rc = ssh_key_algorithm_allowed(session, sig_type_c); if (!rc) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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); @@ -538,39 +668,26 @@ int ssh_userauth_try_publickey(ssh_session session, } allowed = ssh_key_size_allowed(session, pubkey); if (!allowed) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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)); + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(pubkey)); return SSH_AUTH_DENIED; } - rc = ssh_userauth_request_service(session); - if (rc == SSH_AGAIN) { - return SSH_AUTH_AGAIN; - } else if (rc == SSH_ERROR) { - return SSH_AUTH_ERROR; - } - /* public key */ rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); if (rc < 0) { goto fail; } - /* request */ - rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 0, /* private key ? */ - sig_type_c, /* algo */ - pubkey_s /* public key */ - ); + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 0, sig_type_c, pubkey_s); if (rc < 0) { goto fail; } - SSH_STRING_FREE(pubkey_s); session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; @@ -639,16 +756,28 @@ int ssh_userauth_publickey(ssh_session session, return SSH_AUTH_ERROR; } - switch(session->pending_call_state) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_PUBKEY: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Bad call during pending SSH call in ssh_userauth_try_publickey"); - return SSH_AUTH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PUBKEY: + goto pending; + default: + ssh_set_error( + session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_publickey"); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; } /* Cert auth requires presenting the cert type name (*-cert@openssh.com) */ @@ -657,13 +786,15 @@ int ssh_userauth_publickey(ssh_session session, /* Check if the given public key algorithm is allowed */ sig_type_c = ssh_key_get_signature_algorithm(session, key_type); if (sig_type_c == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, + ssh_set_error(session, + SSH_REQUEST_DENIED, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } rc = ssh_key_algorithm_allowed(session, sig_type_c); if (!rc) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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); @@ -671,35 +802,23 @@ int ssh_userauth_publickey(ssh_session session, } allowed = ssh_key_size_allowed(session, privkey); if (!allowed) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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)); + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(privkey)); return SSH_AUTH_DENIED; } - rc = ssh_userauth_request_service(session); - if (rc == SSH_AGAIN) { - return SSH_AUTH_AGAIN; - } else if (rc == SSH_ERROR) { - return SSH_AUTH_ERROR; - } - /* get public key or cert */ rc = ssh_pki_export_pubkey_blob(privkey, &str); if (rc < 0) { goto fail; } - /* request */ - rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 1, /* private key */ - sig_type_c, /* algo */ - str /* public key or cert */ - ); + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, str); if (rc < 0) { goto fail; } @@ -754,18 +873,23 @@ static int ssh_userauth_agent_publickey(ssh_session session, 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; } + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { return SSH_AUTH_AGAIN; @@ -782,14 +906,16 @@ static int ssh_userauth_agent_publickey(ssh_session session, /* Check if the given public key algorithm is allowed */ sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); if (sig_type_c == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, + ssh_set_error(session, + SSH_REQUEST_DENIED, "Invalid key type (unknown)"); SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } rc = ssh_key_algorithm_allowed(session, sig_type_c); if (!rc) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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); @@ -798,27 +924,21 @@ static int ssh_userauth_agent_publickey(ssh_session session, } allowed = ssh_key_size_allowed(session, pubkey); if (!allowed) { - ssh_set_error(session, SSH_REQUEST_DENIED, + 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)); + "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 */ - ); - SSH_STRING_FREE(pubkey_s); + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, pubkey_s); if (rc < 0) { goto fail; } + SSH_STRING_FREE(pubkey_s); /* sign the buffer with the private key */ sig_blob = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey); @@ -858,6 +978,7 @@ static int ssh_userauth_agent_publickey(ssh_session session, enum ssh_agent_state_e { SSH_AGENT_STATE_NONE = 0, SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, SSH_AGENT_STATE_AUTH }; @@ -875,7 +996,7 @@ void ssh_agent_state_free(void *data) if (state) { SSH_STRING_FREE_CHAR(state->comment); ssh_key_free(state->pubkey); - free (state); + free(state); } } @@ -901,13 +1022,14 @@ void ssh_agent_state_free(void *data) * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. */ -int ssh_userauth_agent(ssh_session session, - const char *username) +int ssh_userauth_agent(ssh_session session, 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) { @@ -919,13 +1041,12 @@ int ssh_userauth_agent(ssh_session session, } if (!session->agent_state) { - session->agent_state = malloc(sizeof(struct ssh_agent_state_struct)); + session->agent_state = calloc(1, sizeof(struct ssh_agent_state_struct)); if (!session->agent_state) { ssh_set_error_oom(session); return SSH_AUTH_ERROR; } - ZERO_STRUCTP(session->agent_state); - session->agent_state->state=SSH_AGENT_STATE_NONE; + session->agent_state->state = SSH_AGENT_STATE_NONE; } state = session->agent_state; @@ -943,22 +1064,29 @@ int ssh_userauth_agent(ssh_session session, * 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(configKeys[0])); - if (!configKeys) { + 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. */ - ssh_key pubkey = NULL; rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); if (rc == SSH_OK) { configKeys[configKeysCount++] = pubkey; @@ -966,9 +1094,7 @@ int ssh_userauth_agent(ssh_session session, char *pubkeyFile = NULL; size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); - if (pubkey) { - SSH_KEY_FREE(pubkey); - } + SSH_KEY_FREE(pubkey); /* * If we couldn't get the public key from the private key file, @@ -982,13 +1108,47 @@ int ssh_userauth_agent(ssh_session session, } 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); } - free(pubkeyFile); } + /* 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; } } @@ -996,13 +1156,26 @@ int ssh_userauth_agent(ssh_session session, 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++) { - if (ssh_key_cmp(state->pubkey, configKeys[i], - SSH_KEY_CMP_PUBLIC) == 0) { + 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; } @@ -1011,7 +1184,8 @@ int ssh_userauth_agent(ssh_session session, if (!found_key) { SSH_LOG(SSH_LOG_DEBUG, "Identities only is enabled and identity %s was " - "not listed in config, skipping", state->comment); + "not listed in config, skipping", + state->comment); SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; SSH_KEY_FREE(state->pubkey); @@ -1026,28 +1200,48 @@ int ssh_userauth_agent(ssh_session session, } } 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; goto done; } else if (rc == SSH_AUTH_AGAIN) { - state->state = SSH_AGENT_STATE_PUBKEY; + 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); + 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) { @@ -1058,14 +1252,15 @@ int ssh_userauth_agent(ssh_session session, 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; goto done; } else if (rc != SSH_AUTH_SUCCESS) { 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); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } @@ -1083,6 +1278,10 @@ int ssh_userauth_agent(ssh_session session, ssh_key_free(configKeys[i]); } free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); return rc; } @@ -1090,6 +1289,8 @@ 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 }; @@ -1098,6 +1299,8 @@ struct ssh_auth_auto_state_struct { struct ssh_iterator *it; ssh_key privkey; ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; }; /** @@ -1132,7 +1335,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; } @@ -1176,6 +1380,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, @@ -1183,12 +1390,17 @@ 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) { return SSH_AUTH_ERROR; } + + SSH_LOG(SSH_LOG_INFO, + "Starting authentication as a user %s", + username ? username : session->opts.username); + if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) { session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY; return SSH_AUTH_DENIED; @@ -1214,7 +1426,7 @@ int ssh_userauth_publickey_auto(ssh_session session, 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; } state->state = SSH_AUTH_AUTO_STATE_PUBKEY; @@ -1229,7 +1441,9 @@ 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; @@ -1242,50 +1456,56 @@ 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 #endif /* WITH_PKCS11_URI */ { - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + 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; } @@ -1299,29 +1519,102 @@ int ssh_userauth_publickey_auto(ssh_session session, } 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_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; } @@ -1331,24 +1624,24 @@ 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_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); @@ -1357,16 +1650,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_DEBUG, - "Successfully authenticated using %s", - privkey_file); + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); } return rc; } @@ -1374,8 +1684,9 @@ 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_DEBUG, "The server accepted the public key but refused the signature"); @@ -1491,7 +1802,7 @@ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, ssh_public_key publickey) { - ssh_key key; + ssh_key key = NULL; int rc; key = ssh_key_new(); @@ -1502,18 +1813,22 @@ 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; -#ifndef HAVE_LIBCRYPTO - key->rsa = publickey->rsa_pub; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = publickey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) key->key = publickey->key_pub; +#else + key->rsa = publickey->rsa_pub; #endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_agent_publickey(session, username, key); -#ifndef HAVE_LIBCRYPTO - key->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) key->key = NULL; +#else + key->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -1549,7 +1864,7 @@ void ssh_kbdint_free(ssh_kbdint kbd) if (kbd->prompts) { for (i = 0; i < n; i++) { if (kbd->prompts[i] != NULL) { - explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i])); + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); } SAFE_FREE(kbd->prompts[i]); } @@ -1560,7 +1875,7 @@ void ssh_kbdint_free(ssh_kbdint kbd) if (kbd->answers) { for (i = 0; i < n; i++) { if (kbd->answers[i] != NULL) { - explicit_bzero(kbd->answers[i], strlen(kbd->answers[i])); + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); } SAFE_FREE(kbd->answers[i]); } @@ -1585,7 +1900,7 @@ void ssh_kbdint_clean(ssh_kbdint kbd) n = kbd->nprompts; if (kbd->prompts) { for (i = 0; i < n; i++) { - explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i])); + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); SAFE_FREE(kbd->prompts[i]); } SAFE_FREE(kbd->prompts); @@ -1595,7 +1910,7 @@ void ssh_kbdint_clean(ssh_kbdint kbd) if (kbd->answers) { for (i = 0; i < n; i++) { - explicit_bzero(kbd->answers[i], strlen(kbd->answers[i])); + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); SAFE_FREE(kbd->answers[i]); } SAFE_FREE(kbd->answers); @@ -2019,7 +2334,8 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) * * @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) { @@ -2075,8 +2391,8 @@ ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, } if (session->kbdint->answers[i]) { - explicit_bzero(session->kbdint->answers[i], - strlen(session->kbdint->answers[i])); + ssh_burn(session->kbdint->answers[i], + strlen(session->kbdint->answers[i])); SAFE_FREE(session->kbdint->answers[i]); } @@ -2149,4 +2465,111 @@ int ssh_userauth_gssapi(ssh_session session) return rc; } +/** + * @brief Try to authenticate through the "gssapi-keyex" method. + * + * @param[in] session The ssh session to use. + * + * @returns + * - `SSH_AUTH_ERROR`: A serious error happened. + * - `SSH_AUTH_DENIED`: Authentication failed : use another method. + * - `SSH_AUTH_PARTIAL`: You've been partially authenticated, you still + * have to use another method. + * - `SSH_AUTH_SUCCESS`: Authentication success. + * - `SSH_AUTH_AGAIN`: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi_keyex(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + OM_uint32 min_stat; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + /* Check if GSSAPI Key exchange was performed */ + if (!ssh_kex_is_gss(session->current_crypto)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key exchange."); + return SSH_ERROR; + } + + if (session->gssapi == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-keyex"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_KEYEX; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX; + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_gssapi_auth_keyex_mic(session, &mic_token_buf); + if (rc != SSH_OK) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsssdP", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-keyex", + mic_token_buf.length, + (size_t)mic_token_buf.length, + mic_token_buf.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + gss_release_buffer(&min_stat, &mic_token_buf); + return rc; + } + + gss_release_buffer(&min_stat, &mic_token_buf); + + session->auth.state = SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT; + + ssh_packet_send(session); + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void)session; /* unused */ +#endif + return rc; +} + /** @} */ diff --git a/src/base64.c b/src/base64.c index 4148f49c..73dd0f77 100644 --- a/src/base64.c +++ b/src/base64.c @@ -29,6 +29,9 @@ #include "libssh/priv.h" #include "libssh/buffer.h" +/* Do not allow encoding more than 256MB of data */ +#define BASE64_MAX_INPUT_LEN 256 * 1024 * 1024 + static const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" @@ -57,119 +60,120 @@ static int get_equals(char *string); * @returns A buffer containing the decoded string, NULL if something went * wrong (e.g. incorrect char). */ -ssh_buffer base64_to_bin(const char *source) { - ssh_buffer buffer = NULL; - unsigned char block[3]; - char *base64; - char *ptr; - size_t len; - int equals; - - base64 = strdup(source); - if (base64 == NULL) { - return NULL; - } - ptr = base64; +ssh_buffer base64_to_bin(const char *source) +{ + ssh_buffer buffer = NULL; + unsigned char block[3]; + char *base64 = NULL; + char *ptr = NULL; + size_t len; + int equals; + + base64 = strdup(source); + if (base64 == NULL) { + return NULL; + } + ptr = base64; - /* Get the number of equals signs, which mirrors the padding */ - equals = get_equals(ptr); - if (equals > 2) { - SAFE_FREE(base64); - return NULL; - } + /* Get the number of equals signs, which mirrors the padding */ + equals = get_equals(ptr); + if (equals > 2) { + SAFE_FREE(base64); + return NULL; + } - buffer = ssh_buffer_new(); - if (buffer == NULL) { - SAFE_FREE(base64); - return NULL; - } - /* - * The base64 buffer often contains sensitive data. Make sure we don't leak - * sensitive data - */ - ssh_buffer_set_secure(buffer); - - len = strlen(ptr); - while (len > 4) { - if (_base64_to_bin(block, ptr, 3) < 0) { - goto error; + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SAFE_FREE(base64); + return NULL; } - if (ssh_buffer_add_data(buffer, block, 3) < 0) { - goto error; + /* + * The base64 buffer often contains sensitive data. Make sure we don't leak + * sensitive data + */ + ssh_buffer_set_secure(buffer); + + len = strlen(ptr); + while (len > 4) { + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + len -= 4; + ptr += 4; } - len -= 4; - ptr += 4; - } - - /* - * Depending on the number of bytes resting, there are 3 possibilities - * from the RFC. - */ - switch (len) { + + /* + * Depending on the number of bytes resting, there are 3 possibilities + * from the RFC. + */ + switch (len) { /* * (1) The final quantum of encoding input is an integral multiple of * 24 bits. Here, the final unit of encoded output will be an integral * multiple of 4 characters with no "=" padding */ case 4: - if (equals != 0) { - goto error; - } - if (_base64_to_bin(block, ptr, 3) < 0) { - goto error; - } - if (ssh_buffer_add_data(buffer, block, 3) < 0) { - goto error; - } - SAFE_FREE(base64); + if (equals != 0) { + goto error; + } + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + SAFE_FREE(base64); - return buffer; + return buffer; /* * (2) The final quantum of encoding input is exactly 8 bits; here, the * final unit of encoded output will be two characters followed by * two "=" padding characters. */ case 2: - if (equals != 2){ - goto error; - } + if (equals != 2) { + goto error; + } - if (_base64_to_bin(block, ptr, 1) < 0) { - goto error; - } - if (ssh_buffer_add_data(buffer, block, 1) < 0) { - goto error; - } - SAFE_FREE(base64); + if (_base64_to_bin(block, ptr, 1) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 1) < 0) { + goto error; + } + SAFE_FREE(base64); - return buffer; + return buffer; /* * The final quantum of encoding input is exactly 16 bits. Here, the final * unit of encoded output will be three characters followed by one "=" * padding character. */ case 3: - if (equals != 1) { - goto error; - } - if (_base64_to_bin(block, ptr, 2) < 0) { - goto error; - } - if (ssh_buffer_add_data(buffer,block,2) < 0) { - goto error; - } - SAFE_FREE(base64); + if (equals != 1) { + goto error; + } + if (_base64_to_bin(block, ptr, 2) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 2) < 0) { + goto error; + } + SAFE_FREE(base64); - return buffer; + return buffer; default: - /* 4,3,2 are the only padding size allowed */ - goto error; - } + /* 4,3,2 are the only padding size allowed */ + goto error; + } error: - SAFE_FREE(base64); - SSH_BUFFER_FREE(buffer); - return NULL; + SAFE_FREE(base64); + SSH_BUFFER_FREE(buffer); + return NULL; } #define BLOCK(letter, n) do {ptr = strchr((const char *)alphabet, source[n]); \ @@ -179,59 +183,62 @@ ssh_buffer base64_to_bin(const char *source) { } while(0) /* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */ -static int to_block4(unsigned long *block, const char *source, int num) { - const char *ptr = NULL; - unsigned int i; +static int to_block4(unsigned long *block, const char *source, int num) +{ + const char *ptr = NULL; + size_t i; - *block = 0; - if (num < 1) { - return 0; - } + *block = 0; + if (num < 1) { + return 0; + } - BLOCK(A, 0); /* 6 bit */ - BLOCK(B,1); /* 12 bit */ + BLOCK(A, 0); /* 6 bit */ + BLOCK(B, 1); /* 12 bit */ - if (num < 2) { - return 0; - } + if (num < 2) { + return 0; + } - BLOCK(C, 2); /* 18 bit */ + BLOCK(C, 2); /* 18 bit */ - if (num < 3) { - return 0; - } + if (num < 3) { + return 0; + } - BLOCK(D, 3); /* 24 bit */ + BLOCK(D, 3); /* 24 bit */ - return 0; + return 0; } /* num = numbers of final bytes to be decoded */ -static int _base64_to_bin(unsigned char dest[3], const char *source, int num) { - unsigned long block; +static int _base64_to_bin(unsigned char dest[3], const char *source, int num) +{ + unsigned long block; - if (to_block4(&block, source, num) < 0) { - return -1; - } - dest[0] = GET_A(block); - dest[1] = GET_B(block); - dest[2] = GET_C(block); + if (to_block4(&block, source, num) < 0) { + return -1; + } + dest[0] = GET_A(block); + dest[1] = GET_B(block); + dest[2] = GET_C(block); - return 0; + return 0; } /* Count the number of "=" signs and replace them by zeroes */ -static int get_equals(char *string) { - char *ptr = string; - int num = 0; +static int get_equals(char *string) +{ + char *ptr = string; + int num = 0; - while ((ptr=strchr(ptr,'=')) != NULL) { - num++; - *ptr = '\0'; - ptr++; - } + while ((ptr = strchr(ptr, '=')) != NULL) { + num++; + *ptr = '\0'; + ptr++; + } - return num; + return num; } /* thanks sysk for debugging my mess :) */ @@ -274,7 +281,15 @@ uint8_t *bin_to_base64(const uint8_t *source, size_t len) { uint8_t *base64 = NULL; uint8_t *ptr = NULL; - size_t flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ + size_t flen = 0; + + /* Set the artificial upper limit for the input. Otherwise on 32b arch, the + * following line could overflow for sizes larger than SIZE_MAX / 4 */ + if (len > BASE64_MAX_INPUT_LEN) { + return NULL; + } + + flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ flen = (4 * flen) / 3 + 1; base64 = malloc(flen); @@ -283,7 +298,7 @@ uint8_t *bin_to_base64(const uint8_t *source, size_t len) } ptr = base64; - while(len > 0){ + while (len > 0) { _bin_to_base64(ptr, source, len > 3 ? 3 : len); ptr += 4; if (len < 3) { diff --git a/src/bignum.c b/src/bignum.c index bee55d67..b18c1162 100644 --- a/src/bignum.c +++ b/src/bignum.c @@ -27,40 +27,52 @@ #include "libssh/bignum.h" #include "libssh/string.h" -ssh_string ssh_make_bignum_string(bignum num) { - ssh_string ptr = NULL; - size_t pad = 0; - size_t len = bignum_num_bytes(num); - size_t bits = bignum_num_bits(num); - - if (len == 0) { - return NULL; - } - - /* If the first bit is set we have a negative number */ - if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { - pad++; - } +static ssh_string make_bignum_string(bignum num, size_t pad_to_len) +{ + ssh_string ptr = NULL; + size_t pad = 0; + size_t len = bignum_num_bytes(num); + size_t bits = bignum_num_bits(num); + + if (pad_to_len == 0) { + /* If the first bit is set we have a negative number */ + if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { + pad++; + } + } else { + if (len > pad_to_len) { + return NULL; + } + pad = pad_to_len - len; + } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_TRACE, - "%zu bits, %zu bytes, %zu padding", - bits, len, pad); + SSH_LOG(SSH_LOG_TRACE, "%zu bits, %zu bytes, %zu padding", bits, len, pad); #endif /* DEBUG_CRYPTO */ - ptr = ssh_string_new(len + pad); - if (ptr == NULL) { - return NULL; - } + ptr = ssh_string_new(len + pad); + if (ptr == NULL) { + return NULL; + } + + /* We have a negative number so we need a leading zero */ + if (pad) { + memset(ptr->data, 0, pad); + } + + bignum_bn2bin(num, len, ptr->data + pad); - /* We have a negative number so we need a leading zero */ - if (pad) { - ptr->data[0] = 0; - } + return ptr; +} - bignum_bn2bin(num, len, ptr->data + pad); +ssh_string ssh_make_bignum_string(bignum num) +{ + return make_bignum_string(num, 0); +} - return ptr; +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len) +{ + return make_bignum_string(num, pad_len); } bignum ssh_make_string_bn(ssh_string string) @@ -70,11 +82,12 @@ bignum ssh_make_string_bn(ssh_string string) #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "Importing a %zu bits, %zu bytes object ...\n", - len * 8, len); + "Importing a %zu bits, %zu bytes object ...", + len * 8, + len); #endif /* DEBUG_CRYPTO */ - bignum_bin2bn(string->data, len, &bn); + bignum_bin2bn(string->data, (int)len, &bn); return bn; } @@ -86,7 +99,9 @@ void ssh_print_bignum(const char *name, const_bignum num) if (num != NULL) { bignum_bn2hex(num, &hex); } - SSH_LOG(SSH_LOG_DEBUG, "%s value: %s", name, + SSH_LOG(SSH_LOG_DEBUG, + "%s value: %s", + name, (hex == NULL) ? "(null)" : (char *)hex); ssh_crypto_free(hex); } diff --git a/src/bind.c b/src/bind.c index d150933c..97feaac2 100644 --- a/src/bind.c +++ b/src/bind.c @@ -74,7 +74,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, int port) { char port_c[6]; - struct addrinfo *ai; + struct addrinfo *ai = NULL; struct addrinfo hints; int opt = 1; socket_t s; @@ -132,8 +132,9 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, return s; } -ssh_bind ssh_bind_new(void) { - ssh_bind ptr; +ssh_bind ssh_bind_new(void) +{ + ssh_bind ptr = NULL; ptr = calloc(1, sizeof(struct ssh_bind_struct)); if (ptr == NULL) { @@ -149,14 +150,6 @@ ssh_bind ssh_bind_new(void) { static int ssh_bind_import_keys(ssh_bind sshbind) { int rc; - if (sshbind->ecdsakey == NULL && - sshbind->rsakey == NULL && - sshbind->ed25519key == NULL) { - ssh_set_error(sshbind, SSH_FATAL, - "ECDSA, ED25519, 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, @@ -225,90 +218,110 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { return SSH_OK; } -int ssh_bind_listen(ssh_bind sshbind) { - const char *host; - socket_t fd; - int rc; +int ssh_bind_listen(ssh_bind sshbind) +{ + const char *host = NULL; + socket_t fd; + int rc; - if (sshbind->rsa == 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->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) { - 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); - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); + } + } - sshbind->bindfd = fd; - } else { - SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); - } - return 0; + 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_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 @@ -336,20 +349,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){ @@ -374,6 +391,7 @@ void ssh_bind_free(ssh_bind sshbind){ SAFE_FREE(sshbind->rsakey); SAFE_FREE(sshbind->ecdsakey); SAFE_FREE(sshbind->ed25519key); + SAFE_FREE(sshbind->gssapi_key_exchange_algs); ssh_key_free(sshbind->rsa); sshbind->rsa = NULL; @@ -405,13 +423,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 */ @@ -442,7 +453,7 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) return SSH_ERROR; } } else { - char *p; + char *p = NULL; /* If something was set to the session prior to calling this * function, keep only what is allowed by the options set in * sshbind */ @@ -458,18 +469,29 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) } session->common.log_verbosity = sshbind->common.log_verbosity; + session->opts.gssapi_key_exchange = sshbind->gssapi_key_exchange; + + if (sshbind->gssapi_key_exchange_algs != NULL) { + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = + strdup(sshbind->gssapi_key_exchange_algs); + if (session->opts.gssapi_key_exchange_algs == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } if (sshbind->banner != NULL) { - session->opts.custombanner = strdup(sshbind->banner); - if (session->opts.custombanner == NULL) { + session->server_opts.custombanner = strdup(sshbind->banner); + if (session->server_opts.custombanner == NULL) { ssh_set_error_oom(sshbind); return SSH_ERROR; } } if (sshbind->moduli_file != NULL) { - session->opts.moduli_file = strdup(sshbind->moduli_file); - if (session->opts.moduli_file == NULL) { + session->server_opts.moduli_file = strdup(sshbind->moduli_file); + if (session->server_opts.moduli_file == NULL) { ssh_set_error_oom(sshbind); return SSH_ERROR; } @@ -484,7 +506,10 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) ssh_set_error_oom(sshbind); return SSH_ERROR; } - ssh_socket_set_fd(session->socket, fd); + rc = ssh_socket_set_fd(session->socket, fd); + if (rc != SSH_OK) { + return rc; + } handle = ssh_socket_get_poll_handle(session->socket); if (handle == NULL) { ssh_set_error_oom(sshbind); @@ -501,8 +526,13 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) sshbind->ecdsa == NULL && sshbind->ed25519 == NULL) { rc = ssh_bind_import_keys(sshbind); - if (rc != SSH_OK) { - return SSH_ERROR; + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); } } diff --git a/src/bind_config.c b/src/bind_config.c index a4c7a8d7..4f2b1c9b 100644 --- a/src/bind_config.c +++ b/src/bind_config.c @@ -104,6 +104,11 @@ ssh_bind_config_keyword_table[] = { .opcode = BIND_CFG_HOSTKEY_ALGORITHMS, .allowed_in_match = true }, + { + .name = "requiredrsasize", + .opcode = BIND_CFG_REQUIRED_RSA_SIZE, + .allowed_in_match = true + }, { .opcode = BIND_CFG_UNKNOWN, } @@ -200,7 +205,7 @@ local_parse_file(ssh_bind bind, uint8_t *seen, unsigned int depth) { - FILE *f; + FILE *f = NULL; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; @@ -212,7 +217,7 @@ local_parse_file(ssh_bind bind, return; } - f = fopen(filename, "r"); + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", filename); @@ -242,9 +247,14 @@ static void local_parse_glob(ssh_bind bind, uint8_t *seen, unsigned int depth) { +#ifdef __FILC__ + /* FilC's libc provides glob_t without the non-standard gl_flags member. */ + glob_t globbuf = {0}; +#else glob_t globbuf = { .gl_flags = 0, }; +#endif int rt; u_int i; @@ -293,6 +303,7 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *p = NULL; char *s = NULL, *x = NULL; char *keyword = NULL; + long l; size_t len; int rc = 0; @@ -594,6 +605,19 @@ ssh_bind_config_parse_line(ssh_bind bind, } } break; + case BIND_CFG_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + if (l >= 0 && l <= INT_MAX && (*parser_flags & PARSING)) { + int i = (int)l; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &i); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set RequiredRSASize value '%ld'", + count, + l); + } + } + break; case BIND_CFG_NOT_ALLOWED_IN_MATCH: SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d", keyword, count); @@ -626,7 +650,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) { char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; - FILE *f; + FILE *f = NULL; uint32_t parser_flags; int rv; @@ -636,7 +660,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) * option to be redefined later by another file. */ uint8_t seen[BIND_CFG_MAX] = {0}; - f = fopen(filename, "r"); + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); if (f == NULL) { return 0; } @@ -669,7 +693,8 @@ 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; + unsigned int line_num = 0; + size_t line_len; uint32_t parser_flags; int rv; @@ -698,8 +723,10 @@ int ssh_bind_config_parse_string(ssh_bind bind, const char *input) } 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); + SSH_LOG(SSH_LOG_WARN, + "Line %u too long: %zu characters", + line_num, + line_len); return SSH_ERROR; } memcpy(line, line_start, line_len); diff --git a/src/buffer.c b/src/buffer.c index 299eac54..60119175 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -156,10 +156,10 @@ void ssh_buffer_free(struct ssh_buffer_struct *buffer) if (buffer->secure && buffer->allocated > 0) { /* burn the data */ - explicit_bzero(buffer->data, buffer->allocated); + ssh_burn(buffer->data, buffer->allocated); SAFE_FREE(buffer->data); - explicit_bzero(buffer, sizeof(struct ssh_buffer_struct)); + ssh_burn(buffer, sizeof(struct ssh_buffer_struct)); } else { SAFE_FREE(buffer->data); } @@ -205,7 +205,7 @@ static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) return -1; } memcpy(new, buffer->data, buffer->used); - explicit_bzero(buffer->data, buffer->used); + ssh_burn(buffer->data, buffer->used); SAFE_FREE(buffer->data); } else { new = realloc(buffer->data, needed); @@ -241,7 +241,7 @@ static void buffer_shift(ssh_buffer buffer) if (buffer->secure) { void *ptr = buffer->data + buffer->used; - explicit_bzero(ptr, burn_pos); + ssh_burn(ptr, burn_pos); } buffer_verify(buffer); @@ -266,7 +266,7 @@ int ssh_buffer_reinit(struct ssh_buffer_struct *buffer) buffer_verify(buffer); if (buffer->secure && buffer->allocated > 0) { - explicit_bzero(buffer->data, buffer->allocated); + ssh_burn(buffer->data, buffer->allocated); } buffer->used = 0; buffer->pos = 0; @@ -371,7 +371,8 @@ int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, */ void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len) { - void *ptr; + void *ptr = NULL; + buffer_verify(buffer); if (buffer->used + len < len) { @@ -406,20 +407,26 @@ void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len) * * @return 0 on success, < 0 on error. */ -int ssh_buffer_add_ssh_string(struct ssh_buffer_struct *buffer, - struct ssh_string_struct *string) { - uint32_t len = 0; +int +ssh_buffer_add_ssh_string(struct ssh_buffer_struct *buffer, + struct ssh_string_struct *string) +{ + size_t len; + int rc; - if (string == NULL) { - return -1; - } + if (string == NULL) { + return -1; + } - len = ssh_string_len(string); - if (ssh_buffer_add_data(buffer, string, len + sizeof(uint32_t)) < 0) { - return -1; - } + len = ssh_string_len(string) + sizeof(uint32_t); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_add_data(buffer, string, (uint32_t)len); + if (rc < 0) { + return -1; + } - return 0; + return 0; } /** @@ -610,6 +617,53 @@ uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){ return buffer->used - buffer->pos; } +/** + * @internal + * + * @brief Duplicate an existing buffer. + * + * Creates a new ssh_buffer and copies all data from the source buffer. + * The new buffer preserves the secure flag setting of the source. + * + * @param[in] buffer The buffer to duplicate. Can be NULL. + * + * @return A new buffer containing a copy of the data on success, + * NULL on failure or if buffer is NULL. + * + * @see ssh_buffer_free() + */ +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer) +{ + ssh_buffer new_buffer = NULL; + int rc; + + if (buffer == NULL) { + return NULL; + } + + buffer_verify(buffer); + + new_buffer = ssh_buffer_new(); + if (new_buffer == NULL) { + return NULL; + } + + new_buffer->secure = buffer->secure; + + if (ssh_buffer_get_len(buffer) > 0) { + rc = ssh_buffer_add_data(new_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc != SSH_OK) { + ssh_buffer_free(new_buffer); + return NULL; + } + } + + buffer_verify(new_buffer); + return new_buffer; +} + /** * @internal * @@ -822,6 +876,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, const char *p = NULL; ssh_string string = NULL; char *cstring = NULL; + bignum b = NULL; size_t needed_size = 0; size_t len; size_t count; @@ -852,7 +907,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, break; case 'S': string = va_arg(ap, ssh_string); - needed_size += 4 + ssh_string_len(string); + needed_size += sizeof(uint32_t) + ssh_string_len(string); string = NULL; break; case 's': @@ -866,13 +921,18 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, va_arg(ap, void *); count++; /* increase argument count */ break; + case 'F': case 'B': - va_arg(ap, bignum); - /* - * Use a fixed size for a bignum - * (they should normally be around 32) - */ - needed_size += 64; + b = va_arg(ap, bignum); + if (*p == 'F') { + /* For padded bignum, we know the exact length */ + len = va_arg(ap, size_t); + count++; /* increase argument count */ + needed_size += sizeof(uint32_t) + len; + } else { + /* The bignum bytes + 1 for possible padding */ + needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1; + } break; case 't': cstring = va_arg(ap, char *); @@ -919,13 +979,14 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, * SSH_ERROR on error * @see ssh_buffer_add_format() for format list values. */ -int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, - const char *format, - size_t argc, - va_list ap) +static int +ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) { int rc = SSH_ERROR; - const char *p; + const char *p = NULL; union { uint8_t byte; uint16_t word; @@ -934,7 +995,7 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, ssh_string string; void *data; } o; - char *cstring; + char *cstring = NULL; bignum b; size_t len; size_t count; @@ -977,24 +1038,40 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, case 's': cstring = va_arg(ap, char *); len = strlen(cstring); - rc = ssh_buffer_add_u32(buffer, htonl(len)); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + o.dword = (uint32_t)len; + rc = ssh_buffer_add_u32(buffer, htonl(o.dword)); if (rc == SSH_OK){ - rc = ssh_buffer_add_data(buffer, cstring, len); + rc = ssh_buffer_add_data(buffer, cstring, o.dword); } cstring = NULL; break; case 'P': len = va_arg(ap, size_t); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } o.data = va_arg(ap, void *); count++; /* increase argument count */ - rc = ssh_buffer_add_data(buffer, o.data, len); + rc = ssh_buffer_add_data(buffer, o.data, (uint32_t)len); o.data = NULL; break; + case 'F': case 'B': b = va_arg(ap, bignum); - o.string = ssh_make_bignum_string(b); + if (*p == 'F') { + len = va_arg(ap, size_t); + count++; /* increase argument count */ + o.string = ssh_make_padded_bignum_string(b, len); + } else { + o.string = ssh_make_bignum_string(b); + } if(o.string == NULL){ rc = SSH_ERROR; break; @@ -1005,7 +1082,11 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, case 't': cstring = va_arg(ap, char *); len = strlen(cstring); - rc = ssh_buffer_add_data(buffer, cstring, len); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_data(buffer, cstring, (uint32_t)len); cstring = NULL; break; default: @@ -1046,6 +1127,8 @@ int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, * 'P': size_t, void * (len of data, pointer to data) * only pushes data. * 'B': bignum (pushed as SSH string) + * 'F': bignum, size_t (bignum, padded to fixed length, + * pushed as SSH string) * @returns SSH_OK on success * SSH_ERROR on error * @warning when using 'P' with a constant size (e.g. 8), do not @@ -1093,7 +1176,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, va_list ap) { int rc = SSH_ERROR; - const char *p = format, *last; + const char *p = format, *last = NULL; union { uint8_t *byte; uint16_t *word; @@ -1185,28 +1268,28 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, if (rlen != 4){ break; } - len = ntohl(u32len); - if (len > max_len - 1) { + u32len = ntohl(u32len); + if (u32len > max_len - 1) { break; } - rc = ssh_buffer_validate_length(buffer, len); + rc = ssh_buffer_validate_length(buffer, u32len); if (rc != SSH_OK) { break; } - *o.cstring = malloc(len + 1); + *o.cstring = malloc(u32len + 1); if (*o.cstring == NULL){ rc = SSH_ERROR; break; } - rlen = ssh_buffer_get_data(buffer, *o.cstring, len); - if (rlen != len){ + rlen = ssh_buffer_get_data(buffer, *o.cstring, u32len); + if (rlen != u32len) { SAFE_FREE(*o.cstring); rc = SSH_ERROR; break; } - (*o.cstring)[len] = '\0'; + (*o.cstring)[u32len] = '\0'; o.cstring = NULL; rc = SSH_OK; break; @@ -1231,7 +1314,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, rc = SSH_ERROR; break; } - rlen = ssh_buffer_get_data(buffer, *o.data, len); + rlen = ssh_buffer_get_data(buffer, *o.data, (uint32_t)len); if (rlen != len){ SAFE_FREE(*o.data); rc = SSH_ERROR; @@ -1269,28 +1352,28 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, case 'b': o.byte = va_arg(ap_copy, uint8_t *); if (buffer->secure) { - explicit_bzero(o.byte, sizeof(uint8_t)); + ssh_burn(o.byte, sizeof(uint8_t)); break; } break; case 'w': o.word = va_arg(ap_copy, uint16_t *); if (buffer->secure) { - explicit_bzero(o.word, sizeof(uint16_t)); + ssh_burn(o.word, sizeof(uint16_t)); break; } break; case 'd': o.dword = va_arg(ap_copy, uint32_t *); if (buffer->secure) { - explicit_bzero(o.dword, sizeof(uint32_t)); + ssh_burn(o.dword, sizeof(uint32_t)); break; } break; case 'q': o.qword = va_arg(ap_copy, uint64_t *); if (buffer->secure) { - explicit_bzero(o.qword, sizeof(uint64_t)); + ssh_burn(o.qword, sizeof(uint64_t)); break; } break; @@ -1308,7 +1391,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, case 's': o.cstring = va_arg(ap_copy, char **); if (buffer->secure) { - explicit_bzero(*o.cstring, strlen(*o.cstring)); + ssh_burn(*o.cstring, strlen(*o.cstring)); } SAFE_FREE(*o.cstring); break; @@ -1316,7 +1399,7 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, len = va_arg(ap_copy, size_t); o.data = va_arg(ap_copy, void **); if (buffer->secure) { - explicit_bzero(*o.data, len); + ssh_burn(*o.data, len); } SAFE_FREE(*o.data); break; diff --git a/src/callbacks.c b/src/callbacks.c index 3ed2f11c..94ea729e 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -24,11 +24,11 @@ #include "config.h" #include "libssh/callbacks.h" -#include "libssh/session.h" #include "libssh/misc.h" +#include "libssh/session.h" #define is_callback_valid(session, cb) \ - (cb->size <= 0 || cb->size > 1024 * sizeof(void *)) + (cb->size > 0 || cb->size <= 1024 * sizeof(void *)) /* LEGACY */ static void ssh_legacy_log_callback(int priority, @@ -45,26 +45,35 @@ static void ssh_legacy_log_callback(int priority, log_fn(session, priority, buffer, log_data); } -int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) { - if (session == NULL || cb == NULL) { - return SSH_ERROR; - } - - if (is_callback_valid(session, cb)) { - ssh_set_error(session, - SSH_FATAL, - "Invalid callback passed in (badly initialized)"); - return SSH_ERROR; - }; - session->common.callbacks = cb; - - /* LEGACY */ - if (ssh_get_log_callback() == NULL && cb->log_function) { - ssh_set_log_callback(ssh_legacy_log_callback); - ssh_set_log_userdata(session); - } - - return 0; +void _ssh_remove_legacy_log_cb(void) +{ + if (ssh_get_log_callback() == ssh_legacy_log_callback) { + _ssh_reset_log_cb(); + ssh_set_log_userdata(NULL); + } +} + +int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->common.callbacks = cb; + + /* LEGACY */ + if (ssh_get_log_callback() == NULL && cb->log_function) { + ssh_set_log_callback(ssh_legacy_log_callback); + ssh_set_log_userdata(session); + } + + return 0; } static int ssh_add_set_channel_callbacks(ssh_channel channel, @@ -75,11 +84,11 @@ static int ssh_add_set_channel_callbacks(ssh_channel channel, int rc; if (channel == NULL || cb == NULL) { - return SSH_ERROR; + return SSH_ERROR; } session = channel->session; - if (is_callback_valid(session, cb)) { + if (!is_callback_valid(session, cb)) { ssh_set_error(session, SSH_FATAL, "Invalid callback passed in (badly initialized)"); @@ -87,7 +96,7 @@ static int ssh_add_set_channel_callbacks(ssh_channel channel, }; if (channel->callbacks == NULL) { channel->callbacks = ssh_list_new(); - if (channel->callbacks == NULL){ + if (channel->callbacks == NULL) { ssh_set_error_oom(session); return SSH_ERROR; } @@ -113,14 +122,14 @@ int ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) { - struct ssh_iterator *it; + struct ssh_iterator *it = NULL; - if (channel == NULL || channel->callbacks == NULL){ + if (channel == NULL || channel->callbacks == NULL) { return SSH_ERROR; } it = ssh_list_find(channel->callbacks, cb); - if (it == NULL){ + if (it == NULL) { return SSH_ERROR; } @@ -129,19 +138,19 @@ int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) return SSH_OK; } +int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } -int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb){ - if (session == NULL || cb == NULL) { - return SSH_ERROR; - } - - if (is_callback_valid(session, cb)) { + if (!is_callback_valid(session, cb)) { ssh_set_error(session, SSH_FATAL, "Invalid callback passed in (badly initialized)"); return SSH_ERROR; }; - session->server_callbacks = cb; + session->server_callbacks = cb; - return 0; + return 0; } diff --git a/src/chachapoly.c b/src/chachapoly.c index 2cd23854..354a0d26 100644 --- a/src/chachapoly.c +++ b/src/chachapoly.c @@ -42,7 +42,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { - struct chacha20_poly1305_keysched *sched; + struct chacha20_poly1305_keysched *sched = NULL; uint8_t *u8key = key; (void)IV; diff --git a/src/channels.c b/src/channels.c index b54af3bb..e9f57f88 100644 --- a/src/channels.c +++ b/src/channels.c @@ -52,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 @@ -119,7 +122,7 @@ ssh_channel ssh_channel_new(ssh_session session) } channel->session = session; - channel->exit_status = -1; + channel->exit.code = (uint32_t)-1; channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; if (session->channels == NULL) { @@ -163,68 +166,72 @@ uint32_t ssh_channel_new_id(ssh_session session) * * Constructs the channel object. */ -SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ - uint32_t channelid=0; - ssh_channel channel; - int rc; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf) +{ + uint32_t channelid = 0; + ssh_channel channel = NULL; + int rc; + (void)type; + (void)user; - SSH_LOG(SSH_LOG_PACKET,"Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); - rc = ssh_buffer_unpack(packet, "d", &channelid); - if (rc != SSH_OK) - goto error; - channel=ssh_channel_from_local(session,channelid); - if(channel==NULL){ - ssh_set_error(session, SSH_FATAL, - "Unknown channel id %" PRIu32, - (uint32_t) channelid); - /* TODO: Set error marking in channel object */ + rc = ssh_buffer_unpack(packet, "d", &channelid); + if (rc != SSH_OK) + goto error; + channel = ssh_channel_from_local(session, channelid); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Unknown channel id %" PRIu32, + (uint32_t)channelid); + /* TODO: Set error marking in channel object */ - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - rc = ssh_buffer_unpack(packet, "ddd", - &channel->remote_channel, - &channel->remote_window, - &channel->remote_maxpacket); - if (rc != SSH_OK) - goto error; + rc = ssh_buffer_unpack(packet, + "ddd", + &channel->remote_channel, + &channel->remote_window, + &channel->remote_maxpacket); + if (rc != SSH_OK) + goto error; - SSH_LOG(SSH_LOG_DEBUG, - "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 ":%" PRIu32, - channel->local_channel, - channel->remote_channel); + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 + ":%" PRIu32, + channel->local_channel, + channel->remote_channel); - if (channel->state != SSH_CHANNEL_STATE_OPENING) { - SSH_LOG(SSH_LOG_RARE, - "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " - "channel state %d", - channel->state); - goto error; - } + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " + "channel state %d", + channel->state); + goto error; + } - SSH_LOG(SSH_LOG_DEBUG, - "Remote window : %" PRIu32 ", maxpacket : %" PRIu32, - channel->remote_window, - 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; + 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 */); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + true /* is_success */); - return SSH_PACKET_USED; + return SSH_PACKET_USED; error: - ssh_set_error(session, SSH_FATAL, "Invalid packet"); - return SSH_PACKET_USED; + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; } /** @@ -232,52 +239,54 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ * * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. */ -SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail) +{ + ssh_channel channel = NULL; + char *error = NULL; + uint32_t code; + int rc; + (void)user; + (void)type; - ssh_channel channel; - char *error = NULL; - uint32_t code; - int rc; - (void)user; - (void)type; + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_RARE, "Invalid channel in packet"); + return SSH_PACKET_USED; + } - channel=channel_from_msg(session,packet); - if(channel==NULL){ - SSH_LOG(SSH_LOG_RARE,"Invalid channel in packet"); - return SSH_PACKET_USED; - } + rc = ssh_buffer_unpack(packet, "ds", &code, &error); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; + } - rc = ssh_buffer_unpack(packet, "ds", &code, &error); - if (rc != SSH_OK){ - ssh_set_error(session, SSH_FATAL, "Invalid packet"); - return SSH_PACKET_USED; - } + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " + "state %d", + channel->state); + SAFE_FREE(error); + goto error; + } - if (channel->state != SSH_CHANNEL_STATE_OPENING) { - SSH_LOG(SSH_LOG_RARE, - "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " - "state %d", - channel->state); - SAFE_FREE(error); - goto error; - } + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 + ") %s", + channel->local_channel, + code, + error); + SAFE_FREE(error); + channel->state = SSH_CHANNEL_STATE_OPEN_DENIED; - ssh_set_error(session, SSH_REQUEST_DENIED, - "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 ") %s", - channel->local_channel, - 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 */); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + false /* is_success */); - return SSH_PACKET_USED; + return SSH_PACKET_USED; error: ssh_set_error(session, SSH_FATAL, "Invalid packet"); @@ -312,7 +321,7 @@ static int ssh_channel_open_termination(void *c) * * @param[in] payload The buffer containing additional payload for the query. * - * @return SSH_OK if successful; SSH_ERROR otherwise. + * @return `SSH_OK` if successful; `SSH_ERROR` otherwise. */ static int channel_open(ssh_channel channel, @@ -400,21 +409,23 @@ channel_open(ssh_channel channel, } /* return channel with corresponding local id, or NULL if not found */ -ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { - struct ssh_iterator *it; - ssh_channel channel; +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) +{ + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; - for (it = ssh_list_get_iterator(session->channels); it != NULL ; it=it->next) { - channel = ssh_iterator_value(ssh_channel, it); - if (channel == NULL) { - continue; - } - if (channel->local_channel == id) { - return channel; + for (it = ssh_list_get_iterator(session->channels); it != NULL; + it = it->next) { + channel = ssh_iterator_value(ssh_channel, it); + if (channel == NULL) { + continue; + } + if (channel->local_channel == id) { + return channel; + } } - } - return NULL; + return NULL; } /** @@ -422,32 +433,43 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { * @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. + * @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) { + /* 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, new_window, + 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; @@ -458,12 +480,12 @@ static int grow_window(ssh_session session, } SSH_LOG(SSH_LOG_DEBUG, - "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes", + "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: @@ -487,72 +509,77 @@ static int grow_window(ssh_session session, */ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { - ssh_channel channel; - uint32_t chan; - int rc; + ssh_channel channel = NULL; + uint32_t chan; + int rc; - rc = ssh_buffer_unpack(packet,"d",&chan); - if (rc != SSH_OK) { - ssh_set_error(session, SSH_FATAL, - "Getting channel from message: short read"); - return NULL; - } + rc = ssh_buffer_unpack(packet, "d", &chan); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } - channel = ssh_channel_from_local(session, chan); - if (channel == NULL) { - ssh_set_error(session, SSH_FATAL, - "Server specified invalid channel %" PRIu32, - (uint32_t) chan); - } + channel = ssh_channel_from_local(session, chan); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Server specified invalid channel %" PRIu32, + (uint32_t)chan); + } - return channel; + return channel; } -SSH_PACKET_CALLBACK(channel_rcv_change_window) { - ssh_channel channel; - uint32_t bytes; - int rc; - bool was_empty; +SSH_PACKET_CALLBACK(channel_rcv_change_window) +{ + ssh_channel channel = NULL; + uint32_t bytes; + int rc; + bool was_empty; - (void)user; - (void)type; + (void)user; + (void)type; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - } + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } - rc = ssh_buffer_unpack(packet, "d", &bytes); - if (channel == NULL || rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, - "Error getting a window adjust message: invalid packet"); + rc = ssh_buffer_unpack(packet, "d", &bytes); + if (channel == NULL || rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - 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); + 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; + was_empty = channel->remote_window == 0; - channel->remote_window += bytes; + 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); - } + /* 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; + return SSH_PACKET_USED; } /* is_stderr is set to 1 if the data are extended, ie stderr */ @@ -604,22 +631,30 @@ SSH_PACKET_CALLBACK(channel_rcv_data) return SSH_PACKET_USED; } - len = ssh_string_len(str); + /* STRING_SIZE_MAX < UINT32_MAX */ + len = (uint32_t)ssh_string_len(str); SSH_LOG(SSH_LOG_PACKET, "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32 - " remote win=%" PRIu32 ")", + " remote win=%" PRIu32 ") on channel %" PRIu32 ":%" PRIu32, len, - is_stderr ? " in stderr" : "", + is_stderr ? " in stderr" : "", channel->local_window, - channel->remote_window); + channel->remote_window, + channel->local_channel, + channel->remote_channel); - /* 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(%" 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); @@ -629,11 +664,7 @@ SSH_PACKET_CALLBACK(channel_rcv_data) return SSH_PACKET_USED; } - if (len <= channel->local_window) { - channel->local_window -= len; - } else { - channel->local_window = 0; /* buggy remote */ - } + channel->local_window -= len; SSH_LOG(SSH_LOG_PACKET, "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", @@ -661,48 +692,50 @@ SSH_PACKET_CALLBACK(channel_rcv_data) 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); + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } } } ssh_callbacks_iterate_end(); - if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } - } return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(channel_rcv_eof) { - ssh_channel channel; - (void)user; - (void)type; +SSH_PACKET_CALLBACK(channel_rcv_eof) +{ + ssh_channel channel = NULL; + (void)user; + (void)type; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", - channel->local_channel, - channel->remote_channel); - /* channel->remote_window = 0; */ - channel->remote_eof = 1; + SSH_LOG(SSH_LOG_PACKET, + "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_eof_function, - channel->session, - channel); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_eof_function, + channel->session, + channel); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } static bool ssh_channel_has_unread_data(ssh_channel channel) @@ -722,8 +755,9 @@ static bool ssh_channel_has_unread_data(ssh_channel channel) return false; } -SSH_PACKET_CALLBACK(channel_rcv_close) { - ssh_channel channel; +SSH_PACKET_CALLBACK(channel_rcv_close) +{ + ssh_channel channel = NULL; (void)user; (void)type; @@ -768,98 +802,105 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(channel_rcv_request) { - ssh_channel channel; - char *request=NULL; +SSH_PACKET_CALLBACK(channel_rcv_request) +{ + ssh_channel channel = NULL; + char *request = NULL; uint8_t want_reply; int rc; - (void)user; - (void)type; - - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } - - rc = ssh_buffer_unpack(packet, "sb", - &request, - &want_reply); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } - - if (strcmp(request,"exit-status") == 0) { + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-status") == 0) { SAFE_FREE(request); - rc = ssh_buffer_unpack(packet, "d", &channel->exit_status); + rc = ssh_buffer_unpack(packet, "d", &channel->exit.code); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet"); return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PACKET, "received exit-status %d", channel->exit_status); + channel->exit.status = true; + + SSH_LOG(SSH_LOG_PACKET, + "received exit-status %u on channel %" PRIu32 ":%" PRIu32, + channel->exit.code, + channel->local_channel, + channel->remote_channel); ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_exit_status_function, channel->session, channel, - channel->exit_status); + channel->exit.code); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - if (strcmp(request,"signal") == 0) { + if (strcmp(request, "signal") == 0) { char *sig = NULL; - SAFE_FREE(request); - SSH_LOG(SSH_LOG_PACKET, "received signal"); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PACKET, "received signal"); - rc = ssh_buffer_unpack(packet, "s", &sig); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } + rc = ssh_buffer_unpack(packet, "s", &sig); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Remote connection sent a signal SIG %s", sig); + SSH_LOG(SSH_LOG_PACKET, "Remote connection sent a signal SIG %s", sig); ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_signal_function, channel->session, channel, sig); - SAFE_FREE(sig); - - return SSH_PACKET_USED; - } - - if (strcmp(request, "exit-signal") == 0) { - const char *core = "(core dumped)"; - char *sig = NULL; - char *errmsg = NULL; - char *lang = NULL; - uint8_t core_dumped; - - SAFE_FREE(request); - - rc = ssh_buffer_unpack(packet, "sbss", - &sig, /* signal name */ - &core_dumped, /* core dumped */ - &errmsg, /* error message */ - &lang); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } - - if (core_dumped == 0) { - core = ""; - } - - SSH_LOG(SSH_LOG_PACKET, - "Remote connection closed by signal SIG %s %s", sig, core); - ssh_callbacks_execute_list(channel->callbacks, + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + char *sig = NULL; + char *errmsg = NULL; + char *lang = NULL; + uint8_t core_dumped; + + SAFE_FREE(request); + + rc = ssh_buffer_unpack(packet, + "sbss", + &sig, /* signal name */ + &core_dumped, /* core dumped */ + &errmsg, /* error message */ + &lang); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (core_dumped == 0) { + core = ""; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", + sig, + core); + ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_exit_signal_function, channel->session, @@ -869,74 +910,85 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { errmsg, lang); + channel->exit.core_dumped = core_dumped; + if (sig != NULL) { + SAFE_FREE(channel->exit.signal); + channel->exit.signal = sig; + } + channel->exit.status = true; + SAFE_FREE(lang); SAFE_FREE(errmsg); - SAFE_FREE(sig); - - return SSH_PACKET_USED; - } - if(strcmp(request,"keepalive@openssh.com")==0){ - SAFE_FREE(request); - SSH_LOG(SSH_LOG_DEBUG,"Responding to Openssh's keepalive"); - - rc = ssh_buffer_pack(session->out_buffer, - "bd", - SSH2_MSG_CHANNEL_FAILURE, - channel->remote_channel); - if (rc != SSH_OK) { - return SSH_PACKET_USED; - } - ssh_packet_send(session); - - return SSH_PACKET_USED; - } - - if (strcmp(request, "auth-agent-req@openssh.com") == 0) { - int status; - SAFE_FREE(request); - 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; + return SSH_PACKET_USED; } - ssh_callbacks_iterate_end(); + if (strcmp(request, "keepalive@openssh.com") == 0) { + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Responding to Openssh's keepalive"); - if (want_reply) { rc = ssh_buffer_pack(session->out_buffer, "bd", - status, + SSH2_MSG_CHANNEL_FAILURE, channel->remote_channel); if (rc != SSH_OK) { return SSH_PACKET_USED; } ssh_packet_send(session); + + return SSH_PACKET_USED; } - return SSH_PACKET_USED; - } -#ifdef WITH_SERVER - /* If we are here, that means we have a request that is not in the understood - * client requests. That means we need to create a ssh message to be passed - * to the user code handling ssh messages - */ - ssh_message_handle_channel_request(session,channel,packet,request,want_reply); -#else - SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); -#endif + if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; - SAFE_FREE(request); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); - return SSH_PACKET_USED; -} + 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; + } +#ifdef WITH_SERVER + /* If we are here, that means we have a request that is not in the + * understood client requests. That means we need to create a ssh message to + * be passed to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session, + channel, + packet, + request, + want_reply); +#else + SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); +#endif + + SAFE_FREE(request); + + return SSH_PACKET_USED; +} /* * When data has been received from the ssh server, it can be applied to the @@ -948,58 +1000,58 @@ int channel_default_bufferize(ssh_channel channel, void *data, uint32_t len, bool is_stderr) { - ssh_session session; - - if(channel == NULL) { - return -1; - } - - session = channel->session; - - if(data == NULL) { - ssh_set_error_invalid(session); - return -1; - } + ssh_session session = NULL; - SSH_LOG(SSH_LOG_PACKET, - "placing %" PRIu32 " bytes into channel buffer (%s)", - len, - is_stderr ? "stderr" : "stdout"); - if (!is_stderr) { - /* stdout */ - if (channel->stdout_buffer == NULL) { - channel->stdout_buffer = ssh_buffer_new(); - if (channel->stdout_buffer == NULL) { - ssh_set_error_oom(session); + if (channel == NULL) { return -1; - } } - if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) { - ssh_set_error_oom(session); - SSH_BUFFER_FREE(channel->stdout_buffer); - channel->stdout_buffer = NULL; - return -1; - } - } else { - /* stderr */ - if (channel->stderr_buffer == NULL) { - channel->stderr_buffer = ssh_buffer_new(); - if (channel->stderr_buffer == NULL) { - ssh_set_error_oom(session); + session = channel->session; + + if (data == NULL) { + ssh_set_error_invalid(session); return -1; - } } - if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) { - ssh_set_error_oom(session); - SSH_BUFFER_FREE(channel->stderr_buffer); - channel->stderr_buffer = NULL; - return -1; + SSH_LOG(SSH_LOG_PACKET, + "placing %" PRIu32 " bytes into channel buffer (%s)", + len, + is_stderr ? "stderr" : "stdout"); + if (!is_stderr) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } } - } - return 0; + return 0; } /** @@ -1007,9 +1059,9 @@ int channel_default_bufferize(ssh_channel channel, * * @param[in] channel An allocated channel. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @see ssh_channel_open_forward() @@ -1025,22 +1077,22 @@ int ssh_channel_open_session(ssh_channel channel) return channel_open(channel, "session", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } /** * @brief Open an agent authentication forwarding channel. This type of channel - * can be opened by a server towards a client in order to provide SSH-Agent services - * to the server-side process. This channel can only be opened if the client - * claimed support by sending a channel request beforehand. + * can be opened by a server towards a client in order to provide SSH-Agent + * services to the server-side process. This channel can only be opened if the + * client claimed support by sending a channel request beforehand. * * @param[in] channel An allocated channel. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @see ssh_channel_open_forward() @@ -1053,12 +1105,11 @@ int ssh_channel_open_auth_agent(ssh_channel channel) return channel_open(channel, "auth-agent@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } - /** * @brief Open a TCP/IP forwarding channel. * @@ -1075,62 +1126,62 @@ int ssh_channel_open_auth_agent(ssh_channel channel) * @param[in] localport The port on the host from where the connection * originated. This is mostly for logging purposes. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * - * @warning This function does not bind the local port and does not automatically - * forward the content of a socket to the channel. You still have to - * use ssh_channel_read and ssh_channel_write for this. + * @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 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) { - ssh_session session; - ssh_buffer payload = NULL; - ssh_string str = NULL; - int rc = SSH_ERROR; + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; - if (channel == NULL) { - return rc; - } + if (channel == NULL) { + return rc; + } - session = channel->session; + session = channel->session; - if(remotehost == NULL || sourcehost == NULL) { - ssh_set_error_invalid(session); - return rc; - } + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } - payload = ssh_buffer_new(); - if (payload == NULL) { - ssh_set_error_oom(session); - goto error; - } + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } - rc = ssh_buffer_pack(payload, - "sdsd", - remotehost, - remoteport, - sourcehost, - localport); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } - rc = channel_open(channel, - "direct-tcpip", - CHANNEL_INITIAL_WINDOW, - CHANNEL_MAX_PACKET, - payload); + rc = channel_open(channel, + "direct-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); error: - SSH_BUFFER_FREE(payload); - SSH_STRING_FREE(str); + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); - return rc; + return rc; } /** @@ -1147,16 +1198,16 @@ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, * @param[in] localport The port on the host from where the connection * originated. This is mostly for logging purposes. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK on` success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @warning This function does not bind the local port and does not - * automatically forward the content of a socket to the channel. - * You still have to use ssh_channel_read and ssh_channel_write for this. + * automatically forward the content of a socket to the channel. + * 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, const char *remotepath, const char *sourcehost, @@ -1164,7 +1215,6 @@ int ssh_channel_open_forward_unix(ssh_channel channel, { ssh_session session = NULL; ssh_buffer payload = NULL; - ssh_string str = NULL; int rc = SSH_ERROR; int version; @@ -1205,13 +1255,12 @@ 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); error: SSH_BUFFER_FREE(payload); - SSH_STRING_FREE(str); return rc; } @@ -1225,7 +1274,7 @@ int ssh_channel_open_forward_unix(ssh_channel channel, */ void ssh_channel_free(ssh_channel channel) { - ssh_session session; + ssh_session session = NULL; if (channel == NULL) { return; @@ -1258,6 +1307,11 @@ void ssh_channel_free(ssh_channel channel) } channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + /* The idea behind the flags is the following : it is well possible * 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* @@ -1291,6 +1345,7 @@ void ssh_channel_do_free(ssh_channel channel) ssh_list_free(channel->callbacks); channel->callbacks = NULL; } + SAFE_FREE(channel->exit.signal); channel->session = NULL; SAFE_FREE(channel); @@ -1303,7 +1358,7 @@ void ssh_channel_do_free(ssh_channel channel) * * @param[in] channel The channel to send the eof to. * - * @return SSH_OK on success, SSH_ERROR if an error occurred. + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. * * Example: @code @@ -1326,7 +1381,7 @@ void ssh_channel_do_free(ssh_channel channel) */ int ssh_channel_send_eof(ssh_channel channel) { - ssh_session session; + ssh_session session = NULL; int rc = SSH_ERROR; int err; @@ -1380,14 +1435,14 @@ int ssh_channel_send_eof(ssh_channel channel) * * @param[in] channel The channel to close. * - * @return SSH_OK on success, SSH_ERROR if an error occurred. + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. * * @see ssh_channel_free() * @see ssh_channel_is_eof() */ int ssh_channel_close(ssh_channel channel) { - ssh_session session; + ssh_session session = NULL; int rc = 0; if(channel == NULL) { @@ -1468,11 +1523,11 @@ static int ssh_waitsession_unblocked(void *s) /** * @internal * @brief Flushes a channel (and its session) until the output buffer - * is empty, or timeout elapsed. + * is empty, or timeout elapsed. * @param channel SSH channel - * @return SSH_OK On success, - * SSH_ERROR On error. - * SSH_AGAIN Timeout elapsed (or in nonblocking mode). + * @return `SSH_OK` On success, + * `SSH_ERROR` On error. + * `SSH_AGAIN` Timeout elapsed (or in nonblocking mode). */ int ssh_channel_flush(ssh_channel channel) { @@ -1483,144 +1538,153 @@ static int channel_write_common(ssh_channel channel, const void *data, uint32_t len, int is_stderr) { - ssh_session session; - uint32_t origlen = len; - size_t effectivelen; - size_t maxpacketlen; - int rc; - - if(channel == NULL) { - return -1; - } - session = channel->session; - if(data == NULL) { - ssh_set_error_invalid(session); - return -1; - } + ssh_session session = NULL; + uint32_t origlen = len; + uint32_t effectivelen; + int rc; - if (len > INT_MAX) { - SSH_LOG(SSH_LOG_TRACE, - "Length (%" PRIu32 ") is bigger than INT_MAX", len); - return SSH_ERROR; - } + if (channel == NULL) { + return -1; + } + session = channel->session; + if (data == NULL) { + ssh_set_error_invalid(session); + return -1; + } - /* - * Handle the max packet len from remote side, be nice - * 10 bytes for the headers - */ - maxpacketlen = channel->remote_maxpacket - 10; + if (len > INT_MAX) { + SSH_LOG(SSH_LOG_TRACE, + "Length (%" PRIu32 ") is bigger than INT_MAX", + len); + return SSH_ERROR; + } - if (channel->local_eof) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Can't write to channel %" PRIu32 ":%" PRIu32 " after EOF was sent", - channel->local_channel, - channel->remote_channel); - return -1; - } + if (channel->local_eof) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't write to channel %" PRIu32 ":%" PRIu32 + " after EOF was sent", + channel->local_channel, + channel->remote_channel); + return -1; + } - if (channel->state != SSH_CHANNEL_STATE_OPEN || channel->delayed_close != 0) { - ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + if (channel->state != SSH_CHANNEL_STATE_OPEN || + channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); - return -1; - } + return -1; + } - if (session->session_state == SSH_SESSION_STATE_ERROR) { - return SSH_ERROR; - } + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } - if (ssh_waitsession_unblocked(session) == 0){ - rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_DEFAULT, - ssh_waitsession_unblocked, session); - if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) - goto out; - } - while (len > 0) { - if (channel->remote_window < len) { - SSH_LOG(SSH_LOG_DEBUG, - "Remote window is %" PRIu32 " bytes. going to write %" PRIu32 " bytes", - channel->remote_window, - len); - /* When the window is zero, wait for it to grow */ - if(channel->remote_window == 0) { - /* nothing can be written */ - 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); - if (rc == SSH_ERROR || - !ssh_channel_waitwindow_termination(channel) || - session->session_state == SSH_SESSION_STATE_ERROR || - channel->state == SSH_CHANNEL_STATE_CLOSED) + if (ssh_waitsession_unblocked(session) == 0) { + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_waitsession_unblocked, + session); + if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) goto out; - continue; - } - /* When the window is non-zero, accept data up to the window size */ - effectivelen = MIN(len, channel->remote_window); - } else { - effectivelen = len; } + while (len > 0) { + if (channel->remote_window < len) { + SSH_LOG(SSH_LOG_DEBUG, + "Remote window is %" PRIu32 + " bytes. going to write %" PRIu32 " bytes", + channel->remote_window, + len); + /* When the window is zero, wait for it to grow */ + if (channel->remote_window == 0) { + /* nothing can be written */ + 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); + if (rc == SSH_ERROR || + !ssh_channel_waitwindow_termination(channel) || + session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + goto out; + continue; + } + /* 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); - - rc = ssh_buffer_pack(session->out_buffer, - "bd", - is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA : SSH2_MSG_CHANNEL_DATA, - channel->remote_channel); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } + /* + * 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); - /* stderr message has an extra field */ - if (is_stderr) { rc = ssh_buffer_pack(session->out_buffer, - "d", - SSH2_EXTENDED_DATA_STDERR); + "bd", + is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA + : SSH2_MSG_CHANNEL_DATA, + channel->remote_channel); if (rc != SSH_OK) { ssh_set_error_oom(session); goto error; } - } - /* append payload data */ - rc = ssh_buffer_pack(session->out_buffer, - "dP", - effectivelen, - (size_t)effectivelen, data); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } + /* stderr message has an extra field */ + if (is_stderr) { + rc = ssh_buffer_pack(session->out_buffer, + "d", + SSH2_EXTENDED_DATA_STDERR); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + } - rc = ssh_packet_send(session); - if (rc == SSH_ERROR) { - return SSH_ERROR; - } + /* append payload data */ + rc = ssh_buffer_pack(session->out_buffer, + "dP", + effectivelen, + (size_t)effectivelen, + data); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } - SSH_LOG(SSH_LOG_PACKET, - "ssh_channel_write wrote %ld bytes", (long int) effectivelen); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } - channel->remote_window -= effectivelen; - len -= effectivelen; - data = ((uint8_t*)data + effectivelen); - if (channel->counter != NULL) { - channel->counter->out_bytes += effectivelen; + SSH_LOG(SSH_LOG_PACKET, + "ssh_channel_write wrote %" PRIu32 " bytes", + effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t *)data + effectivelen); + if (channel->counter != NULL) { + channel->counter->out_bytes += effectivelen; + } } - } - /* it's a good idea to flush the socket now */ - rc = ssh_channel_flush(channel); - if (rc == SSH_ERROR) { - goto error; - } + /* it's a good idea to flush the socket now */ + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } out: - return (int)(origlen - len); + return (int)(origlen - len); error: - ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_buffer); - return SSH_ERROR; + return SSH_ERROR; } /** @@ -1654,7 +1718,7 @@ uint32_t ssh_channel_window_size(ssh_channel channel) * * @param[in] len The length of the buffer to write to. * - * @return The number of bytes written, SSH_ERROR on error. + * @return The number of bytes written, `SSH_ERROR` on error. * * @see ssh_channel_read() */ @@ -1691,7 +1755,7 @@ int ssh_channel_is_open(ssh_channel channel) */ int ssh_channel_is_closed(ssh_channel channel) { - if (channel == NULL) { + if (channel == NULL || channel->session == NULL) { return SSH_ERROR; } return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); @@ -1740,35 +1804,37 @@ void ssh_channel_set_blocking(ssh_channel channel, int blocking) * * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. */ -SSH_PACKET_CALLBACK(ssh_packet_channel_success){ - ssh_channel channel; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_channel_success) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; - channel=channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", - channel->local_channel, - channel->remote_channel); - if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ - SSH_LOG(SSH_LOG_RARE, "SSH_CHANNEL_SUCCESS received in incorrect state %d", - channel->request_state); - } else { - channel->request_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED; - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_request_response_function, - channel->session, - channel); - } + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } - return SSH_PACKET_USED; + return SSH_PACKET_USED; } /** @@ -1776,36 +1842,38 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_success){ * * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. */ -SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ - ssh_channel channel; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_channel_failure) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; - channel=channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", - channel->local_channel, - channel->remote_channel); - if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING){ - SSH_LOG(SSH_LOG_RARE, "SSH_CHANNEL_FAILURE received in incorrect state %d", - channel->request_state); - } else { - channel->request_state=SSH_CHANNEL_REQ_STATE_DENIED; + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED; - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_request_response_function, - channel->session, - channel); - } + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } - return SSH_PACKET_USED; + return SSH_PACKET_USED; } static int ssh_channel_request_termination(void *c) @@ -1856,7 +1924,10 @@ static int channel_request(ssh_channel channel, const char *request, } SSH_LOG(SSH_LOG_PACKET, - "Sent a SSH_MSG_CHANNEL_REQUEST %s", request); + "Sent a SSH_MSG_CHANNEL_REQUEST %s on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); if (reply == 0) { channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; return SSH_OK; @@ -1876,13 +1947,20 @@ static int channel_request(ssh_channel channel, const char *request, rc=SSH_ERROR; break; case SSH_CHANNEL_REQ_STATE_DENIED: - ssh_set_error(session, SSH_REQUEST_DENIED, - "Channel request %s failed", request); + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel request %s failed on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); rc=SSH_ERROR; break; case SSH_CHANNEL_REQ_STATE_ACCEPTED: SSH_LOG(SSH_LOG_DEBUG, - "Channel request %s success",request); + "Channel request %s success on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); rc=SSH_OK; break; case SSH_CHANNEL_REQ_STATE_PENDING: @@ -1914,61 +1992,96 @@ static int channel_request(ssh_channel channel, const char *request, * * @param[in] row The number of rows. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @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; + ssh_session session = NULL; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; - if(channel == NULL) { - return SSH_ERROR; - } - session = channel->session; + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; - if(terminal == NULL) { - ssh_set_error_invalid(channel->session); - return rc; - } + if (terminal == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } - switch(channel->request_state){ - case SSH_CHANNEL_REQ_STATE_NONE: - break; - default: - goto pending; - } + switch (channel->request_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(session); - goto error; - } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } - rc = ssh_buffer_pack(buffer, - "sdddddb", - terminal, - col, - row, - 0, /* pix */ - 0, /* pix */ - 1, /* add a 0byte string */ - 0); + rc = ssh_buffer_pack(buffer, + "sdddddP", + terminal, + col, + row, + 0, /* pix */ + 0, /* pix */ + (uint32_t)modes_len, + (size_t)modes_len, + modes); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } pending: - rc = channel_request(channel, "pty-req", buffer, 1); + rc = channel_request(channel, "pty-req", buffer, 1); error: - SSH_BUFFER_FREE(buffer); + SSH_BUFFER_FREE(buffer); - return rc; + return rc; +} + +/** + * @brief Request a PTY with a specific size using current TTY modes. + * + * Encodes @p terminal modes from the current TTY and sends a PTY request + * for the given channel, terminal type, and size in columns/rows. + * + * @param[in] channel The channel to send the request on. + * @param[in] terminal The terminal type (e.g. "xterm"). + * @param[in] col Number of columns. + * @param[in] row Number of rows. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure. + */ +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); } /** @@ -1976,9 +2089,9 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, * * @param[in] channel The channel to send the request. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @see ssh_channel_request_pty_size() @@ -1997,7 +2110,7 @@ int ssh_channel_request_pty(ssh_channel channel) * * @param[in] rows The new number of rows. * - * @return SSH_OK on success, SSH_ERROR if an error occurred. + * @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 the @@ -2038,9 +2151,9 @@ int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) * * @param[in] channel The channel to send the request. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. */ int ssh_channel_request_shell(ssh_channel channel) @@ -2059,9 +2172,9 @@ int ssh_channel_request_shell(ssh_channel channel) * * @param[in] subsys The subsystem to request (for example "sftp"). * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @warning You normally don't have to call it for sftp, see sftp_new(). @@ -2109,9 +2222,9 @@ int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) * * @param[in] channel The channel to request the sftp subsystem. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * @note You should use sftp_new() which does this for you. @@ -2165,9 +2278,9 @@ static char *generate_cookie(void) * * @param[in] screen_number The screen number. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. */ int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, @@ -2233,7 +2346,7 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, #endif ssh_message msg = NULL; ssh_channel channel = NULL; - struct ssh_iterator *iterator; + struct ssh_iterator *iterator = NULL; int t; /* @@ -2300,15 +2413,16 @@ ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) } /** - * @brief Send an "auth-agent-req" channel request over an existing session channel. + * @brief Send an "auth-agent-req" channel request over an existing session + * channel. * - * This client-side request will enable forwarding the agent over an secure tunnel. - * When the server is ready to open one authentication agent channel, an + * This client-side request will enable forwarding the agent over an secure + * tunnel. When the server is ready to open one authentication agent channel, an * ssh_channel_open_request_auth_agent_callback event will be generated. * * @param[in] channel The channel to send signal. * - * @return SSH_OK on success, SSH_ERROR if an error occurred + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred */ int ssh_channel_request_auth_agent(ssh_channel channel) { if (channel == NULL) { @@ -2389,9 +2503,9 @@ static int ssh_global_request_termination(void *s) * * @param[in] reply Set if you expect a reply from server. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. */ int ssh_global_request(ssh_session session, @@ -2483,7 +2597,7 @@ int ssh_global_request(ssh_session session, /** * @brief Sends the "tcpip-forward" global request to ask the server to begin - * listening for inbound connections. + * listening for inbound connections. * * @param[in] session The ssh session to send the request. * @@ -2498,9 +2612,9 @@ int ssh_global_request(ssh_session session, * @param[in] bound_port The pointer to get actual bound port. Pass NULL to * ignore. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. **/ int ssh_channel_listen_forward(ssh_session session, @@ -2607,9 +2721,9 @@ ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, i * * @param[in] port The bound port on the server. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. */ int ssh_channel_cancel_forward(ssh_session session, @@ -2658,9 +2772,9 @@ int ssh_forward_cancel(ssh_session session, const char *address, int port) * * @param[in] value The value to set. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * @warning Some environment variables may be refused by security reasons. */ @@ -2714,9 +2828,9 @@ int ssh_channel_request_env(ssh_channel channel, const char *name, const char *v * @param[in] cmd The command to execute * (e.g. "ls ~/ -al | grep -i reports"). * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * * Example: @@ -2732,6 +2846,12 @@ int ssh_channel_request_env(ssh_channel channel, const char *name, const char *v } } @endcode + * + * @warning In a single channel, only ONE command can be executed! + * If you want to executed multiple commands, allocate separate channels for + * them or consider opening interactive shell. + * Attempting to run multiple consecutive commands in one channel will fail. + * See RFC 4254 Section 6.5. * * @see ssh_channel_request_shell() */ @@ -2773,9 +2893,9 @@ int ssh_channel_request_exec(ssh_channel channel, const char *cmd) return rc; } - /** - * @brief Send a signal to remote process (as described in RFC 4254, section 6.9). + * @brief Send a signal to remote process (as described in RFC 4254, + * section 6.9). * * Sends a signal 'sig' to the remote process. * Note, that remote system may not support signals concept. @@ -2799,7 +2919,7 @@ int ssh_channel_request_exec(ssh_channel channel, const char *cmd) * SIGUSR1 -> USR1 \n * SIGUSR2 -> USR2 \n * - * @return SSH_OK on success, SSH_ERROR if an error occurred. + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. */ int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { @@ -2832,7 +2952,6 @@ int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) return rc; } - /** * @brief Send a break signal to the server (as described in RFC 4335). * @@ -2844,7 +2963,7 @@ int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) * * @param[in] length The break-length in milliseconds to send. * - * @return SSH_OK on success, SSH_ERROR if an error occurred + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred */ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { @@ -2874,7 +2993,6 @@ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) return rc; } - /** * @brief Read data from a channel into a buffer. * @@ -2883,13 +3001,14 @@ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) * @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 - * return once anything is available. + * the exact size will be read, else (bytes=0) it will return once anything is + * available. * * @param is_stderr A boolean value to mark reading from the stderr stream. * - * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. + * * @deprecated Please use ssh_channel_read instead * @warning This function doesn't work in nonblocking/timeout mode * @see ssh_channel_read @@ -2897,84 +3016,83 @@ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, int is_stderr) { - ssh_session session; - char *buffer_tmp = NULL; - int r; - uint32_t total=0; - - if(channel == NULL) { - return SSH_ERROR; - } - session = channel->session; + ssh_session session = NULL; + char *buffer_tmp = NULL; + int r; + uint32_t total = 0; - if(buffer == NULL) { - ssh_set_error_invalid(channel->session); - return SSH_ERROR; - } + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; - ssh_buffer_reinit(buffer); - if(count==0){ - do { - r=ssh_channel_poll(channel, is_stderr); - if(r < 0){ - return r; - } - if(r > 0){ - count = r; - buffer_tmp = ssh_buffer_allocate(buffer, count); - if (buffer_tmp == NULL) { - ssh_set_error_oom(session); - return SSH_ERROR; - } - r=ssh_channel_read(channel, buffer_tmp, r, is_stderr); - if(r < 0){ - ssh_buffer_pass_bytes_end(buffer, count); - return r; - } - /* Rollback the unused space */ - ssh_buffer_pass_bytes_end(buffer, count - r); + if (buffer == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } - return r; - } - if(ssh_channel_is_eof(channel)){ - return 0; - } - ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); - } while (r == 0); - } + ssh_buffer_reinit(buffer); + if (count == 0) { + do { + r = ssh_channel_poll(channel, is_stderr); + if (r < 0) { + return r; + } + if (r > 0) { + count = r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + r = ssh_channel_read(channel, buffer_tmp, r, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - r); + + return r; + } + if (ssh_channel_is_eof(channel)) { + return 0; + } + ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); + } while (r == 0); + } - buffer_tmp = ssh_buffer_allocate(buffer, count); - if (buffer_tmp == NULL) { - ssh_set_error_oom(session); - return SSH_ERROR; - } - while(total < count){ - r=ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); - if(r<0){ - ssh_buffer_pass_bytes_end(buffer, count); - return r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; } - if(r==0){ - /* Rollback the unused space */ - ssh_buffer_pass_bytes_end(buffer, count - total); - return total; + while (total < count) { + r = ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + if (r == 0) { + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - total); + return total; + } + total += r; } - total += r; - } - return total; + return total; } struct ssh_channel_read_termination_struct { ssh_channel channel; - uint32_t count; ssh_buffer buffer; }; static int ssh_channel_read_termination(void *s) { struct ssh_channel_read_termination_struct *ctx = s; - if (ssh_buffer_get_len(ctx->buffer) >= ctx->count || + if (ssh_buffer_get_len(ctx->buffer) >= 1 || ctx->channel->remote_eof || ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) return 1; @@ -2982,8 +3100,6 @@ static int ssh_channel_read_termination(void *s) return 0; } -/* TODO: FIXME Fix the blocking behaviours */ - /** * @brief Reads data from a channel. * @@ -2995,12 +3111,11 @@ static int ssh_channel_read_termination(void *s) * * @param[in] is_stderr A boolean value to mark reading from the stderr flow. * - * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. In nonblocking mode it can return 0 if no data - * is available or SSH_AGAIN. + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. * * @warning This function may return less than count bytes of data, and won't - * block until count bytes have been read. + * block until count bytes have been read. */ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { @@ -3025,9 +3140,8 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * @param[in] timeout_ms A timeout in milliseconds. A value of -1 means * infinite timeout. * - * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. In nonblocking mode it Can return 0 if no data - * is available or SSH_AGAIN. + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout, `SSH_ERROR` on error. * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. @@ -3038,109 +3152,96 @@ int ssh_channel_read_timeout(ssh_channel channel, int is_stderr, int timeout_ms) { - ssh_session session; - ssh_buffer stdbuf; - uint32_t len; - struct ssh_channel_read_termination_struct ctx; - int rc; + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; + uint32_t len; + struct ssh_channel_read_termination_struct ctx; + int rc; - if(channel == NULL) { - return SSH_ERROR; - } - if(dest == NULL) { - ssh_set_error_invalid(channel->session); - return SSH_ERROR; - } + if (channel == NULL) { + return SSH_ERROR; + } + if (dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } - session = channel->session; - stdbuf = channel->stdout_buffer; + session = channel->session; + stdbuf = channel->stdout_buffer; - if (count == 0) { - return 0; - } + if (count == 0) { + return 0; + } - if (is_stderr) { - stdbuf=channel->stderr_buffer; - } + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } - /* - * We may have problem if the window is too small to accept as much data - * as asked - */ - SSH_LOG(SSH_LOG_PACKET, - "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, - count, - ssh_buffer_get_len(stdbuf), - channel->local_window); + SSH_LOG(SSH_LOG_PACKET, + "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; - /* 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; + } - if (timeout_ms < SSH_TIMEOUT_DEFAULT) { - timeout_ms = SSH_TIMEOUT_INFINITE; - } + rc = ssh_handle_packets_termination(session, + timeout_ms, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } - rc = ssh_handle_packets_termination(session, - timeout_ms, - ssh_channel_read_termination, - &ctx); - if (rc == SSH_ERROR){ - return rc; - } + /* + * If the channel is closed or in an error state, reading from it is an + * error + */ + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + /* If the server closed the channel properly, there is nothing to do */ + if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) { + return 0; + } + if (channel->state == SSH_CHANNEL_STATE_CLOSED) { + ssh_set_error(session, SSH_FATAL, "Remote channel is closed."); + return SSH_ERROR; + } + len = ssh_buffer_get_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, ssh_buffer_get(stdbuf), len); + ssh_buffer_pass_bytes(stdbuf, len); + if (channel->counter != NULL) { + channel->counter->in_bytes += len; + } + /* Try completing the delayed_close */ + if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } - /* - * If the channel is closed or in an error state, reading from it is an error - */ - if (session->session_state == SSH_SESSION_STATE_ERROR) { - return SSH_ERROR; - } - /* If the server closed the channel properly, there is nothing to do */ - if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) { - return 0; - } - if (channel->state == SSH_CHANNEL_STATE_CLOSED) { - ssh_set_error(session, - SSH_FATAL, - "Remote channel is closed."); - return SSH_ERROR; - } - len = ssh_buffer_get_len(stdbuf); - /* Read count bytes if len is greater, everything otherwise */ - len = (len > count ? count : len); - memcpy(dest, ssh_buffer_get(stdbuf), len); - ssh_buffer_pass_bytes(stdbuf,len); - if (channel->counter != NULL) { - channel->counter->in_bytes += len; - } - /* Try completing the delayed_close */ - 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; + return len; } /** * @brief Do a nonblocking read on the channel. * * A nonblocking read on the specified channel. it will return <= count bytes of - * data read atomically. + * data read atomically. It will also trigger any callbacks set on the channel. * * @param[in] channel The channel to read from. * @@ -3150,10 +3251,8 @@ int ssh_channel_read_timeout(ssh_channel channel, * * @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, `SSH_AGAIN` if nothing is + * available, `SSH_ERROR` on error, and `SSH_EOF` if the channel is EOF. * * @see ssh_channel_is_eof() */ @@ -3162,7 +3261,7 @@ int ssh_channel_read_nonblocking(ssh_channel channel, uint32_t count, int is_stderr) { - ssh_session session; + ssh_session session = NULL; uint32_t to_read; int rc; int blocking; @@ -3203,16 +3302,18 @@ int ssh_channel_read_nonblocking(ssh_channel channel, /** * @brief Polls a channel for data to read. * + * If callbacks are set on the channel, they will be called. + * * @param[in] channel The channel to poll. * * @param[in] is_stderr A boolean to select the stderr stream. * * @return The number of bytes available for reading, 0 if nothing - * is available or SSH_ERROR on error. + * is available or `SSH_ERROR` on error. * When a channel is freed the function returns - * SSH_ERROR immediately. + * `SSH_ERROR` immediately. * - * @warning When the channel is in EOF state, the function returns SSH_EOF. + * @warning When the channel is in EOF state, the function returns `SSH_EOF`. * * @see ssh_channel_is_eof() */ @@ -3230,7 +3331,7 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr) stdbuf = channel->stderr_buffer; } - if (ssh_buffer_get_len(stdbuf) == 0 && channel->remote_eof == 0) { + if (channel->remote_eof == 0) { if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ return SSH_ERROR; } @@ -3255,25 +3356,26 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr) * * @param[in] channel The channel to poll. * @param[in] timeout Set an upper limit on the time for which this function - * will block, in milliseconds. Specifying a negative value - * means an infinite timeout. This parameter is passed to - * the poll() function. + * will block, in milliseconds. Specifying a negative + * value means an infinite timeout. This parameter is + * passed to the poll() function. * @param[in] is_stderr A boolean to select the stderr stream. * * @return The number of bytes available for reading, * 0 if nothing is available (timeout elapsed), - * SSH_EOF on end of file, - * SSH_ERROR on error. + * `SSH_EOF` on end of file, + * `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. + * @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() */ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) { - ssh_session session; - ssh_buffer stdbuf; + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; struct ssh_channel_read_termination_struct ctx; size_t len; int rc; @@ -3290,7 +3392,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, @@ -3341,17 +3442,95 @@ ssh_session ssh_channel_get_session(ssh_channel channel) static int ssh_channel_exit_status_termination(void *c) { - ssh_channel channel = c; - if(channel->exit_status != -1 || - /* When a channel is closed, no exit status message can - * come anymore */ - (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || - channel->session->session_state == SSH_SESSION_STATE_ERROR) - return 1; - else + ssh_channel channel = c; + if (channel->exit.status || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + { + return 1; + } return 0; } +/** + * @brief Get the exit state of the channel (error code from the executed + * instruction or signal). + * + * @param[in] channel The channel to get the status from. + * + * @param[out] pexit_code A pointer to an uint32_t to store the exit status. + * + * @param[out] pexit_signal A pointer to store the exit signal as a string. + * The signal is without the SIG prefix, e.g. "TERM" or + * "KILL"). The caller has to free the memory. + * + * @param[out] pcore_dumped A pointer to store a boolean value if it dumped a + * core. + * + * @return `SSH_OK` on success, `SSH_AGAIN` if we don't have a + * status or an SSH 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 + * @see ssh_channel_exit_signal_callback + */ +int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped) +{ + ssh_session session = NULL; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + session = channel->session; + + rc = ssh_handle_packets_termination(channel->session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_exit_status_termination, + channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + /* If we don't have any kind of exit state, return SSH_AGAIN */ + if (!channel->exit.status) { + return SSH_AGAIN; + } + + if (pexit_code != NULL) { + *pexit_code = channel->exit.code; + } + + if (pexit_signal != NULL) { + *pexit_signal = NULL; + if (channel->exit.signal != NULL) { + *pexit_signal = strdup(channel->exit.signal); + if (pexit_signal == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + } + + if (pcore_dumped != NULL) { + *pcore_dumped = channel->exit.core_dumped; + } + + return SSH_OK; +} + /** * @brief Get the exit status of the channel (error code from the executed * instruction). @@ -3359,31 +3538,29 @@ static int ssh_channel_exit_status_termination(void *c) * @param[in] channel The channel to get the status from. * * @return The exit status, -1 if no exit status has been returned - * (yet), or SSH_ERROR on error. + * (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. + * `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 + * @deprecated Please use ssh_channel_exit_state() */ int ssh_channel_get_exit_status(ssh_channel channel) { - int rc; - if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { - return SSH_ERROR; - } - rc = ssh_handle_packets_termination(channel->session, - SSH_TIMEOUT_DEFAULT, - ssh_channel_exit_status_termination, - channel); - if (rc == SSH_ERROR || channel->session->session_state == - SSH_SESSION_STATE_ERROR) - return SSH_ERROR; - return channel->exit_status; + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return exit_status; } /* @@ -3400,50 +3577,54 @@ 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; + ssh_channel chan = NULL; + int i; + int j = 0; - for (i = 0; rchans[i] != NULL; i++) { - chan = rchans[i]; + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; - while (ssh_channel_is_open(chan) && ssh_socket_data_available(chan->session->socket)) { - ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); - } + while (ssh_channel_is_open(chan) && + ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); + } - if ((chan->stdout_buffer && ssh_buffer_get_len(chan->stdout_buffer) > 0) || - (chan->stderr_buffer && ssh_buffer_get_len(chan->stderr_buffer) > 0) || - chan->remote_eof) { - rout[j] = chan; - j++; + if ((chan->stdout_buffer && + ssh_buffer_get_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && + ssh_buffer_get_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } } - } - rout[j] = NULL; + rout[j] = NULL; - j = 0; - for(i = 0; wchans[i] != NULL; i++) { - chan = wchans[i]; - /* It's not our business to seek if the file descriptor is writable */ - if (ssh_socket_data_writable(chan->session->socket) && - ssh_channel_is_open(chan) && (chan->remote_window > 0)) { - wout[j] = chan; - j++; + j = 0; + for (i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } } - } - wout[j] = NULL; + wout[j] = NULL; - j = 0; - for (i = 0; echans[i] != NULL; i++) { - chan = echans[i]; + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; - if (!ssh_socket_is_open(chan->session->socket) || ssh_channel_is_closed(chan)) { - eout[j] = chan; - j++; + if (!ssh_socket_is_open(chan->session->socket) || + ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } } - } - eout[j] = NULL; + eout[j] = NULL; - return 0; + return 0; } /* Just count number of pointers in the array */ @@ -3474,131 +3655,142 @@ static size_t count_ptrs(ssh_channel *ptrs) * * @param[in] timeout Timeout as defined by select(2). * - * @return SSH_OK on a successful operation, SSH_EINTR if the + * @return `SSH_OK` on a successful operation, `SSH_EINTR` if the * select(2) syscall was interrupted, then relaunch the - * function, or SSH_ERROR on error. + * function, or `SSH_ERROR` on error. */ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct timeval * timeout) { - ssh_channel *rchans, *wchans, *echans; - ssh_channel dummy = NULL; - ssh_event event = NULL; - int rc; - int i; - int tm, tm_base; - int firstround=1; - struct ssh_timestamp ts; - - if (timeout != NULL) - tm_base = timeout->tv_sec * 1000 + timeout->tv_usec/1000; - else - tm_base = SSH_TIMEOUT_INFINITE; - ssh_timestamp_init(&ts); - tm = tm_base; - /* don't allow NULL pointers */ - if (readchans == NULL) { - readchans = &dummy; - } + ssh_channel *rchans = NULL, *wchans = NULL, *echans = NULL; + ssh_channel dummy = NULL; + ssh_event event = NULL; + int rc; + int i; + int tm, tm_base; + int firstround = 1; + struct ssh_timestamp ts; - if (writechans == NULL) { - writechans = &dummy; - } + if (timeout != NULL) + tm_base = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + else + tm_base = SSH_TIMEOUT_INFINITE; + ssh_timestamp_init(&ts); + tm = tm_base; + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } - if (exceptchans == NULL) { - exceptchans = &dummy; - } + if (writechans == NULL) { + writechans = &dummy; + } - if (readchans[0] == NULL && writechans[0] == NULL && exceptchans[0] == NULL) { - /* No channel to poll?? Go away! */ - return 0; - } + if (exceptchans == NULL) { + exceptchans = &dummy; + } - /* Prepare the outgoing temporary arrays */ - rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel)); - if (rchans == NULL) { - return SSH_ERROR; - } + if (readchans[0] == NULL && writechans[0] == NULL && + exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } - wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel)); - if (wchans == NULL) { - SAFE_FREE(rchans); - return SSH_ERROR; - } + /* Prepare the outgoing temporary arrays */ + rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel)); + if (rchans == NULL) { + return SSH_ERROR; + } - echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel)); - if (echans == NULL) { - SAFE_FREE(rchans); - SAFE_FREE(wchans); - return SSH_ERROR; - } + wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } - /* - * First, try without doing network stuff then, use the ssh_poll - * infrastructure to poll on all sessions. - */ - do { - channel_protocol_select(readchans, writechans, exceptchans, - rchans, wchans, echans); - if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { - /* At least one channel has an event */ - break; + echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; } - /* Add all channels' sessions right into an event object */ - if (event == NULL) { - event = ssh_event_new(); - if (event == NULL) { - SAFE_FREE(rchans); - SAFE_FREE(wchans); - SAFE_FREE(echans); - return SSH_ERROR; - } - for (i = 0; readchans[i] != NULL; i++) { - ssh_poll_get_default_ctx(readchans[i]->session); - ssh_event_add_session(event, readchans[i]->session); - } - for (i = 0; writechans[i] != NULL; i++) { - ssh_poll_get_default_ctx(writechans[i]->session); - ssh_event_add_session(event, writechans[i]->session); - } - for (i = 0; exceptchans[i] != NULL; i++) { - ssh_poll_get_default_ctx(exceptchans[i]->session); - ssh_event_add_session(event, exceptchans[i]->session); - } + /* + * First, try without doing network stuff then, use the ssh_poll + * infrastructure to poll on all sessions. + */ + do { + channel_protocol_select(readchans, + writechans, + exceptchans, + rchans, + wchans, + echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* At least one channel has an event */ + break; + } + /* Add all channels' sessions right into an event object */ + if (event == NULL) { + event = ssh_event_new(); + if (event == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + + return SSH_ERROR; + } + for (i = 0; readchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(readchans[i]->session); + ssh_event_add_session(event, readchans[i]->session); + } + for (i = 0; writechans[i] != NULL; i++) { + ssh_poll_get_default_ctx(writechans[i]->session); + ssh_event_add_session(event, writechans[i]->session); + } + for (i = 0; exceptchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(exceptchans[i]->session); + ssh_event_add_session(event, exceptchans[i]->session); + } + } + /* Get out if the timeout has elapsed */ + if (!firstround && ssh_timeout_elapsed(&ts, tm_base)) { + break; + } + /* Here we go */ + rc = ssh_event_dopoll(event, tm); + if (rc != SSH_OK) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + ssh_event_free(event); + return rc; + } + tm = ssh_timeout_update(&ts, tm_base); + firstround = 0; + } while (1); + + if (readchans != &dummy) { + memcpy(readchans, + rchans, + (count_ptrs(rchans) + 1) * sizeof(ssh_channel)); } - /* Get out if the timeout has elapsed */ - if (!firstround && ssh_timeout_elapsed(&ts, tm_base)){ - break; + if (writechans != &dummy) { + memcpy(writechans, + wchans, + (count_ptrs(wchans) + 1) * sizeof(ssh_channel)); } - /* Here we go */ - rc = ssh_event_dopoll(event,tm); - if (rc != SSH_OK){ - SAFE_FREE(rchans); - SAFE_FREE(wchans); - SAFE_FREE(echans); - ssh_event_free(event); - return rc; + if (exceptchans != &dummy) { + memcpy(exceptchans, + echans, + (count_ptrs(echans) + 1) * sizeof(ssh_channel)); } - tm = ssh_timeout_update(&ts, tm_base); - firstround=0; - } while(1); - - 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); - if(event) - ssh_event_free(event); - return 0; + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + if (event) + ssh_event_free(event); + return 0; } /** @@ -3662,60 +3854,60 @@ int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len * @param[in] localport The source port (your local computer). It's optional * and for logging purpose. * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. * - * @warning This function does not bind the local port and does not automatically - * forward the content of a socket to the channel. You still have to - * use ssh_channel_read and ssh_channel_write for this. + * @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 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) { - ssh_session session; - ssh_buffer payload = NULL; - int rc = SSH_ERROR; + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; - if(channel == NULL) { - return rc; - } - if(remotehost == NULL || sourcehost == NULL) { - ssh_set_error_invalid(channel->session); - return rc; - } + if (channel == NULL) { + return rc; + } + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } - session = channel->session; + session = channel->session; - if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) - goto pending; - payload = ssh_buffer_new(); - if (payload == NULL) { - ssh_set_error_oom(session); - goto error; - } - rc = ssh_buffer_pack(payload, - "sdsd", - remotehost, - remoteport, - sourcehost, - localport); - if (rc != SSH_OK){ - ssh_set_error_oom(session); - goto error; - } + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } pending: - rc = channel_open(channel, - "forwarded-tcpip", - CHANNEL_INITIAL_WINDOW, - CHANNEL_MAX_PACKET, - payload); + rc = channel_open(channel, + "forwarded-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); error: - SSH_BUFFER_FREE(payload); + SSH_BUFFER_FREE(payload); - return rc; + return rc; } /** @@ -3727,58 +3919,55 @@ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost * * @param[in] orig_port The source port (the local server). * - * @return SSH_OK on success, - * SSH_ERROR if an error occurred, - * SSH_AGAIN if in nonblocking mode and call has + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has * to be done again. - * @warning This function does not bind the local port and does not automatically - * forward the content of a socket to the channel. You still have to - * use shh_channel_read and ssh_channel_write for this. + * @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 shh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port) { - ssh_session session; - ssh_buffer payload = NULL; - int rc = SSH_ERROR; + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; - if(channel == NULL) { - return rc; - } - if(orig_addr == NULL) { - ssh_set_error_invalid(channel->session); - return rc; - } - session = channel->session; + if (channel == NULL) { + return rc; + } + if (orig_addr == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + session = channel->session; - if(channel->state != SSH_CHANNEL_STATE_NOT_OPEN) - goto pending; + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; - payload = ssh_buffer_new(); - if (payload == NULL) { - ssh_set_error_oom(session); - goto error; - } + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } - rc = ssh_buffer_pack(payload, - "sd", - orig_addr, - orig_port); - if (rc != SSH_OK) { - ssh_set_error_oom(session); - goto error; - } + rc = ssh_buffer_pack(payload, "sd", orig_addr, orig_port); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } pending: - rc = channel_open(channel, - "x11", - CHANNEL_INITIAL_WINDOW, - CHANNEL_MAX_PACKET, - payload); + rc = channel_open(channel, + "x11", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); error: - SSH_BUFFER_FREE(payload); + SSH_BUFFER_FREE(payload); - return rc; + return rc; } /** @@ -3791,7 +3980,7 @@ int ssh_channel_open_x11(ssh_channel channel, * * @param[in] exit_status The exit status to send * - * @return SSH_OK on success, SSH_ERROR if an error occurred. + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. */ int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { @@ -3835,7 +4024,7 @@ int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) * @param[in] errmsg A CRLF explanation text about the error condition * @param[in] lang The language used in the message (format: RFC 3066) * - * @return SSH_OK on success, SSH_ERROR if an error occurred + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred */ int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, int core, const char *errmsg, const char *lang) diff --git a/src/client.c b/src/client.c index a54bac60..0d01d373 100644 --- a/src/client.c +++ b/src/client.c @@ -30,14 +30,15 @@ #include #endif -#include "libssh/priv.h" -#include "libssh/ssh2.h" #include "libssh/buffer.h" -#include "libssh/packet.h" +#include "libssh/kex-gss.h" +#include "libssh/dh.h" #include "libssh/options.h" -#include "libssh/socket.h" +#include "libssh/packet.h" +#include "libssh/priv.h" #include "libssh/session.h" -#include "libssh/dh.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" #ifdef WITH_GEX #include "libssh/dh-gex.h" #endif /* WITH_GEX */ @@ -46,6 +47,13 @@ #include "libssh/misc.h" #include "libssh/pki.h" #include "libssh/kex.h" +#include "libssh/hybrid_mlkem.h" + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +extern int proxy_disconnect; +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ #define set_status(session, status) do {\ if (session->common.callbacks && session->common.callbacks->connect_status_function) \ @@ -185,13 +193,13 @@ int ssh_send_banner(ssh_session session, int server) int rc = SSH_ERROR; if (server == 1) { - if (session->opts.custombanner == NULL){ + if (session->server_opts.custombanner == NULL) { session->serverbanner = strdup(banner); if (session->serverbanner == NULL) { goto end; } } else { - len = strlen(session->opts.custombanner); + len = strlen(session->server_opts.custombanner); session->serverbanner = malloc(len + 8 + 1); if(session->serverbanner == NULL) { goto end; @@ -199,7 +207,7 @@ int ssh_send_banner(ssh_session session, int server) snprintf(session->serverbanner, len + 8 + 1, "SSH-2.0-%s", - session->opts.custombanner); + session->server_opts.custombanner); } snprintf(buffer, @@ -220,7 +228,7 @@ int ssh_send_banner(ssh_session session, int server) terminator); } - rc = ssh_socket_write(session->socket, buffer, strlen(buffer)); + rc = ssh_socket_write(session->socket, buffer, (uint32_t)strlen(buffer)); if (rc == SSH_ERROR) { goto end; } @@ -229,8 +237,8 @@ int ssh_send_banner(ssh_session session, int server) ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_OUT, buffer, - strlen(buffer), - strlen(buffer)); + (uint32_t)strlen(buffer), + (uint32_t)strlen(buffer)); } #endif @@ -248,45 +256,68 @@ int ssh_send_banner(ssh_session session, int server) */ int dh_handshake(ssh_session session) { - int rc = SSH_AGAIN; + 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); + 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) { + switch (session->dh_handshake_state) { case DH_STATE_INIT: - switch(session->next_crypto->kex_type){ + switch (session->next_crypto->kex_type) { +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_client_gss_kex_init(session); + break; +#endif case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: case SSH_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: - rc = ssh_client_dh_init(session); - break; + rc = ssh_client_dh_init(session); + break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: - rc = ssh_client_dhgex_init(session); - break; + rc = ssh_client_dhgex_init(session); + break; #endif /* WITH_GEX */ #ifdef HAVE_ECDH case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP384: case SSH_KEX_ECDH_SHA2_NISTP521: - rc = ssh_client_ecdh_init(session); - break; + rc = ssh_client_ecdh_init(session); + break; #endif #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: - rc = ssh_client_curve25519_init(session); - break; + rc = ssh_client_curve25519_init(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_client_sntrup761x25519_init(session); + break; #endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_client_hybrid_mlkem_init(session); + break; default: - rc = SSH_ERROR; - } + rc = SSH_ERROR; + } - break; + break; case DH_STATE_INIT_SENT: /* wait until ssh_packet_dh_reply is called */ break; @@ -294,15 +325,17 @@ int dh_handshake(ssh_session session) /* wait until ssh_packet_newkeys is called */ break; case DH_STATE_FINISHED: - return SSH_OK; + return SSH_OK; default: - ssh_set_error(session, SSH_FATAL, "Invalid state in dh_handshake(): %d", - session->dh_handshake_state); + ssh_set_error(session, + SSH_FATAL, + "Invalid state in dh_handshake(): %d", + session->dh_handshake_state); - return SSH_ERROR; - } + return SSH_ERROR; + } - return rc; + return rc; } static int ssh_service_request_termination(void *s) @@ -486,9 +519,7 @@ static void ssh_client_connection_callback(ssh_session session) 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)); } @@ -589,13 +620,24 @@ int ssh_connect(ssh_session session) if (session->opts.fd != SSH_INVALID_SOCKET) { session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; - ssh_socket_set_fd(session->socket, session->opts.fd); - ret = SSH_OK; + ret = ssh_socket_set_fd(session->socket, session->opts.fd); #ifndef _WIN32 +#ifdef HAVE_PTHREAD + } else if (ssh_libssh_proxy_jumps() && + ssh_list_count(session->opts.proxy_jumps) != 0) { + ret = ssh_socket_connect_proxyjump(session->socket); +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ } else if (session->opts.ProxyCommand != NULL) { +#ifdef WITH_EXEC ret = ssh_socket_connect_proxycommand(session->socket, session->opts.ProxyCommand); -#endif +#else + ssh_set_error(session, + SSH_FATAL, + "The libssh is built without support for proxy commands."); + ret = SSH_ERROR; +#endif /* WITH_EXEC */ } else { ret = ssh_socket_connect(session->socket, session->opts.host, @@ -755,7 +797,6 @@ ssh_session_set_disconnect_message(ssh_session session, const char *message) return SSH_OK; } - /** * @brief Disconnect from a session (client or server). * @@ -770,13 +811,23 @@ ssh_session_set_disconnect_message(ssh_session session, const char *message) void ssh_disconnect(ssh_session session) { - struct ssh_iterator *it; + struct ssh_iterator *it = NULL; int rc; if (session == NULL) { return; } +#ifndef _WIN32 +#ifdef HAVE_PTHREAD + /* Only send the disconnect to all other threads when the root session calls + * ssh_disconnect() */ + if (session->proxy_root) { + proxy_disconnect = 1; + } +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + if (session->disconnect_message == NULL) { session->disconnect_message = strdup("Bye Bye") ; if (session->disconnect_message == NULL) { @@ -798,10 +849,7 @@ ssh_disconnect(ssh_session session) } ssh_packet_send(session); - /* Do not close the socket, if the fd was set via options. */ - if (session->opts.fd == SSH_INVALID_SOCKET) { - ssh_socket_close(session->socket); - } + ssh_session_socket_close(session); } error: @@ -814,6 +862,7 @@ ssh_disconnect(ssh_session session) session->opts.fd = SSH_INVALID_SOCKET; session->session_state = SSH_SESSION_STATE_DISCONNECTED; session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; while ((it = ssh_list_get_iterator(session->channels)) != NULL) { ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); @@ -873,7 +922,7 @@ ssh_disconnect(ssh_session session) */ const char *ssh_copyright(void) { - return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2023 " + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2026 " "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 156b146e..0af0f798 100644 --- a/src/config.c +++ b/src/config.c @@ -39,6 +39,11 @@ # include # include # include +# include +# include +#endif +#ifdef HAVE_IFADDRS_H +#include #endif #include "libssh/config_parser.h" @@ -55,100 +60,104 @@ struct ssh_config_keyword_table_s { const char *name; enum ssh_config_opcode_e opcode; + bool cli_supported; }; static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { - { "host", SOC_HOST }, - { "match", SOC_MATCH }, - { "hostname", SOC_HOSTNAME }, - { "port", SOC_PORT }, - { "user", SOC_USERNAME }, - { "identityfile", SOC_IDENTITY }, - { "ciphers", SOC_CIPHERS }, - { "macs", SOC_MACS }, - { "compression", SOC_COMPRESSION }, - { "connecttimeout", SOC_TIMEOUT }, - { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, - { "userknownhostsfile", SOC_KNOWNHOSTS }, - { "proxycommand", SOC_PROXYCOMMAND }, - { "gssapiserveridentity", SOC_GSSAPISERVERIDENTITY }, - { "gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY }, - { "gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS }, - { "include", SOC_INCLUDE }, - { "bindaddress", SOC_BINDADDRESS}, - { "globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE}, - { "loglevel", SOC_LOGLEVEL}, - { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS}, - { "kexalgorithms", SOC_KEXALGORITHMS}, - { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION}, - { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION}, - { "passwordauthentication", SOC_PASSWORDAUTHENTICATION}, - { "pubkeyauthentication", SOC_PUBKEYAUTHENTICATION}, - { "addkeystoagent", SOC_UNSUPPORTED}, - { "addressfamily", SOC_UNSUPPORTED}, - { "batchmode", SOC_UNSUPPORTED}, - { "canonicaldomains", SOC_UNSUPPORTED}, - { "canonicalizefallbacklocal", SOC_UNSUPPORTED}, - { "canonicalizehostname", SOC_UNSUPPORTED}, - { "canonicalizemaxdots", SOC_UNSUPPORTED}, - { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, - { "certificatefile", SOC_UNSUPPORTED}, - { "kbdinteractiveauthentication", SOC_UNSUPPORTED}, - { "checkhostip", SOC_UNSUPPORTED}, - { "connectionattempts", SOC_UNSUPPORTED}, - { "enablesshkeysign", SOC_UNSUPPORTED}, - { "fingerprinthash", SOC_UNSUPPORTED}, - { "forwardagent", SOC_UNSUPPORTED}, - { "hashknownhosts", SOC_UNSUPPORTED}, - { "hostbasedauthentication", SOC_UNSUPPORTED}, - { "hostbasedacceptedalgorithms", SOC_UNSUPPORTED}, - { "hostkeyalias", SOC_UNSUPPORTED}, - { "identitiesonly", SOC_IDENTITIESONLY}, - { "identityagent", SOC_IDENTITYAGENT}, - { "ipqos", SOC_UNSUPPORTED}, - { "kbdinteractivedevices", SOC_UNSUPPORTED}, - { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED}, - { "numberofpasswordprompts", SOC_UNSUPPORTED}, - { "pkcs11provider", SOC_UNSUPPORTED}, - { "preferredauthentications", SOC_UNSUPPORTED}, - { "proxyjump", SOC_PROXYJUMP}, - { "proxyusefdpass", SOC_UNSUPPORTED}, - { "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES}, - { "rekeylimit", SOC_REKEYLIMIT}, - { "remotecommand", SOC_UNSUPPORTED}, - { "revokedhostkeys", SOC_UNSUPPORTED}, - { "serveralivecountmax", SOC_UNSUPPORTED}, - { "serveraliveinterval", SOC_UNSUPPORTED}, - { "streamlocalbindmask", SOC_UNSUPPORTED}, - { "streamlocalbindunlink", SOC_UNSUPPORTED}, - { "syslogfacility", SOC_UNSUPPORTED}, - { "tcpkeepalive", SOC_UNSUPPORTED}, - { "updatehostkeys", SOC_UNSUPPORTED}, - { "verifyhostkeydns", SOC_UNSUPPORTED}, - { "visualhostkey", SOC_UNSUPPORTED}, - { "clearallforwardings", SOC_NA}, - { "controlmaster", SOC_CONTROLMASTER}, - { "controlpersist", SOC_NA}, - { "controlpath", SOC_CONTROLPATH}, - { "dynamicforward", SOC_NA}, - { "escapechar", SOC_NA}, - { "exitonforwardfailure", SOC_NA}, - { "forwardx11", SOC_NA}, - { "forwardx11timeout", SOC_NA}, - { "forwardx11trusted", SOC_NA}, - { "gatewayports", SOC_NA}, - { "ignoreunknown", SOC_NA}, - { "localcommand", SOC_NA}, - { "localforward", SOC_NA}, - { "permitlocalcommand", SOC_NA}, - { "remoteforward", SOC_NA}, - { "requesttty", SOC_NA}, - { "sendenv", SOC_NA}, - { "tunnel", SOC_NA}, - { "tunneldevice", SOC_NA}, - { "xauthlocation", SOC_NA}, - { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES}, - { NULL, SOC_UNKNOWN } + {"host", SOC_HOST, true}, + {"match", SOC_MATCH, false}, + {"hostname", SOC_HOSTNAME, true}, + {"port", SOC_PORT, true}, + {"user", SOC_USERNAME, true}, + {"identityfile", SOC_IDENTITY, true}, + {"ciphers", SOC_CIPHERS, true}, + {"macs", SOC_MACS, true}, + {"compression", SOC_COMPRESSION, true}, + {"connecttimeout", SOC_TIMEOUT, true}, + {"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true}, + {"userknownhostsfile", SOC_KNOWNHOSTS, true}, + {"proxycommand", SOC_PROXYCOMMAND, true}, + {"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false}, + {"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false}, + {"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true}, + {"include", SOC_INCLUDE, true}, + {"bindaddress", SOC_BINDADDRESS, true}, + {"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true}, + {"loglevel", SOC_LOGLEVEL, true}, + {"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true}, + {"kexalgorithms", SOC_KEXALGORITHMS, true}, + {"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true}, + {"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true}, + {"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true}, + {"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true}, + {"addkeystoagent", SOC_UNSUPPORTED, true}, + {"addressfamily", SOC_ADDRESSFAMILY, true}, + {"batchmode", SOC_UNSUPPORTED, true}, + {"canonicaldomains", SOC_UNSUPPORTED, true}, + {"canonicalizefallbacklocal", SOC_UNSUPPORTED, true}, + {"canonicalizehostname", SOC_UNSUPPORTED, true}, + {"canonicalizemaxdots", SOC_UNSUPPORTED, true}, + {"canonicalizepermittedcnames", SOC_UNSUPPORTED, true}, + {"certificatefile", SOC_CERTIFICATE, true}, + {"kbdinteractiveauthentication", SOC_UNSUPPORTED, true}, + {"checkhostip", SOC_UNSUPPORTED, true}, + {"connectionattempts", SOC_UNSUPPORTED, true}, + {"enablesshkeysign", SOC_UNSUPPORTED, true}, + {"fingerprinthash", SOC_UNSUPPORTED, true}, + {"forwardagent", SOC_UNSUPPORTED, true}, + {"hashknownhosts", SOC_UNSUPPORTED, true}, + {"hostbasedauthentication", SOC_UNSUPPORTED, true}, + {"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true}, + {"hostkeyalias", SOC_UNSUPPORTED, true}, + {"identitiesonly", SOC_IDENTITIESONLY, true}, + {"identityagent", SOC_IDENTITYAGENT, true}, + {"ipqos", SOC_UNSUPPORTED, true}, + {"kbdinteractivedevices", SOC_UNSUPPORTED, true}, + {"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true}, + {"numberofpasswordprompts", SOC_UNSUPPORTED, true}, + {"pkcs11provider", SOC_UNSUPPORTED, true}, + {"preferredauthentications", SOC_UNSUPPORTED, true}, + {"proxyjump", SOC_PROXYJUMP, true}, + {"proxyusefdpass", SOC_UNSUPPORTED, true}, + {"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"rekeylimit", SOC_REKEYLIMIT, true}, + {"remotecommand", SOC_UNSUPPORTED, true}, + {"revokedhostkeys", SOC_UNSUPPORTED, true}, + {"serveralivecountmax", SOC_UNSUPPORTED, true}, + {"serveraliveinterval", SOC_UNSUPPORTED, true}, + {"streamlocalbindmask", SOC_UNSUPPORTED, true}, + {"streamlocalbindunlink", SOC_UNSUPPORTED, true}, + {"syslogfacility", SOC_UNSUPPORTED, true}, + {"tcpkeepalive", SOC_UNSUPPORTED, true}, + {"updatehostkeys", SOC_UNSUPPORTED, true}, + {"verifyhostkeydns", SOC_UNSUPPORTED, true}, + {"visualhostkey", SOC_UNSUPPORTED, true}, + {"clearallforwardings", SOC_NA, true}, + {"controlmaster", SOC_NA, true}, + {"controlpersist", SOC_NA, true}, + {"controlpath", SOC_NA, true}, + {"dynamicforward", SOC_NA, true}, + {"escapechar", SOC_NA, true}, + {"exitonforwardfailure", SOC_NA, true}, + {"forwardx11", SOC_NA, true}, + {"forwardx11timeout", SOC_NA, true}, + {"forwardx11trusted", SOC_NA, true}, + {"gatewayports", SOC_NA, true}, + {"ignoreunknown", SOC_NA, true}, + {"localcommand", SOC_NA, true}, + {"localforward", SOC_NA, true}, + {"permitlocalcommand", SOC_NA, true}, + {"remoteforward", SOC_NA, true}, + {"requesttty", SOC_NA, true}, + {"sendenv", SOC_NA, true}, + {"tunnel", SOC_NA, true}, + {"tunneldevice", SOC_NA, true}, + {"xauthlocation", SOC_NA, true}, + {"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true}, + {"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true}, + {"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true}, + {NULL, SOC_UNKNOWN, false}, }; enum ssh_config_match_e { @@ -160,7 +169,8 @@ enum ssh_config_match_e { MATCH_HOST, MATCH_ORIGINALHOST, MATCH_USER, - MATCH_LOCALUSER + MATCH_LOCALUSER, + MATCH_LOCALNETWORK }; struct ssh_config_match_keyword_table_s { @@ -168,31 +178,62 @@ struct ssh_config_match_keyword_table_s { enum ssh_config_match_e opcode; }; -static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = { - { "all", MATCH_ALL }, - { "canonical", MATCH_CANONICAL }, - { "final", MATCH_FINAL }, - { "exec", MATCH_EXEC }, - { "host", MATCH_HOST }, - { "originalhost", MATCH_ORIGINALHOST }, - { "user", MATCH_USER }, - { "localuser", MATCH_LOCALUSER }, - { NULL, MATCH_UNKNOWN }, +static struct ssh_config_match_keyword_table_s + ssh_config_match_keyword_table[] = { + {"all", MATCH_ALL}, + {"canonical", MATCH_CANONICAL}, + {"final", MATCH_FINAL}, + {"exec", MATCH_EXEC}, + {"host", MATCH_HOST}, + {"originalhost", MATCH_ORIGINALHOST}, + {"user", MATCH_USER}, + {"localuser", MATCH_LOCALUSER}, + {"localnetwork", MATCH_LOCALNETWORK}, + {NULL, MATCH_UNKNOWN}, }; -static int ssh_config_parse_line(ssh_session session, const char *line, - unsigned int count, int *parsing, unsigned int depth, bool global); +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global); + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown); + +int ssh_config_parse_line_cli(ssh_session session, const char *line); + +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) +{ + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { + return ssh_config_keyword_table[i].opcode; + } + } + + return SOC_UNKNOWN; +} -static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { - int i; +static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode) +{ + int i; - for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { - if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { - return ssh_config_keyword_table[i].opcode; + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (opcode == ssh_config_keyword_table[i].opcode) { + return ssh_config_keyword_table[i].cli_supported; + } } - } - return SOC_UNKNOWN; + return false; } #define LIBSSH_CONF_MAX_DEPTH 16 @@ -203,7 +244,7 @@ local_parse_file(ssh_session session, unsigned int depth, bool global) { - FILE *f; + FILE *f = NULL; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; @@ -215,10 +256,9 @@ local_parse_file(ssh_session session, return; } - f = fopen(filename, "r"); + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); if (f == NULL) { - SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", - filename); + /* The underlying function logs the reasons */ return; } @@ -243,9 +283,14 @@ static void local_parse_glob(ssh_session session, unsigned int depth, bool global) { +#ifdef __FILC__ + /* FilC's libc provides glob_t without the non-standard gl_flags member. */ + glob_t globbuf = {0}; +#else glob_t globbuf = { .gl_flags = 0, }; +#endif int rt; size_t i; @@ -299,20 +344,8 @@ ssh_config_match(char *value, const char *pattern, bool negate) return result; } -#ifdef _WIN32 -static int -ssh_match_exec(ssh_session session, const char *command, bool negate) -{ - (void) session; - (void) command; - (void) negate; - - SSH_LOG(SSH_LOG_TRACE, "Unsupported 'exec' command on Windows '%s'", - command); - return 0; -} -#else /* _WIN32 */ - +#ifdef WITH_EXEC +/* FIXME reuse the ssh_execute_command() from socket.c */ static int ssh_exec_shell(char *cmd) { @@ -425,12 +458,33 @@ ssh_match_exec(ssh_session session, const char *command, bool negate) free(cmd); return result; } -#endif /* _WIN32 */ +#else +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + (void)session; + (void)command; + (void)negate; -/* @brief: Parse the ProxyJump configuration line and if parsing, + SSH_LOG(SSH_LOG_TRACE, + "Unsupported 'exec' command on Windows '%s'", + command); + return 0; +} +#endif /* WITH_EXEC */ + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure */ -static int +int ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) { char *c = NULL, *cp = NULL, *endp = NULL; @@ -439,12 +493,20 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) char *port = NULL; char *next = NULL; int cmp, rv = SSH_ERROR; + struct ssh_jump_info_struct *jump_host = NULL; bool parse_entry = do_parsing; + bool libssh_proxy_jump = ssh_libssh_proxy_jumps(); + if (do_parsing) { + SAFE_FREE(session->opts.proxy_jumps_str); + ssh_proxyjumps_free(session->opts.proxy_jumps); + } /* Special value none disables the proxy */ cmp = strcasecmp(s, "none"); - if (cmp == 0 && do_parsing) { - ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s); + if (cmp == 0) { + if (!libssh_proxy_jump && do_parsing) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s); + } return SSH_OK; } @@ -455,6 +517,17 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) return SSH_ERROR; } + if (do_parsing) { + /* Store the whole string in session */ + SAFE_FREE(session->opts.proxy_jumps_str); + session->opts.proxy_jumps_str = strdup(s); + if (session->opts.proxy_jumps_str == NULL) { + free(c); + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + cp = c; do { endp = strchr(cp, ','); @@ -462,25 +535,65 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) /* Split out the token */ *endp = '\0'; } - if (parse_entry) { + if (parse_entry && libssh_proxy_jump) { + jump_host = calloc(1, sizeof(struct ssh_jump_info_struct)); + if (jump_host == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + goto out; + } + + rv = ssh_config_parse_uri(cp, + &jump_host->username, + &jump_host->hostname, + &port, + false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + SAFE_FREE(jump_host); + goto out; + } + if (port == NULL) { + jump_host->port = 22; + } else { + jump_host->port = strtol(port, NULL, 10); + SAFE_FREE(port); + } + + /* Prepend because we will recursively proxy jump */ + rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + SAFE_FREE(jump_host); + goto out; + } + } else 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); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + goto out; + } /* The rest of the list needs to be passed on */ if (endp != NULL) { next = strdup(endp + 1); if (next == NULL) { ssh_set_error_oom(session); rv = SSH_ERROR; + goto out; } } } 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) { + ssh_set_error_invalid(session); + goto out; + } } - if (rv != SSH_OK) { - goto out; + if (!libssh_proxy_jump) { + parse_entry = 0; } - parse_entry = 0; if (endp != NULL) { cp = endp + 1; } else { @@ -488,7 +601,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } } while (cp != NULL); - if (hostname != NULL && do_parsing) { + if (!libssh_proxy_jump && hostname != NULL && do_parsing) { char com[512] = {0}; rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", @@ -504,11 +617,19 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) rv = SSH_ERROR; goto out; } - ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com); + rv = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } } + rv = SSH_OK; out: + if (rv != SSH_OK) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } SAFE_FREE(username); SAFE_FREE(hostname); SAFE_FREE(port); @@ -572,13 +693,169 @@ ssh_config_make_absolute(ssh_session session, return out; } +#ifdef HAVE_IFADDRS_H +/** + * @brief Checks if host address matches the local network specified. + * + * Verify whether a local network interface address matches any of the CIDR + * patterns. + * + * @param addrlist The CIDR pattern-list to be checked, can contain both + * IPv4 and IPv6 addresses and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param negate The negate condition. The return value is negated + * (returns 1 instead of 0 and vice versa). + * + * @return 1 if match found. + * @return 0 if no match found. + * @return -1 on errors. + */ static int -ssh_config_parse_line(ssh_session session, - const char *line, - unsigned int count, - int *parsing, - unsigned int depth, - bool global) +ssh_match_localnetwork(const char *addrlist, bool negate) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + int r, found = 0; + char address[NI_MAXHOST], err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socklen_t sa_len; + + r = getifaddrs(&ifaddrs); + if (r != 0) { + SSH_LOG(SSH_LOG_WARN, + "Match localnetwork: getifaddrs() failed: %s", + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Interface %s: unsupported address family %d", + ifa->ifa_name, + ifa->ifa_addr->sa_family); + continue; + } + + r = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (r != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Interface %s getnameinfo failed: %s", + ifa->ifa_name, + gai_strerror(r)); + continue; + } + SSH_LOG(SSH_LOG_TRACE, + "Interface %s address %s", + ifa->ifa_name, + address); + + r = match_cidr_address_list(address, + addrlist, + ifa->ifa_addr->sa_family); + if (r == 1) { + SSH_LOG(SSH_LOG_TRACE, + "Matched interface %s: address %s in %s", + ifa->ifa_name, + address, + addrlist); + found = 1; + break; + } + } + + freeifaddrs(ifaddrs); + + return (found == (negate ? 0 : 1)); +} +#endif /* HAVE_IFADDRS_H */ + +static enum ssh_options_e +ssh_config_get_auth_option(enum ssh_config_opcode_e opcode) +{ + struct auth_option_map { + enum ssh_config_opcode_e opcode; + const char *name; + enum ssh_options_e option; + }; + + static struct auth_option_map auth_options[] = { + { + SOC_GSSAPIAUTHENTICATION, + "GSSAPIAuthentication", + SSH_OPTIONS_GSSAPI_AUTH, + }, + { + SOC_KBDINTERACTIVEAUTHENTICATION, + "KbdInteractiveAuthentication", + SSH_OPTIONS_KBDINT_AUTH, + }, + { + SOC_PASSWORDAUTHENTICATION, + "PasswordAuthentication", + SSH_OPTIONS_PASSWORD_AUTH, + }, + { + SOC_PUBKEYAUTHENTICATION, + "PubkeyAuthentication", + SSH_OPTIONS_PUBKEY_AUTH, + }, + {0, NULL, 0}, + }; + + for (struct auth_option_map *map = auth_options; map->name != NULL; map++) { + if (map->opcode == opcode) { + return map->option; + } + } + return -1; +} + +#define CHECK_COND_OR_FAIL(cond, error_message) \ + if ((cond)) { \ + SSH_LOG(SSH_LOG_DEBUG, \ + "line %d: %s: %s", \ + count, \ + error_message, \ + keyword); \ + if (fail_on_unknown) { \ + ssh_set_error(session, \ + SSH_FATAL, \ + is_cli ? "%s '%s' value on CLI" \ + : "%s '%s' value at line %d", \ + error_message, \ + keyword, \ + is_cli ? 0 : count); \ + SAFE_FREE(x); \ + return SSH_ERROR; \ + } \ + break; \ + } + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown) { enum ssh_config_opcode_e opcode; const char *p = NULL, *p2 = NULL; @@ -593,6 +870,9 @@ ssh_config_parse_line(ssh_session session, /* Ignore empty lines */ if (line == NULL || *line == '\0') { + if (is_cli) { + return SSH_ERROR; + } return 0; } @@ -618,11 +898,22 @@ ssh_config_parse_line(ssh_session session, } opcode = ssh_config_get_opcode(keyword); + if (is_cli && !ssh_config_is_cli_supported(opcode)) { + ssh_set_error( + session, + SSH_FATAL, + "Option '%s' is not supported in command-line configuration", + keyword); + SAFE_FREE(x); + return SSH_ERROR; + } + if (*parsing == 1 && opcode != SOC_HOST && opcode != SOC_MATCH && opcode != SOC_INCLUDE && opcode != SOC_IDENTITY && + opcode != SOC_CERTIFICATE && opcode > SOC_UNSUPPORTED && opcode < SOC_MAX) { /* Ignore all unknown types here */ /* Skip all the options that were already applied */ @@ -731,9 +1022,11 @@ ssh_config_parse_line(ssh_session session, /* Here we match only one argument */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL || p[0] == '\0') { - ssh_set_error(session, SSH_FATAL, - "line %d: ERROR - Match user keyword " - "requires argument", count); + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match localuser keyword " + "requires argument", + count); SAFE_FREE(x); return -1; } @@ -794,19 +1087,68 @@ ssh_config_parse_line(ssh_session session, args++; break; - case MATCH_UNKNOWN: - default: - ssh_set_error(session, SSH_FATAL, - "ERROR - Unknown argument '%s' for Match keyword", p); + case MATCH_LOCALNETWORK: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match local network keyword" + "requires argument", + count); + SAFE_FREE(x); + return -1; + } +#ifdef HAVE_IFADDRS_H + rv = match_cidr_address_list(NULL, p, -1); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - List invalid entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + rv = ssh_match_localnetwork(p, negate); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Error while retrieving " + "network interface information -" + " List entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + + result &= rv; +#else /* HAVE_IFADDRS_H */ + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - match localnetwork " + "not supported on this platform", + count); SAFE_FREE(x); return -1; +#endif /* HAVE_IFADDRS_H */ + args++; + break; + + case MATCH_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown argument '%s' for Match keyword. Not matching", + p); + result = 0; + break; } } while (p != NULL && p[0] != '\0'); if (args == 0) { - ssh_set_error(session, SSH_FATAL, - "ERROR - Match keyword requires an argument"); - SAFE_FREE(x); - return -1; + SSH_LOG(SSH_LOG_WARN, + "ERROR - Match keyword requires an argument. Not matching"); + result = 0; } *parsing = result; break; @@ -836,82 +1178,91 @@ ssh_config_parse_line(ssh_session session, } case SOC_HOSTNAME: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - char *z = ssh_path_expand_escape(session, p); - if (z == NULL) { - z = strdup(p); - } - ssh_options_set(session, SSH_OPTIONS_HOST, z); - free(z); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + char *z = ssh_path_expand_escape(session, p); + if (z == NULL) { + z = strdup(p); + } + ssh_options_set(session, SSH_OPTIONS_HOST, z); + free(z); } break; case SOC_PORT: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); } break; case SOC_USERNAME: - if (session->opts.username == NULL) { - p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_USER, p); - } - } - break; + } + break; case SOC_IDENTITY: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); } break; case SOC_CIPHERS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); - ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); } break; case SOC_MACS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p); - ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p); + ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p); } break; case SOC_COMPRESSION: i = ssh_config_get_yesno(&s, -1); - if (i >= 0 && *parsing) { - if (i) { - ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); - } else { - ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); - } + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + if (i) { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + } else { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + } } break; case SOC_TIMEOUT: l = ssh_config_get_long(&s, -1); - if (l >= 0 && *parsing) { - ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l); + CHECK_COND_OR_FAIL(l < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l); } break; case SOC_STRICTHOSTKEYCHECK: i = ssh_config_get_yesno(&s, -1); - if (i >= 0 && *parsing) { - ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); } break; case SOC_KNOWNHOSTS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); } break; case SOC_PROXYCOMMAND: p = ssh_config_get_cmd(&s); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); /* We share the seen value with the ProxyJump */ - if (p && *parsing && !seen[SOC_PROXYJUMP]) { - ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); + if (*parsing && !seen[SOC_PROXYJUMP]) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); } break; case SOC_PROXYJUMP: @@ -921,7 +1272,8 @@ ssh_config_parse_line(ssh_session session, return -1; } /* We share the seen value with the ProxyCommand */ - rv = ssh_config_parse_proxy_jump(session, p, + rv = ssh_config_parse_proxy_jump(session, + p, (*parsing && !seen[SOC_PROXYCOMMAND])); if (rv != SSH_OK) { SAFE_FREE(x); @@ -930,37 +1282,43 @@ ssh_config_parse_line(ssh_session session, break; case SOC_GSSAPISERVERIDENTITY: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p); } break; case SOC_GSSAPICLIENTIDENTITY: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p); } break; case SOC_GSSAPIDELEGATECREDENTIALS: i = ssh_config_get_yesno(&s, -1); - if (i >=0 && *parsing) { - ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i); } break; case SOC_BINDADDRESS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_BINDADDR, p); } break; case SOC_GLOBALKNOWNHOSTSFILE: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p); } break; case SOC_LOGLEVEL: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { int value = -1; if (strcasecmp(p, "quiet") == 0) { @@ -978,6 +1336,7 @@ ssh_config_parse_line(ssh_session session, strcasecmp(p, "DEBUG3") == 0) { value = SSH_LOG_TRACE; } + CHECK_COND_OR_FAIL(value == -1, "Invalid value"); if (value != -1) { ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value); } @@ -985,19 +1344,22 @@ ssh_config_parse_line(ssh_session session, break; case SOC_HOSTKEYALGORITHMS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); } break; case SOC_PUBKEYACCEPTEDKEYTYPES: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); } break; case SOC_KEXALGORITHMS: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p); } break; @@ -1005,6 +1367,7 @@ ssh_config_parse_line(ssh_session session, /* Parse the data limit */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing data limit"); break; } else if (strcmp(p, "default") == 0) { /* Default rekey limits enforced automatically */ @@ -1013,8 +1376,7 @@ ssh_config_parse_line(ssh_session session, char *endp = NULL; ll = strtoll(p, &endp, 10); if (p == endp || ll < 0) { - /* No number or negative */ - SSH_LOG(SSH_LOG_TRACE, "Invalid argument to rekey limit"); + CHECK_COND_OR_FAIL(1, "Invalid data limit"); break; } switch (*endp) { @@ -1052,19 +1414,19 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != ' ' && *endp != '\0') { - SSH_LOG(SSH_LOG_TRACE, - "Invalid trailing characters after the rekey limit: %s", - endp); + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); break; } } - if (ll > -1 && *parsing) { + CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit"); + if (*parsing) { uint64_t v = (uint64_t)ll; ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v); } /* Parse the time limit */ p = ssh_config_get_str_tok(&s, NULL); if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing time limit"); break; } else if (strcmp(p, "none") == 0) { ll = 0; @@ -1073,7 +1435,7 @@ 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_TRACE, "Invalid argument to rekey limit"); + CHECK_COND_OR_FAIL(1, "Invalid time limit"); break; } switch (*endp) { @@ -1126,11 +1488,11 @@ ssh_config_parse_line(ssh_session session, break; } if (*endp != '\0') { - SSH_LOG(SSH_LOG_TRACE, "Invalid trailing characters after the" - " rekey limit: %s", endp); + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); break; } } + CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit"); if (ll > -1 && *parsing) { uint32_t v = (uint32_t)ll; ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v); @@ -1139,56 +1501,44 @@ ssh_config_parse_line(ssh_session session, case SOC_GSSAPIAUTHENTICATION: case SOC_KBDINTERACTIVEAUTHENTICATION: case SOC_PASSWORDAUTHENTICATION: - case SOC_PUBKEYAUTHENTICATION: + case SOC_PUBKEYAUTHENTICATION: { + enum ssh_options_e option = ssh_config_get_auth_option(opcode); i = ssh_config_get_yesno(&s, 0); - if (i>=0 && *parsing) { - switch(opcode){ - case SOC_GSSAPIAUTHENTICATION: - ssh_options_set(session, SSH_OPTIONS_GSSAPI_AUTH, &i); - break; - case SOC_KBDINTERACTIVEAUTHENTICATION: - ssh_options_set(session, SSH_OPTIONS_KBDINT_AUTH, &i); - break; - case SOC_PASSWORDAUTHENTICATION: - ssh_options_set(session, SSH_OPTIONS_PASSWORD_AUTH, &i); - break; - case SOC_PUBKEYAUTHENTICATION: - ssh_options_set(session, SSH_OPTIONS_PUBKEY_AUTH, &i); - break; - /* make gcc happy */ - default: - break; - } + + CHECK_COND_OR_FAIL(i < 0, "Authentication option"); + if (*parsing) { + ssh_options_set(session, option, &i); } break; + } case SOC_NA: - SSH_LOG(SSH_LOG_TRACE, "Unapplicable option: %s, line: %d", - keyword, count); - break; + CHECK_COND_OR_FAIL(1, "Unapplicable option"); + break; case SOC_UNSUPPORTED: - SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d", - keyword, count); - break; + CHECK_COND_OR_FAIL(1, "Unsupported option"); + break; case SOC_UNKNOWN: - SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", - keyword, count); - break; + CHECK_COND_OR_FAIL(1, "Unknown option"); + break; case SOC_IDENTITYAGENT: p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*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); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*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) { + CHECK_COND_OR_FAIL(p == NULL, "ControlMaster"); + if (*parsing) { int value = -1; if (strcasecmp(p, "auto") == 0) { @@ -1203,6 +1553,7 @@ ssh_config_parse_line(ssh_session session, value = SSH_CONTROL_MASTER_ASK; } + CHECK_COND_OR_FAIL(value == -1, "Invalid argument"); if (value != -1) { ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); } @@ -1218,6 +1569,66 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); } break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; + case SOC_GSSAPIKEYEXCHANGE: { + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = (i == 1) ? true : false; + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b); + } + break; + } + case SOC_GSSAPIKEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p); + } + break; + case SOC_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument"); + if (*parsing) { + i = (int)l; + ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i); + } + break; + case SOC_ADDRESSFAMILY: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "line %d: no argument after keyword \"addressfamily\"", + count); + SAFE_FREE(x); + return SSH_ERROR; + } + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "any") == 0) { + value = SSH_ADDRESS_FAMILY_ANY; + } else if (strcasecmp(p, "inet") == 0) { + value = SSH_ADDRESS_FAMILY_INET; + } else if (strcasecmp(p, "inet6") == 0) { + value = SSH_ADDRESS_FAMILY_INET6; + } else { + SSH_LOG(SSH_LOG_WARNING, + "line %d: invalid argument \"%s\"", + count, + p); + SAFE_FREE(x); + return SSH_ERROR; + } + ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); @@ -1230,45 +1641,100 @@ ssh_config_parse_line(ssh_session session, return 0; } -/* @brief Parse configuration file and set the options to the given session +#undef CHECK_COND_OR_FAIL + +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global) +{ + return ssh_config_parse_line_internal(session, + line, + count, + parsing, + depth, + global, + false, + false); +} + +int ssh_config_parse_line_cli(ssh_session session, const char *line) +{ + int parsing = 1; + return ssh_config_parse_line_internal(session, + line, + 0, + &parsing, + 0, + false, + true, + true); +} + +/* @brief Parse configuration from a file pointer * * @params[in] session The ssh session - * @params[in] filename The path to the ssh configuration file + * @params[in] fp A valid file pointer + * @params[in] global Whether the config is global or not * * @returns 0 on successful parsing the configuration file, -1 on error */ -int ssh_config_parse_file(ssh_session session, const char *filename) +int ssh_config_parse(ssh_session session, FILE *fp, bool global) { char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; - FILE *f; int parsing, rv; + + parsing = 1; + while (fgets(line, sizeof(line), fp)) { + count++; + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); + if (rv < 0) { + return -1; + } + } + + return 0; +} + +/* @brief Parse configuration file and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] filename The path to the ssh configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse_file(ssh_session session, const char *filename) +{ + FILE *fp = NULL; + int rv; bool global = 0; - f = fopen(filename, "r"); - if (f == NULL) { + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + /* The underlying function logs the reasons */ return 0; } rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); +#ifdef USR_GLOBAL_CLIENT_CONFIG + if (rv != 0) { + rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG); + } +#endif + 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, 0, global); - if (rv < 0) { - fclose(f); - return -1; - } - } + rv = ssh_config_parse(session, fp, global); - fclose(f); - return 0; + fclose(fp); + return rv; } /* @brief Parse configuration string and set the options to the given session @@ -1283,7 +1749,8 @@ int ssh_config_parse_string(ssh_session session, const char *input) { char line[MAX_LINE_SIZE] = {0}; const char *c = input, *line_start = input; - unsigned int line_num = 0, line_len; + unsigned int line_num = 0; + size_t line_len; int parsing, rv; SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:"); @@ -1305,8 +1772,10 @@ int ssh_config_parse_string(ssh_session session, const char *input) } line_len = c - line_start; if (line_len > MAX_LINE_SIZE - 1) { - SSH_LOG(SSH_LOG_TRACE, "Line %u too long: %u characters", - line_num, line_len); + SSH_LOG(SSH_LOG_TRACE, + "Line %u too long: %zu characters", + line_num, + line_len); return SSH_ERROR; } memcpy(line, line_start, line_len); diff --git a/src/config_parser.c b/src/config_parser.c index 10cc614f..06264f84 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -30,6 +30,7 @@ #include "libssh/config_parser.h" #include "libssh/priv.h" +#include "libssh/misc.h" /* Returns the original string after skipping the leading whitespace * until finding LF. @@ -38,8 +39,8 @@ */ char *ssh_config_get_cmd(char **str) { - register char *c; - char *r; + register char *c = NULL; + char *r = NULL; /* Ignore leading spaces */ for (c = *str; *c; c++) { @@ -47,7 +48,7 @@ char *ssh_config_get_cmd(char **str) break; } } - + for (r = c; *c; c++) { if (*c == '\n') { *c = '\0'; @@ -66,7 +67,7 @@ char *ssh_config_get_cmd(char **str) */ char *ssh_config_get_token(char **str) { - register char *c; + register char *c = NULL; bool had_equal = false; char *r = NULL; @@ -81,6 +82,13 @@ char *ssh_config_get_token(char **str) if (*c == '\"') { for (r = ++c; *c; c++) { if (*c == '\"' || *c == '\n') { + if (*c == '\"' && r != c && *(c - 1) == '\\') { + /* Escaped quote: Move the remaining one char left */ + int remaining_len = strlen(c); + memmove(c - 1, c, remaining_len); + c[remaining_len - 1] = '\0'; + continue; + } *c = '\0'; c++; break; @@ -115,7 +123,7 @@ char *ssh_config_get_token(char **str) long ssh_config_get_long(char **str, long notfound) { - char *p, *endp; + char *p = NULL, *endp = NULL; long i; p = ssh_config_get_token(str); @@ -132,7 +140,7 @@ long ssh_config_get_long(char **str, long notfound) const char *ssh_config_get_str_tok(char **str, const char *def) { - char *p; + char *p = NULL; p = ssh_config_get_token(str); if (p && *p) { @@ -144,7 +152,7 @@ const char *ssh_config_get_str_tok(char **str, const char *def) int ssh_config_get_yesno(char **str, int notfound) { - const char *p; + const char *p = NULL; p = ssh_config_get_str_tok(str, NULL); if (p == NULL) { @@ -161,12 +169,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) { @@ -180,7 +190,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) { @@ -191,6 +201,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 */ @@ -208,12 +222,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 */ @@ -224,6 +243,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 == ']') { diff --git a/src/connect.c b/src/connect.c index fd54c511..0758e468 100644 --- a/src/connect.c +++ b/src/connect.c @@ -109,7 +109,8 @@ static int ssh_connect_socket_close(socket_t s) #endif } -static int getai(const char *host, int port, struct addrinfo **ai) +static int +getai(const char *host, int port, int ai_family, struct addrinfo **ai) { const char *service = NULL; struct addrinfo hints; @@ -118,7 +119,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) ZERO_STRUCT(hints); hints.ai_protocol = IPPROTO_TCP; - hints.ai_family = PF_UNSPEC; + hints.ai_family = ai_family; hints.ai_socktype = SOCK_STREAM; if (port == 0) { @@ -131,7 +132,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) #endif } - if (ssh_is_ipaddr(host)) { + if (ssh_is_ipaddr(host) == 1) { /* this is an IP address */ SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); hints.ai_flags |= AI_NUMERICHOST; @@ -165,14 +166,39 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, { socket_t s = -1, first = -1; int rc; + int ai_family; + static const char *ai_family_str = NULL; struct addrinfo *ai = NULL; struct addrinfo *itr = NULL; + char addrname[NI_MAXHOST], portname[NI_MAXSERV]; - rc = getai(host, port, &ai); + switch (session->opts.address_family) { + case SSH_ADDRESS_FAMILY_INET: + ai_family = PF_INET; + ai_family_str = "inet"; + break; + case SSH_ADDRESS_FAMILY_INET6: + ai_family = PF_INET6; + ai_family_str = "inet6"; + break; + case SSH_ADDRESS_FAMILY_ANY: + default: + ai_family = PF_UNSPEC; + ai_family_str = "any"; + } + SSH_LOG(SSH_LOG_PACKET, + "Resolve target hostname %s port %d (%s)", + host, + port, + ai_family_str); + rc = getai(host, port, ai_family, &ai); if (rc != 0) { - ssh_set_error(session, SSH_FATAL, - "Failed to resolve hostname %s (%s)", - host, gai_strerror(rc)); + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve hostname %s (%s): %s", + host, + ai_family_str, + gai_strerror(rc)); return -1; } @@ -189,16 +215,21 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } if (bind_addr) { - struct addrinfo *bind_ai; - struct addrinfo *bind_itr; + struct addrinfo *bind_ai = NULL; + struct addrinfo *bind_itr = NULL; - SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr); + SSH_LOG(SSH_LOG_PACKET, + "Resolving bind address %s (%s)", + bind_addr, + ai_family_str); - rc = getai(bind_addr, 0, &bind_ai); + rc = getai(bind_addr, 0, ai_family, &bind_ai); if (rc != 0) { - ssh_set_error(session, SSH_FATAL, - "Failed to resolve bind address %s (%s)", + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve bind address %s (%s): %s", bind_addr, + ai_family_str, gai_strerror(rc)); ssh_connect_socket_close(s); s = -1; @@ -209,7 +240,8 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, bind_itr != NULL; bind_itr = bind_itr->ai_next) { - if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { + rc = bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen); + if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Binding local address: %s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); @@ -251,7 +283,28 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } } + rc = getnameinfo(itr->ai_addr, + itr->ai_addrlen, + addrname, + sizeof(addrname), + portname, + sizeof(portname), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "getnameinfo failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + errno = 0; + SSH_LOG(SSH_LOG_PACKET, + "Connecting to host %s [%s] port %s", + host, + addrname, + portname); rc = connect(s, itr->ai_addr, itr->ai_addrlen); if (rc == -1) { if ((errno != 0) && (errno != EINPROGRESS)) { @@ -262,6 +315,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, s = -1; } else { if (first == -1) { + SSH_LOG(SSH_LOG_PACKET, "EINPROGRESS => Store for later."); first = s; } else { /* errno == EINPROGRESS */ /* save only the first "working" socket */ @@ -281,6 +335,9 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, * connection, otherwise return the first address without error or error */ if (s == -1) { s = first; + } else if (s != first && first != -1) { + /* Clean up the saved socket if any */ + ssh_connect_socket_close(first); } return s; diff --git a/src/connector.c b/src/connector.c index 21c4d79b..2cdfea47 100644 --- a/src/connector.c +++ b/src/connector.c @@ -83,6 +83,20 @@ static ssize_t ssh_connector_fd_write(ssh_connector connector, uint32_t len); static bool ssh_connector_fd_is_socket(socket_t socket); +/** + * @brief Create a new SSH connector. + * + * Allocates and initializes a new connector object for moving data between + * an SSH session and file descriptors. The connector is created with invalid + * file descriptors and callback structures initialized, but not yet attached + * to any channels or sockets. + * + * @param[in] session The SSH session to associate with the connector. + * + * @return A newly allocated connector on success, or NULL if an + * error occurred. On error, an out-of-memory error is + * set on the session. + */ ssh_connector ssh_connector_new(ssh_session session) { ssh_connector connector; @@ -112,8 +126,20 @@ ssh_connector ssh_connector_new(ssh_session session) return connector; } +/** + * @brief Free an SSH connector. + * + * Cleans up and deallocates a connector created by ssh_connector_new(). + * Any channel callbacks and poll objects associated with the @p connector + * are removed and freed before the connector structure itself is released. + * + * @param[in] connector The connector to free. + */ void ssh_connector_free (ssh_connector connector) { + if (connector == NULL) { + return; + } if (connector->in_channel != NULL) { ssh_remove_channel_callbacks(connector->in_channel, &connector->in_channel_cb); @@ -140,6 +166,24 @@ void ssh_connector_free (ssh_connector connector) free(connector); } +/** + * @brief Set the input channel for a connector. + * + * Associates an SSH channel with the @p connector as its input source and + * installs the internal channel callbacks used for reading data. Any + * configured input file descriptor is disabled and the connector will + * receive data from the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as input. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to read from. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ int ssh_connector_set_in_channel(ssh_connector connector, ssh_channel channel, enum ssh_connector_flags_e flags) @@ -156,6 +200,24 @@ int ssh_connector_set_in_channel(ssh_connector connector, return ssh_add_channel_callbacks(channel, &connector->in_channel_cb); } +/** + * @brief Set the output channel for a connector. + * + * Associates an SSH channel with the @p connector as its output target and + * installs the internal channel callbacks used for writing data. Any + * configured output file descriptor is disabled and the connector will + * send data to the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as output. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to write to. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ int ssh_connector_set_out_channel(ssh_connector connector, ssh_channel channel, enum ssh_connector_flags_e flags) @@ -166,12 +228,21 @@ int ssh_connector_set_out_channel(ssh_connector connector, /* Fallback to default value for invalid flags */ if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { - connector->in_flags = SSH_CONNECTOR_STDOUT; + connector->out_flags = SSH_CONNECTOR_STDOUT; } return ssh_add_channel_callbacks(channel, &connector->out_channel_cb); } +/** + * @brief Set the connector's input file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the input source for the + * @p connector , replacing any previously configured input channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd) { connector->in_fd = fd; @@ -179,6 +250,15 @@ void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd) connector->in_channel = NULL; } +/** + * @brief Set the connector's output file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the output target for the + * @p connector , replacing any previously configured output channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd) { connector->out_fd = fd; @@ -225,6 +305,87 @@ static void ssh_connector_reset_pollevents(ssh_connector connector) } } +/** + * @internal + * + * @brief Update the connector's flags after a read-write io + * operation + * + * This should be called after some data is successfully read from + * connector's input and written to connector's output. + * + * @param[in, out] connector Connector for which the io operation occurred. + * + * @warning This does not consider the case when the io indicated failure + * + * @warning This does not consider the case when the input indicated that + * EOF was encountered. + */ +static void ssh_connector_update_flags_after_io(ssh_connector connector) +{ + /* + * With fds we can afford to mark: + * - in_available as 0 after an fd read (even if more pending data can be + * immediately read from the fd) + * + * - out_wontblock as 0 after an fd write (even if more data can + * be written to the fd without blocking) + * + * since poll events set on the fd will get raised to indicate + * possibility of read/write in case existing situation is apt + * (i.e can read/write occur right now) or if situation becomes + * apt in future (read data becomes available, write becomes + * possible) + */ + + /* + * On the other hand, with channels we need to be more careful + * before claiming read/write not possible because channel callbacks + * are called in limited scenarios. + * + * (e.g. connector callback to indicate read data available on input + * channel is called only when new data is received on channel. It is + * not called when we have some pending data in channel's buffers but + * don't receive any new data on the channel) + * + * Hence, in case of channels, blindly setting flag associated with + * read/write input/output to 0 after a read/write may not be a good + * idea as the callback that sets it back to 1 again may not be ever + * called again. + */ + + uint32_t window_size; + + /* update in_available based on input source (fd or channel) */ + if (connector->in_fd != SSH_INVALID_SOCKET) { + connector->in_available = 0; + } else if (connector->in_channel != NULL) { + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0) { + connector->in_available = 1; + } else { + connector->in_available = 0; + } + } else { + /* connector input is invalid ! */ + return; + } + + /* update out_wontblock based on output source (fd or channel) */ + if (connector->out_fd != SSH_INVALID_SOCKET) { + connector->out_wontblock = 0; + } else if (connector->out_channel != NULL) { + window_size = ssh_channel_window_size(connector->out_channel); + if (window_size > 0) { + connector->out_wontblock = 1; + } else { + connector->out_wontblock = 0; + } + } else { + /* connector output is invalid ! */ + return; + } +} + /** * @internal * @@ -250,7 +411,9 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) } r = ssh_connector_fd_read(connector, buffer, toread); - if (r < 0) { + /* Sanity: Make sure we do not get too large return value to make static + * analysis tools happy */ + if (r < 0 || r > (ssize_t)toread) { ssh_connector_except(connector, connector->in_fd); return; } @@ -264,17 +427,17 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) } connector->in_available = 1; /* Don't poll on it */ return; - } else if (r> 0) { + } else if (r > 0) { /* loop around ssh_channel_write in case our window reduced due to a race */ while (total != r){ if (connector->out_flags & SSH_CONNECTOR_STDOUT) { w = ssh_channel_write(connector->out_channel, buffer + total, - r - total); + (uint32_t)(r - total)); } else { w = ssh_channel_write_stderr(connector->out_channel, buffer + total, - r - total); + (uint32_t)(r - total)); } if (w == SSH_ERROR) { return; @@ -291,9 +454,13 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) * 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, + (uint32_t)(r - total)); + /* Sanity: Make sure we do not get too large return value + * to make static analysis tools happy */ + if (w < 0 || w > (r - total)) { ssh_connector_except(connector, connector->out_fd); return; } @@ -304,8 +471,8 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed"); return; } - connector->out_wontblock = 0; - connector->in_available = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->in_available = 1; } @@ -338,8 +505,9 @@ ssh_connector_fd_out_cb(ssh_connector connector) } 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); + w = ssh_connector_fd_write(connector, + buffer + total, + (uint32_t)(r - total)); if (w < 0) { ssh_connector_except(connector, connector->out_fd); return; @@ -357,8 +525,8 @@ ssh_connector_fd_out_cb(ssh_connector connector) "Output socket or channel closed"); return; } - connector->in_available = 0; - connector->out_wontblock = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->out_wontblock = 1; } @@ -369,7 +537,7 @@ ssh_connector_fd_out_cb(ssh_connector connector) * * @brief Callback called when a poll event is received on a file descriptor. * - * This is for (input or output. + * This is for input or output. * * @param[in] fd file descriptor receiving the event * @@ -379,15 +547,13 @@ ssh_connector_fd_out_cb(ssh_connector connector) * * @returns 0 */ -static int ssh_connector_fd_cb(ssh_poll_handle p, +static int ssh_connector_fd_cb(UNUSED_PARAM(ssh_poll_handle p), socket_t fd, int revents, void *userdata) { ssh_connector connector = userdata; - (void)p; - if (revents & POLLERR) { ssh_connector_except(connector, fd); } else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) { @@ -406,6 +572,10 @@ static int ssh_connector_fd_cb(ssh_poll_handle p, * * @brief Callback called when data is received on channel. * + * @param[in] session The SSH session + * + * @param[in] channel The channel data came from + * * @param[in] data Pointer to the data * * @param[in] len Length of data @@ -417,7 +587,7 @@ static int ssh_connector_fd_cb(ssh_poll_handle p, * @returns Amount of data bytes consumed */ static int ssh_connector_channel_data_cb(ssh_session session, - ssh_channel channel, + UNUSED_PARAM(ssh_channel channel), void *data, uint32_t len, int is_stderr, @@ -427,11 +597,11 @@ static int ssh_connector_channel_data_cb(ssh_session session, int w; uint32_t window; - (void) session; - (void) channel; - (void) is_stderr; - - SSH_LOG(SSH_LOG_TRACE,"connector data on channel"); + SSH_LOG(SSH_LOG_TRACE, + "Received data (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + len, + channel->local_channel, + channel->remote_channel); if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) { /* ignore stderr */ @@ -445,6 +615,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, } if (connector->out_wontblock) { + SSH_LOG(SSH_LOG_TRACE, "Writing won't block"); if (connector->out_channel != NULL) { uint32_t window_len; @@ -452,16 +623,8 @@ static int ssh_connector_channel_data_cb(ssh_session session, window_len = MIN(window, len); /* Route the data to the right exception channel */ - if (is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR)) { - w = ssh_channel_write_stderr(connector->out_channel, - data, - window_len); - } else if (!is_stderr && - (connector->out_flags & SSH_CONNECTOR_STDOUT)) { - w = ssh_channel_write(connector->out_channel, - data, - window_len); - } else if (connector->out_flags & SSH_CONNECTOR_STDOUT) { + if (connector->out_flags & SSH_CONNECTOR_STDOUT && + !(is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR))) { w = ssh_channel_write(connector->out_channel, data, window_len); @@ -474,23 +637,22 @@ static int ssh_connector_channel_data_cb(ssh_session session, ssh_connector_except_channel(connector, connector->out_channel); } } else if (connector->out_fd != SSH_INVALID_SOCKET) { - w = ssh_connector_fd_write(connector, data, len); - if (w < 0) + ssize_t ws = ssh_connector_fd_write(connector, data, len); + if (ws < 0) { ssh_connector_except(connector, connector->out_fd); + } + w = (int)ws; } else { ssh_set_error(session, SSH_FATAL, "output socket or channel closed"); return SSH_ERROR; } - connector->out_wontblock = 0; - connector->in_available = 0; - if ((unsigned int)w < len) { - connector->in_available = 1; - } + ssh_connector_update_flags_after_io(connector); ssh_connector_reset_pollevents(connector); return w; } else { + SSH_LOG(SSH_LOG_TRACE, "Writing would block: wait?"); connector->in_available = 1; return 0; @@ -508,10 +670,11 @@ static int ssh_connector_channel_data_cb(ssh_session session, * * @returns Amount of data bytes consumed */ -static int ssh_connector_channel_write_wontblock_cb(ssh_session session, - ssh_channel channel, - uint32_t bytes, - void *userdata) +static int +ssh_connector_channel_write_wontblock_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + uint32_t bytes, + void *userdata) { ssh_connector connector = userdata; uint8_t buffer[CHUNKSIZE]; @@ -519,7 +682,12 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, (void) channel; - SSH_LOG(SSH_LOG_TRACE, "Channel write won't block"); + SSH_LOG(SSH_LOG_TRACE, + "Write won't block (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + bytes, + channel->local_channel, + channel->remote_channel); + if (connector->in_available) { if (connector->in_channel != NULL) { uint32_t len = MIN(CHUNKSIZE, bytes); @@ -530,7 +698,7 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, 0); if (r == SSH_ERROR) { ssh_connector_except_channel(connector, connector->in_channel); - } else if(r == 0 && ssh_channel_is_eof(connector->in_channel)){ + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { ssh_channel_send_eof(connector->out_channel); } else if (r > 0) { w = ssh_channel_write(connector->out_channel, buffer, r); @@ -551,8 +719,8 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, return 0; } - connector->in_available = 0; - connector->out_wontblock = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->out_wontblock = 1; } @@ -601,15 +769,15 @@ int ssh_connector_set_event(ssh_connector connector, ssh_event event) } } if (connector->in_channel != NULL) { - rc = ssh_event_add_session(event, - ssh_channel_get_session(connector->in_channel)); + ssh_session session = ssh_channel_get_session(connector->in_channel); + rc = ssh_event_add_session(event, session); if (rc != SSH_OK) goto error; if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0){ connector->in_available = 1; } } - if(connector->out_channel != NULL) { + if (connector->out_channel != NULL) { ssh_session session = ssh_channel_get_session(connector->out_channel); rc = ssh_event_add_session(event, session); @@ -625,8 +793,9 @@ int ssh_connector_set_event(ssh_connector connector, ssh_event event) return rc; } -int ssh_connector_remove_event(ssh_connector connector) { - ssh_session session; +int ssh_connector_remove_event(ssh_connector connector) +{ + ssh_session session = NULL; if (connector->in_poll != NULL) { ssh_event_remove_poll(connector->event, connector->in_poll); diff --git a/src/curve25519.c b/src/curve25519.c index 26603681..c68fbc79 100644 --- a/src/curve25519.c +++ b/src/curve25519.c @@ -26,119 +26,43 @@ #include "libssh/curve25519.h" #ifdef HAVE_CURVE25519 -#ifdef WITH_NACL -#include "nacl/crypto_scalarmult_curve25519.h" -#endif - -#include "libssh/ssh2.h" +#include "libssh/bignum.h" #include "libssh/buffer.h" -#include "libssh/priv.h" -#include "libssh/session.h" #include "libssh/crypto.h" #include "libssh/dh.h" #include "libssh/pki.h" -#include "libssh/bignum.h" - -#ifdef HAVE_LIBCRYPTO -#include -#endif +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); static ssh_packet_callback dh_client_callbacks[] = { - ssh_packet_client_curve25519_reply + ssh_packet_client_curve25519_reply, }; static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { .start = SSH2_MSG_KEX_ECDH_REPLY, .n_callbacks = 1, .callbacks = dh_client_callbacks, - .user = NULL + .user = NULL, }; -static int ssh_curve25519_init(ssh_session session) +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k) { int rc; -#ifdef HAVE_LIBCRYPTO - EVP_PKEY_CTX *pctx = NULL; - EVP_PKEY *pkey = NULL; - size_t pubkey_len = CURVE25519_PUBKEY_SIZE; - size_t pkey_len = CURVE25519_PRIVKEY_SIZE; - - pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); - if (pctx == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to initialize X25519 context: %s", - ERR_error_string(ERR_get_error(), NULL)); - return SSH_ERROR; - } - rc = EVP_PKEY_keygen_init(pctx); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to initialize X25519 keygen: %s", - ERR_error_string(ERR_get_error(), NULL)); - EVP_PKEY_CTX_free(pctx); - return SSH_ERROR; - } - - rc = EVP_PKEY_keygen(pctx, &pkey); - EVP_PKEY_CTX_free(pctx); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to generate X25519 keys: %s", - ERR_error_string(ERR_get_error(), NULL)); - return SSH_ERROR; - } - - if (session->server) { - rc = EVP_PKEY_get_raw_public_key(pkey, - session->next_crypto->curve25519_server_pubkey, - &pubkey_len); - } else { - rc = EVP_PKEY_get_raw_public_key(pkey, - session->next_crypto->curve25519_client_pubkey, - &pubkey_len); - } - - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get X25519 raw public key: %s", - ERR_error_string(ERR_get_error(), NULL)); - EVP_PKEY_free(pkey); - return SSH_ERROR; - } - - rc = EVP_PKEY_get_raw_private_key(pkey, - session->next_crypto->curve25519_privkey, - &pkey_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get X25519 raw private key: %s", - ERR_error_string(ERR_get_error(), NULL)); - EVP_PKEY_free(pkey); - return SSH_ERROR; - } - - EVP_PKEY_free(pkey); -#else - rc = ssh_get_random(session->next_crypto->curve25519_privkey, - CURVE25519_PRIVKEY_SIZE, 1); - if (rc != 1) { - ssh_set_error(session, SSH_FATAL, "PRNG error"); - return SSH_ERROR; - } - - if (session->server) { - crypto_scalarmult_base(session->next_crypto->curve25519_server_pubkey, - session->next_crypto->curve25519_privkey); - } else { - crypto_scalarmult_base(session->next_crypto->curve25519_client_pubkey, - session->next_crypto->curve25519_privkey); - } -#endif /* HAVE_LIBCRYPTO */ +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->next_crypto->server_kex.cookie, + 16); + ssh_log_hexdump("Session client cookie", + session->next_crypto->client_kex.cookie, + 16); +#endif - return SSH_OK; + rc = curve25519_do_create_k(session, k); + return rc; } /** @internal @@ -177,202 +101,123 @@ 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) +int ssh_curve25519_build_k(ssh_session session) { ssh_curve25519_pubkey k; + int rc; -#ifdef HAVE_LIBCRYPTO - EVP_PKEY_CTX *pctx = NULL; - EVP_PKEY *pkey = NULL, *pubkey = NULL; - size_t shared_key_len = sizeof(k); - int rc, ret = SSH_ERROR; - - pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, - session->next_crypto->curve25519_privkey, - CURVE25519_PRIVKEY_SIZE); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to create X25519 EVP_PKEY: %s", - ERR_error_string(ERR_get_error(), NULL)); - return SSH_ERROR; - } - - pctx = EVP_PKEY_CTX_new(pkey, NULL); - if (pctx == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to initialize X25519 context: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - rc = EVP_PKEY_derive_init(pctx); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to initialize X25519 key derivation: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - if (session->server) { - pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, - session->next_crypto->curve25519_client_pubkey, - CURVE25519_PUBKEY_SIZE); - } else { - pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, - session->next_crypto->curve25519_server_pubkey, - CURVE25519_PUBKEY_SIZE); - } - if (pubkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to create X25519 public key EVP_PKEY: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - rc = EVP_PKEY_derive_set_peer(pctx, pubkey); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to set peer X25519 public key: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - - rc = EVP_PKEY_derive(pctx, k, &shared_key_len); - if (rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to derive X25519 shared secret: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto out; - } - ret = SSH_OK; -out: - EVP_PKEY_free(pkey); - EVP_PKEY_free(pubkey); - EVP_PKEY_CTX_free(pctx); - if (ret == SSH_ERROR) { - return ret; - } -#else - if (session->server) { - crypto_scalarmult(k, session->next_crypto->curve25519_privkey, - session->next_crypto->curve25519_client_pubkey); - } else { - crypto_scalarmult(k, session->next_crypto->curve25519_privkey, - session->next_crypto->curve25519_server_pubkey); + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + return rc; } -#endif /* HAVE_LIBCRYPTO */ - bignum_bin2bn(k, CURVE25519_PUBKEY_SIZE, &session->next_crypto->shared_secret); + bignum_bin2bn(k, + CURVE25519_PUBKEY_SIZE, + &session->next_crypto->shared_secret); if (session->next_crypto->shared_secret == NULL) { return SSH_ERROR; } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("Session server cookie", - session->next_crypto->server_kex.cookie, 16); - ssh_log_hexdump("Session client cookie", - session->next_crypto->client_kex.cookie, 16); ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); #endif - return 0; + return SSH_OK; } /** @internal * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back * a SSH_MSG_NEWKEYS */ -static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ - ssh_string q_s_string = NULL; - ssh_string pubkey_blob = NULL; - ssh_string signature = NULL; - int rc; - (void)type; - (void)user; - - ssh_client_curve25519_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"); - goto error; - } - - rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); - SSH_STRING_FREE(pubkey_blob); - if (rc != 0) { - ssh_set_error(session, - SSH_FATAL, - "Failed to import next public key"); - goto error; - } - - q_s_string = ssh_buffer_get_ssh_string(packet); - if (q_s_string == NULL) { - ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); - goto error; - } - if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE){ - ssh_set_error(session, SSH_FATAL, "Incorrect size for server Curve25519 public key: %d", - (int)ssh_string_len(q_s_string)); - SSH_STRING_FREE(q_s_string); - goto error; - } - memcpy(session->next_crypto->curve25519_server_pubkey, ssh_string_data(q_s_string), CURVE25519_PUBKEY_SIZE); - SSH_STRING_FREE(q_s_string); - - signature = ssh_buffer_get_ssh_string(packet); - if (signature == NULL) { - ssh_set_error(session, SSH_FATAL, "No signature in packet"); - goto error; - } - session->next_crypto->dh_server_signature = signature; - signature=NULL; /* ownership changed */ - /* TODO: verify signature now instead of waiting for NEWKEYS */ - if (ssh_curve25519_build_k(session) < 0) { - ssh_set_error(session, SSH_FATAL, "Cannot build k number"); - goto error; - } - - /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); - if (rc == SSH_ERROR) { - goto error; - } - - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - - return SSH_PACKET_USED; +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_curve25519_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"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server Curve25519 public key: %zu", + ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->curve25519_server_pubkey, + ssh_string_data(q_s_string), + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_curve25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; error: - session->session_state=SSH_SESSION_STATE_ERROR; - return SSH_PACKET_USED; + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; } #ifdef WITH_SERVER static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init); -static ssh_packet_callback dh_server_callbacks[]= { - ssh_packet_server_curve25519_init +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_curve25519_init, }; static struct ssh_packet_callbacks_struct ssh_curve25519_server_callbacks = { .start = SSH2_MSG_KEX_ECDH_INIT, .n_callbacks = 1, .callbacks = dh_server_callbacks, - .user = NULL + .user = NULL, }; /** @internal * @brief sets up the curve25519-sha256@libssh.org kex callbacks */ -void ssh_server_curve25519_init(ssh_session session){ +void ssh_server_curve25519_init(ssh_session session) +{ /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_curve25519_server_callbacks); } @@ -380,7 +225,8 @@ void ssh_server_curve25519_init(ssh_session session){ /** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a * SSH_MSG_KEXDH_REPLY */ -static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init) +{ /* ECDH keys */ ssh_string q_c_string = NULL; ssh_string q_s_string = NULL; @@ -399,10 +245,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ /* 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; } - if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE){ + if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE) { ssh_set_error(session, SSH_FATAL, "Incorrect size for server Curve25519 public key: %zu", @@ -411,7 +257,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_data(q_c_string), + CURVE25519_PUBKEY_SIZE); SSH_STRING_FREE(q_c_string); /* Build server's key pair */ @@ -453,8 +300,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ } /* add host's public key */ - rc = ssh_buffer_add_ssh_string(session->out_buffer, - server_pubkey_blob); + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); SSH_STRING_FREE(server_pubkey_blob); if (rc < 0) { ssh_set_error_oom(session); @@ -502,25 +348,20 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ 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_DEBUG, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: SSH_STRING_FREE(q_c_string); SSH_STRING_FREE(q_s_string); ssh_buffer_reinit(session->out_buffer); - session->session_state=SSH_SESSION_STATE_ERROR; + session->session_state = SSH_SESSION_STATE_ERROR; return SSH_PACKET_USED; } diff --git a/src/curve25519_crypto.c b/src/curve25519_crypto.c new file mode 100644 index 00000000..3314f70e --- /dev/null +++ b/src/curve25519_crypto.c @@ -0,0 +1,164 @@ +/* + * curve25519_crypto.c - Curve25519 ECDH functions for key exchange (OpenSSL) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + size_t pubkey_len = CURVE25519_PUBKEY_SIZE; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen(pctx, &pkey); + EVP_PKEY_CTX_free(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keys: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, *pubkey_loc, &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get X25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return SSH_ERROR; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + EVP_PKEY_free(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + session->next_crypto->curve25519_privkey = pkey; + pkey = NULL; + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL, *pubkey = NULL; + size_t shared_key_len = CURVE25519_PUBKEY_SIZE; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + pkey = session->next_crypto->curve25519_privkey; + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 key derivation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, + NULL, + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 public key EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_set_peer(pctx, pubkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer X25519 public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive(pctx, k, &shared_key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive X25519 shared secret: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + ret = SSH_OK; + +out: + EVP_PKEY_free(pubkey); + EVP_PKEY_CTX_free(pctx); + return ret; +} diff --git a/src/curve25519_fallback.c b/src/curve25519_fallback.c new file mode 100644 index 00000000..e05331d5 --- /dev/null +++ b/src/curve25519_fallback.c @@ -0,0 +1,73 @@ +/* + * curve25519_fallback.c - Curve25519 ECDH functions for key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#ifdef WITH_NACL +#include "nacl/crypto_scalarmult_curve25519.h" +#endif + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + rc = ssh_get_random(session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE, + 1); + if (rc != 1) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + + crypto_scalarmult_base(*pubkey_loc, + session->next_crypto->curve25519_privkey); + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + crypto_scalarmult(k, + session->next_crypto->curve25519_privkey, + *peer_pubkey_loc); + return SSH_OK; +} diff --git a/src/curve25519_gcrypt.c b/src/curve25519_gcrypt.c new file mode 100644 index 00000000..cd522a1d --- /dev/null +++ b/src/curve25519_gcrypt.c @@ -0,0 +1,205 @@ +/* + * curve25519_gcrypt.c - Curve25519 ECDH functions for key exchange (Gcrypt) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t param = NULL, keypair_sexp = NULL; + ssh_string pubkey = NULL; + const char *pubkey_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + gcry_err = + gcry_sexp_build(¶m, NULL, "(genkey (ecdh (curve Curve25519)))"); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create keypair sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_genkey(&keypair_sexp, param); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate keypair: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Extract the public key */ + pubkey = ssh_sexp_extract_mpi(keypair_sexp, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to extract public key: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Store the public key in the session */ + /* The first byte should be 0x40 indicating that the point is compressed, so + * we skip storing it */ + pubkey_data = (char *)ssh_string_data(pubkey); + if (ssh_string_len(pubkey) != CURVE25519_PUBKEY_SIZE + 1 || + pubkey_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid public key with length: %zu", + ssh_string_len(pubkey)); + goto out; + } + + memcpy(*pubkey_loc, pubkey_data + 1, CURVE25519_PUBKEY_SIZE); + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + /* Store the private key */ + session->next_crypto->curve25519_privkey = keypair_sexp; + keypair_sexp = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + gcry_sexp_release(param); + gcry_sexp_release(keypair_sexp); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t pubkey_sexp = NULL, privkey_data_sexp = NULL, + result_sexp = NULL; + ssh_string shared_secret = NULL, privkey = NULL; + char *shared_secret_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + gcry_err = gcry_sexp_build( + &pubkey_sexp, + NULL, + "(key-data(public-key (ecdh (curve Curve25519) (q %b))))", + CURVE25519_PUBKEY_SIZE, + *peer_pubkey_loc); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create peer public key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->curve25519_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract private key"); + goto out; + } + + gcry_err = gcry_sexp_build(&privkey_data_sexp, + NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create private key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_encrypt(&result_sexp, privkey_data_sexp, pubkey_sexp); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + gcry_strerror(gcry_err)); + goto out; + } + + shared_secret = ssh_sexp_extract_mpi(result_sexp, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_USG); + if (shared_secret == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract shared secret"); + goto out; + } + + /* Copy the shared secret to the output buffer */ + /* The first byte should be 0x40 indicating that it is a compressed point, + * so we skip it */ + shared_secret_data = (char *)ssh_string_data(shared_secret); + if (ssh_string_len(shared_secret) != CURVE25519_PUBKEY_SIZE + 1 || + shared_secret_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid shared secret with length: %zu", + ssh_string_len(shared_secret)); + goto out; + } + + memcpy(k, shared_secret_data + 1, CURVE25519_PUBKEY_SIZE); + + ret = SSH_OK; + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + +out: + ssh_string_burn(shared_secret); + SSH_STRING_FREE(shared_secret); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + gcry_sexp_release(privkey_data_sexp); + gcry_sexp_release(pubkey_sexp); + gcry_sexp_release(result_sexp); + return ret; +} diff --git a/src/curve25519_mbedcrypto.c b/src/curve25519_mbedcrypto.c new file mode 100644 index 00000000..f328f7b7 --- /dev/null +++ b/src/curve25519_mbedcrypto.c @@ -0,0 +1,189 @@ +/* + * curve25519_mbedcrypto.c - Curve25519 ECDH functions for key exchange + * (MbedTLS) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "mbedcrypto-compat.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + int rc, ret = SSH_ERROR; + char error_buf[128]; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_ecdh_gen_public(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keypair: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 private key: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q).MBEDTLS_ECDH_PRIVATE(X), + *pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 public key: %s", + error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + char error_buf[128]; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_mpi_read_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read private key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_read_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(X), + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read peer public key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_lset( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(Z), + 1); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to set Z coordinate: %s", error_buf); + goto out; + } + + rc = mbedtls_ecdh_compute_shared(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + k, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to write shared secret: %s", error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} diff --git a/src/dh-gex.c b/src/dh-gex.c index ea30e4e9..f9fe3cd6 100644 --- a/src/dh-gex.c +++ b/src/dh-gex.c @@ -297,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_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; @@ -414,6 +409,7 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, size_t line = 0; size_t best_nlines = 0; + *best_size = 0; for(;;) { line++; firstbyte = getc(moduli); @@ -421,6 +417,9 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, do { firstbyte = getc(moduli); } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } continue; } if (firstbyte == EOF) { @@ -444,6 +443,9 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, do { firstbyte = getc(moduli); } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } continue; } @@ -455,10 +457,9 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, proposed_size = size + 1; if (proposed_size != *best_size && dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { - best_nlines = 0; + best_nlines = 1; *best_size = proposed_size; - } - if (proposed_size == *best_size) { + } else if (proposed_size == *best_size) { best_nlines++; } @@ -525,9 +526,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file, } if (moduli_file != NULL) - moduli = fopen(moduli_file, "r"); + moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); else - moduli = fopen(MODULI_FILE, "r"); + moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); if (moduli == NULL) { char err_msg[SSH_ERRNO_MSG_MAX] = {0}; @@ -577,12 +578,12 @@ static int ssh_retrieve_dhgroup(char *moduli_file, static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request); static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init); -static ssh_packet_callback dhgex_server_callbacks[]= { - NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ - NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ +static ssh_packet_callback dhgex_server_callbacks[] = { + NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ + NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */ NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */ - ssh_packet_server_dhgex_request /* SSH_MSG_GEX_DH_GEX_REQUEST */ + ssh_packet_server_dhgex_request /* SSH_MSG_KEX_DH_GEX_REQUEST */ }; @@ -648,7 +649,7 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) pn = pmin; } } - rc = ssh_retrieve_dhgroup(session->opts.moduli_file, + rc = ssh_retrieve_dhgroup(session->server_opts.moduli_file, pmin, pn, pmax, diff --git a/src/dh.c b/src/dh.c index cd2876cb..c04418dd 100644 --- a/src/dh.c +++ b/src/dh.c @@ -26,6 +26,10 @@ #include "config.h" #include +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif #include "libssh/priv.h" #include "libssh/crypto.h" @@ -36,6 +40,7 @@ #include "libssh/ssh2.h" #include "libssh/pki.h" #include "libssh/bignum.h" +#include "libssh/string.h" static unsigned char p_group1_value[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, @@ -398,16 +403,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_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; error: @@ -551,15 +550,12 @@ 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: diff --git a/src/dh_crypto.c b/src/dh_crypto.c index 9ff7ad3c..647e3bdd 100644 --- a/src/dh_crypto.c +++ b/src/dh_crypto.c @@ -404,9 +404,14 @@ int ssh_dh_set_parameters(struct dh_ctx *ctx, */ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) { - struct dh_ctx *ctx; + struct dh_ctx *ctx = NULL; int rc; + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + ctx = calloc(1, sizeof(*ctx)); if (ctx == NULL) { return SSH_ERROR; @@ -419,9 +424,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) break; case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); break; case SSH_KEX_DH_GROUP18_SHA512: @@ -592,7 +599,7 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, } #endif /* OPENSSL_VERSION_NUMBER */ - *dest = BN_bin2bn(kstring, klen, NULL); + *dest = BN_bin2bn(kstring, (int)klen, NULL); if (*dest == NULL) { rc = SSH_ERROR; goto done; diff --git a/src/dh_key.c b/src/dh_key.c index 20d24a31..19785a9a 100644 --- a/src/dh_key.c +++ b/src/dh_key.c @@ -237,6 +237,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) struct dh_ctx *ctx = NULL; int rc; + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + ctx = calloc(1, sizeof(*ctx)); if (ctx == NULL) { return SSH_ERROR; @@ -248,9 +253,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) break; case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); break; case SSH_KEX_DH_GROUP18_SHA512: diff --git a/src/ecdh.c b/src/ecdh.c index fa5e08c5..af80beec 100644 --- a/src/ecdh.c +++ b/src/ecdh.c @@ -93,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_DEBUG, "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 e31cc0aa..6b4e7706 100644 --- a/src/ecdh_crypto.c +++ b/src/ecdh_crypto.c @@ -51,18 +51,25 @@ 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) { + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: return NISTP256; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif return NISTP384; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + case SSH_KEX_ECDH_SHA2_NISTP521: return NISTP521; - } + default: #if OPENSSL_VERSION_NUMBER < 0x30000000L - return SSH_ERROR; + return SSH_ERROR; #else - return NULL; + return NULL; #endif + } } /* @internal @@ -191,16 +198,51 @@ static ssh_string ssh_ecdh_generate(ssh_session session) #endif /* OPENSSL_VERSION_NUMBER */ return NULL; } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(session->next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(session->next_crypto->ecdh_privkey); +#endif + session->next_crypto->ecdh_privkey = NULL; + } + session->next_crypto->ecdh_privkey = key; return pubkey_string; } +/** @internal + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. + */ +int ssh_ecdh_init(ssh_session session) +{ + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + pubkey = ssh_ecdh_generate(session); + if (pubkey == NULL) { + return SSH_ERROR; + } + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } + + ssh_string_free(*pubkey_loc); + *pubkey_loc = pubkey; + + return SSH_OK; +} + /** @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); @@ -208,19 +250,17 @@ int ssh_client_ecdh_init(ssh_session session) return SSH_ERROR; } - client_pubkey = ssh_ecdh_generate(session); - if (client_pubkey == NULL) { + rc = ssh_ecdh_init(session); + if (rc < 0) { return SSH_ERROR; } - rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_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; @@ -397,6 +437,7 @@ int ecdh_build_k(ssh_session session) "Could not derive shared key: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_PKEY_CTX_free(dh_ctx); + free(secret); return -1; } @@ -441,9 +482,8 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) { /* ECDH keys */ ssh_string q_c_string = NULL; - ssh_string q_s_string = NULL; /* SSH host keys (rsa, ed25519 and ecdsa) */ - ssh_key privkey; + ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; @@ -462,13 +502,11 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) } session->next_crypto->ecdh_client_pubkey = q_c_string; - q_s_string = ssh_ecdh_generate(session); - if (q_s_string == NULL) { + rc = ssh_ecdh_init(session); + if (rc < 0) { goto error; } - session->next_crypto->ecdh_server_pubkey = q_s_string; - /* build k and session_id */ rc = ecdh_build_k(session); if (rc < 0) { @@ -505,7 +543,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) "bSSS", SSH2_MSG_KEXDH_REPLY, pubkey_blob, /* host's pubkey */ - q_s_string, /* ecdh public key */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ sig_blob); /* signature blob */ SSH_STRING_FREE(sig_blob); @@ -522,18 +560,12 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) 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_DEBUG, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c index 76e24873..ec7dccfd 100644 --- a/src/ecdh_gcrypt.c +++ b/src/ecdh_gcrypt.c @@ -36,45 +36,54 @@ /** @internal * @brief Map the given key exchange enum value to its curve name. */ -static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { - if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: return "NIST P-256"; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif return "NIST P-384"; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + case SSH_KEX_ECDH_SHA2_NISTP521: return "NIST P-521"; + default: + return NULL; } - return NULL; } /** @internal - * @brief Starts ecdh-sha2-nistp{256,384,521} key exchange. + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. */ -int ssh_client_ecdh_init(ssh_session session) +int ssh_ecdh_init(ssh_session session) { - int rc; + int rc = SSH_OK; + const char *curve = NULL; + const char *genstring = NULL; gpg_error_t err; - ssh_string client_pubkey = NULL; gcry_sexp_t param = NULL; gcry_sexp_t key = NULL; - const char *curve = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == NULL) { - rc = SSH_ERROR; - goto out; + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + genstring = "(genkey(ecdh(curve %s) (flags transient-key)))"; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + genstring = "(genkey(ecdh(curve %s)))"; } - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); - if (rc < 0) { + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { rc = SSH_ERROR; goto out; } - err = gcry_sexp_build(¶m, - NULL, - "(genkey(ecdh(curve %s)))", - curve); + err = gcry_sexp_build(¶m, NULL, genstring, curve); if (err) { rc = SSH_ERROR; goto out; @@ -86,36 +95,59 @@ int ssh_client_ecdh_init(ssh_session session) goto out; } - client_pubkey = ssh_sexp_extract_mpi(key, - "q", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (client_pubkey == NULL) { + pubkey = ssh_sexp_extract_mpi(key, "q", GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); + if (pubkey == NULL) { rc = SSH_ERROR; goto out; } - rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); - if (rc < 0) { - rc = SSH_ERROR; - goto out; + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; } - session->next_crypto->ecdh_privkey = key; key = NULL; - session->next_crypto->ecdh_client_pubkey = client_pubkey; - client_pubkey = NULL; + + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + gcry_sexp_release(param); + gcry_sexp_release(key); + SSH_STRING_FREE(pubkey); + return rc; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp{256,384,521} key exchange. + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); session->dh_handshake_state = DH_STATE_INIT_SENT; rc = ssh_packet_send(session); - - out: - gcry_sexp_release(param); - gcry_sexp_release(key); - SSH_STRING_FREE(client_pubkey); return rc; } @@ -132,9 +164,9 @@ int ecdh_build_k(ssh_session session) #else size_t k_len = 0; enum ssh_key_exchange_e kex_type = session->next_crypto->kex_type; - ssh_string s; + ssh_string s = NULL; #endif - ssh_string pubkey_raw; + ssh_string pubkey_raw = NULL; gcry_sexp_t pubkey = NULL; ssh_string privkey = NULL; int rc = SSH_ERROR; @@ -265,27 +297,16 @@ int ecdh_build_k(ssh_session session) * SSH_MSG_KEXDH_REPLY */ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ - gpg_error_t err; - /* ECDH keys */ - ssh_string q_c_string; - ssh_string q_s_string; - gcry_sexp_t param = NULL; - gcry_sexp_t key = NULL; - /* SSH host keys (rsa, ed25519 and ecdsa) */ - ssh_key privkey; + ssh_string q_c_string = NULL; + ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; int rc = SSH_ERROR; - const char *curve = NULL; (void)type; (void)user; ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == NULL) { - goto out; - } /* Extract the client pubkey from the init packet */ q_c_string = ssh_buffer_get_ssh_string(packet); @@ -295,29 +316,12 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's key pair */ - err = gcry_sexp_build(¶m, NULL, "(genkey(ecdh(curve %s) (flags transient-key)))", - curve); - if (err) { - goto out; - } - - err = gcry_pk_genkey(&key, param); - if (err) - goto out; - - q_s_string = ssh_sexp_extract_mpi(key, - "q", - GCRYMPI_FMT_USG, - GCRYMPI_FMT_STD); - if (q_s_string == NULL) { + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate a key pair"); goto out; } - session->next_crypto->ecdh_privkey = key; - key = NULL; - session->next_crypto->ecdh_server_pubkey = q_s_string; - /* build k and session_id */ rc = ecdh_build_k(session); if (rc != SSH_OK) { @@ -355,7 +359,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ "bSSS", SSH2_MSG_KEXDH_REPLY, pubkey_blob, /* host's pubkey */ - q_s_string, /* ecdh public key */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ sig_blob); /* signature blob */ SSH_STRING_FREE(sig_blob); @@ -372,20 +376,14 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ 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_DEBUG, "SSH_MSG_NEWKEYS sent"); - out: - gcry_sexp_release(param); - gcry_sexp_release(key); if (rc == SSH_ERROR) { ssh_buffer_reinit(session->out_buffer); session->session_state = SSH_SESSION_STATE_ERROR; diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c index 1c930fb5..7d013b74 100644 --- a/src/ecdh_mbedcrypto.c +++ b/src/ecdh_mbedcrypto.c @@ -38,25 +38,39 @@ #ifdef HAVE_ECDH -static mbedtls_ecp_group_id ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { - if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { +static mbedtls_ecp_group_id +ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: return MBEDTLS_ECP_DP_SECP256R1; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + case SSH_KEX_ECDH_SHA2_NISTP384: return MBEDTLS_ECP_DP_SECP384R1; - } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + case SSH_KEX_ECDH_SHA2_NISTP521: return MBEDTLS_ECP_DP_SECP521R1; + default: + return MBEDTLS_ECP_DP_NONE; } - return MBEDTLS_ECP_DP_NONE; } -int ssh_client_ecdh_init(ssh_session session) + +int ssh_ecdh_init(ssh_session session) { - ssh_string client_pubkey = NULL; - mbedtls_ecp_group grp; int rc; + mbedtls_ecp_group grp; mbedtls_ecp_group_id curve; mbedtls_ctr_drbg_context *ctr_drbg = NULL; mbedtls_ecp_keypair *ecdh_privkey = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); @@ -65,9 +79,10 @@ int ssh_client_ecdh_init(ssh_session session) return SSH_ERROR; } - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); - if (rc < 0) { - return SSH_ERROR; + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); } session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); @@ -87,41 +102,56 @@ int ssh_client_ecdh_init(ssh_session session) } rc = mbedtls_ecp_gen_keypair(&grp, - &ecdh_privkey->MBEDTLS_PRIVATE(d), - &ecdh_privkey->MBEDTLS_PRIVATE(Q), - mbedtls_ctr_drbg_random, - ctr_drbg); - + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } - client_pubkey = make_ecpoint_string(&grp, - &ecdh_privkey->MBEDTLS_PRIVATE(Q)); - if (client_pubkey == NULL) { + pubkey = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); + if (pubkey == NULL) { rc = SSH_ERROR; goto out; } - rc = ssh_buffer_add_ssh_string(session->out_buffer, client_pubkey); + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + mbedtls_ecp_group_free(&grp); + SSH_STRING_FREE(pubkey); + return rc; +} + +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); if (rc < 0) { - rc = SSH_ERROR; - goto out; + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; } - session->next_crypto->ecdh_client_pubkey = client_pubkey; - client_pubkey = NULL; + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); session->dh_handshake_state = DH_STATE_INIT_SENT; rc = ssh_packet_send(session); -out: - mbedtls_ecp_group_free(&grp); - SSH_STRING_FREE(client_pubkey); - return rc; } @@ -197,24 +227,15 @@ int ecdh_build_k(ssh_session session) 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; ssh_string pubkey_blob = NULL; int rc; - mbedtls_ecp_group_id curve; (void)type; (void)user; ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); - if (curve == MBEDTLS_ECP_DP_NONE) { - return SSH_ERROR; - } q_c_string = ssh_buffer_get_ssh_string(packet); if (q_c_string == NULL) { @@ -222,45 +243,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ return SSH_ERROR; } - session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); - if (session->next_crypto->ecdh_privkey == NULL) { - ssh_set_error_oom(session); - return SSH_ERROR; - } - - 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(ecdh_privkey); - - rc = mbedtls_ecp_group_load(&grp, curve); - if (rc != 0) { - rc = SSH_ERROR; - goto out; - } - - rc = mbedtls_ecp_gen_keypair(&grp, - &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, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); - if (q_s_string == NULL) { - rc = SSH_ERROR; - goto out; + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; } - session->next_crypto->ecdh_server_pubkey = q_s_string; - /* build k and session_id */ rc = ecdh_build_k(session); if (rc != SSH_OK) { @@ -299,7 +288,7 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ rc = ssh_buffer_pack(session->out_buffer, "bSSS", SSH2_MSG_KEXDH_REPLY, pubkey_blob, /* host's pubkey */ - q_s_string, /* ecdh public key */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ sig_blob); /* signature blob */ SSH_STRING_FREE(sig_blob); @@ -318,18 +307,14 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ 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_DEBUG, "SSH_MSG_NEWKEYS sent"); - out: - mbedtls_ecp_group_free(&grp); if (rc == SSH_ERROR) { ssh_buffer_reinit(session->out_buffer); session->session_state = SSH_SESSION_STATE_ERROR; diff --git a/src/external/bcrypt_pbkdf.c b/src/external/bcrypt_pbkdf.c index 85f4be47..b05b87f2 100644 --- a/src/external/bcrypt_pbkdf.c +++ b/src/external/bcrypt_pbkdf.c @@ -97,8 +97,8 @@ bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *o } /* zap */ - explicit_bzero(ciphertext, sizeof(ciphertext)); - explicit_bzero(cdata, sizeof(cdata)); + ssh_burn(ciphertext, sizeof(ciphertext)); + ssh_burn(cdata, sizeof(cdata)); } int @@ -180,12 +180,12 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl } /* zap */ - explicit_bzero(out, sizeof(out)); - explicit_bzero(state, sizeof(*state)); + ssh_burn(out, sizeof(out)); + ssh_burn(state, sizeof(*state)); - free(state); - free(countsalt); + free(state); + free(countsalt); - return 0; + return 0; } #endif /* HAVE_BCRYPT_PBKDF */ diff --git a/src/external/blowfish.c b/src/external/blowfish.c index 4008a9c0..42d5df1e 100644 --- a/src/external/blowfish.c +++ b/src/external/blowfish.c @@ -1,4 +1,4 @@ -/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ +/* $OpenBSD: blowfish.c,v 1.20 2021/11/29 01:04:45 djm Exp $ */ /* * Blowfish block cipher for OpenBSD * Copyright 1997 Niels Provos @@ -14,10 +14,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products + * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR diff --git a/src/external/libcrux_mlkem768_sha3.c b/src/external/libcrux_mlkem768_sha3.c new file mode 100644 index 00000000..59130fbd --- /dev/null +++ b/src/external/libcrux_mlkem768_sha3.c @@ -0,0 +1,8897 @@ +/* $OpenBSD: libcrux_mlkem768_sha3.h,v 1.4 2025/11/13 05:13:06 djm Exp $ */ + +/* Extracted from libcrux revision 026a87ab6d88ad3626b9fbbf3710d1e0483c1849 */ + +/* + * MIT License + * + * Copyright (c) 2024 Cryspen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libssh/mlkem_native.h" + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# define __attribute__(x) +#endif +#define KRML_MUSTINLINE inline +#define KRML_NOINLINE __attribute__((noinline, unused)) +#define KRML_HOST_EPRINTF(...) +#define KRML_HOST_EXIT(x) do { \ + fprintf(stderr, "mlkem internal error"); \ + exit(x); \ +} while (0) + +static inline void +store64_le(uint8_t dst[8], uint64_t src) +{ + dst[0] = src & 0xff; + dst[1] = (src >> 8) & 0xff; + dst[2] = (src >> 16) & 0xff; + dst[3] = (src >> 24) & 0xff; + dst[4] = (src >> 32) & 0xff; + dst[5] = (src >> 40) & 0xff; + dst[6] = (src >> 48) & 0xff; + dst[7] = (src >> 56) & 0xff; +} + +static inline uint64_t +load64_le(uint8_t src[8]) +{ + return (uint64_t)(src[0]) | + ((uint64_t)(src[1]) << 8) | + ((uint64_t)(src[2]) << 16) | + ((uint64_t)(src[3]) << 24) | + ((uint64_t)(src[4]) << 32) | + ((uint64_t)(src[5]) << 40) | + ((uint64_t)(src[6]) << 48) | + ((uint64_t)(src[7]) << 56); +} + +#ifdef MISSING_BUILTIN_POPCOUNT +static inline unsigned int +__builtin_popcount(unsigned int num) +{ + const int v[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + return v[num & 0xf] + v[(num >> 4) & 0xf]; +} +#endif + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/eurydice_glue.h */ + + +#ifdef _MSC_VER +// For __popcnt +#endif + + +// C++ HELPERS + +#if defined(__cplusplus) + +#ifndef KRML_HOST_EPRINTF +#define KRML_HOST_EPRINTF(...) fprintf(stderr, __VA_ARGS__) +#endif + + +#ifndef __cpp_lib_type_identity +template +struct type_identity { + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; +#else +using std::type_identity_t; +#endif + +#define KRML_UNION_CONSTRUCTOR(T) \ + template \ + constexpr T(int t, V U::*m, type_identity_t v) : tag(t) { \ + val.*m = std::move(v); \ + } \ + T() = default; + +#endif + +// GENERAL-PURPOSE STUFF + +#define LowStar_Ignore_ignore(e, t, _ret_t) ((void)e) + +#define EURYDICE_ASSERT(test, msg) \ + do { \ + if (!(test)) { \ + fprintf(stderr, "assertion \"%s\" failed: file \"%s\", line %d\n", msg, \ + __FILE__, __LINE__); \ + exit(255); \ + } \ + } while (0) + +// SLICES, ARRAYS, ETC. + +// We represent a slice as a pair of an (untyped) pointer, along with the length +// of the slice, i.e. the number of elements in the slice (this is NOT the +// number of bytes). This design choice has two important consequences. +// - if you need to use `ptr`, you MUST cast it to a proper type *before* +// performing pointer arithmetic on it (remember that C desugars pointer +// arithmetic based on the type of the address) +// - if you need to use `len` for a C style function (e.g. memcpy, memcmp), you +// need to multiply it by sizeof t, where t is the type of the elements. +// +// Empty slices have `len == 0` and `ptr` always needs to be a valid pointer +// that is not NULL (otherwise the construction in EURYDICE_SLICE computes `NULL +// + start`). +typedef struct { + void *ptr; + size_t len; +} Eurydice_slice; + +#if defined(__cplusplus) +#define KRML_CLITERAL(type) type +#else +#define KRML_CLITERAL(type) (type) +#endif + +#if defined(__cplusplus) && defined(__cpp_designated_initializers) || \ + !(defined(__cplusplus)) +#define EURYDICE_CFIELD(X) X +#else +#define EURYDICE_CFIELD(X) +#endif + +// Helper macro to create a slice out of a pointer x, a start index in x +// (included), and an end index in x (excluded). The argument x must be suitably +// cast to something that can decay (see remark above about how pointer +// arithmetic works in C), meaning either pointer or array type. +#define EURYDICE_SLICE(x, start, end) \ + (KRML_CLITERAL(Eurydice_slice){(void *)(x + start), end - start}) + +// Slice length +#define EURYDICE_SLICE_LEN(s, _) (s).len +#define Eurydice_slice_len(s, _) (s).len + +// This macro is a pain because in case the dereferenced element type is an +// array, you cannot simply write `t x` as it would yield `int[4] x` instead, +// which is NOT correct C syntax, so we add a dedicated phase in Eurydice that +// adds an extra argument to this macro at the last minute so that we have the +// correct type of *pointers* to elements. +#define Eurydice_slice_index(s, i, t, t_ptr_t) (((t_ptr_t)s.ptr)[i]) + +// The following functions get sub slices from a slice. + +#define Eurydice_slice_subslice(s, r, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, r.start, r.end) + +// Variant for when the start and end indices are statically known (i.e., the +// range argument `r` is a literal). +#define Eurydice_slice_subslice2(s, start, end, t) \ + EURYDICE_SLICE((t *)s.ptr, (start), (end)) + +// Previous version above does not work when t is an array type (as usual). Will +// be deprecated soon. +#define Eurydice_slice_subslice3(s, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)s.ptr, (start), (end)) + +#define Eurydice_slice_subslice_to(s, subslice_end_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, 0, subslice_end_pos) + +#define Eurydice_slice_subslice_from(s, subslice_start_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, subslice_start_pos, s.len) + +#define Eurydice_array_to_slice(end, x, t) \ + EURYDICE_SLICE(x, 0, \ + end) /* x is already at an array type, no need for cast */ +#define Eurydice_array_to_subslice(_arraylen, x, r, t, _0, _1) \ + EURYDICE_SLICE((t *)x, r.start, r.end) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice2(x, start, end, t) \ + EURYDICE_SLICE((t *)x, (start), (end)) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice3(x, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)x, (start), (end)) + +#define Eurydice_array_repeat(dst, len, init, t) \ + ERROR "should've been desugared" + +// The following functions convert an array into a slice. + +#define Eurydice_array_to_subslice_to(_size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, 0, r) +#define Eurydice_array_to_subslice_from(size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, r, size) + +// Copy a slice with memcopy +#define Eurydice_slice_copy(dst, src, t) \ + memcpy(dst.ptr, src.ptr, dst.len * sizeof(t)) + +#define core_array___Array_T__N___as_slice(len_, ptr_, t, _ret_t) \ + KRML_CLITERAL(Eurydice_slice) { ptr_, len_ } + +#define core_array__core__clone__Clone_for__Array_T__N___clone( \ + len, src, dst, elem_type, _ret_t) \ + (memcpy(dst, src, len * sizeof(elem_type))) +#define TryFromSliceError uint8_t +#define core_array_TryFromSliceError uint8_t + +#define Eurydice_array_eq(sz, a1, a2, t) (memcmp(a1, a2, sz * sizeof(t)) == 0) + +// core::cmp::PartialEq<&0 (@Slice)> for @Array +#define Eurydice_array_eq_slice(sz, a1, s2, t, _) \ + (memcmp(a1, (s2)->ptr, sz * sizeof(t)) == 0) + +#define core_array_equality___core__cmp__PartialEq__Array_U__N___for__Array_T__N____eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, a2, t, _) +#define core_array_equality___core__cmp__PartialEq__0___Slice_U____for__Array_T__N___3__eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, ((a2)->ptr), t, _) + +#define Eurydice_slice_split_at(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, 0, mid), \ + EURYDICE_CFIELD(.snd =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, mid, (slice).len) \ + } + +#define Eurydice_slice_split_at_mut(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + KRML_CLITERAL(Eurydice_slice){EURYDICE_CFIELD(.ptr =)(slice.ptr), \ + EURYDICE_CFIELD(.len =) mid}, \ + EURYDICE_CFIELD(.snd =) KRML_CLITERAL(Eurydice_slice) { \ + EURYDICE_CFIELD(.ptr =) \ + ((char *)slice.ptr + mid * sizeof(element_type)), \ + EURYDICE_CFIELD(.len =)(slice.len - mid) \ + } \ + } + +// Conversion of slice to an array, rewritten (by Eurydice) to name the +// destination array, since arrays are not values in C. +// N.B.: see note in karamel/lib/Inlining.ml if you change this. +#define Eurydice_slice_to_array2(dst, src, _0, t_arr, _1) \ + Eurydice_slice_to_array3(&(dst)->tag, (char *)&(dst)->val.case_Ok, src, \ + sizeof(t_arr)) + +static inline void Eurydice_slice_to_array3(uint8_t *dst_tag, char *dst_ok, + Eurydice_slice src, size_t sz) { + *dst_tag = 0; + memcpy(dst_ok, src.ptr, sz); +} + +// SUPPORT FOR DSTs (Dynamically-Sized Types) + +// A DST is a fat pointer that keeps tracks of the size of it flexible array +// member. Slices are a specific case of DSTs, where [T; N] implements +// Unsize<[T]>, meaning an array of statically known size can be converted to a +// fat pointer, i.e. a slice. +// +// Unlike slices, DSTs have a built-in definition that gets monomorphized, of +// the form: +// +// typedef struct { +// T *ptr; +// size_t len; // number of elements +// } Eurydice_dst; +// +// Furthermore, T = T0<[U0]> where `struct T0`, where the `U` is the +// last field. This means that there are two monomorphizations of T0 in the +// program. One is `T0<[V; N]>` +// -- this is directly converted to a Eurydice_dst via suitable codegen (no +// macro). The other is `T = T0<[U]>`, where `[U]` gets emitted to +// `Eurydice_derefed_slice`, a type that only appears in that precise situation +// and is thus defined to give rise to a flexible array member. + +typedef char Eurydice_derefed_slice[]; + +#define Eurydice_slice_of_dst(fam_ptr, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(fam_ptr), .len = len_}) + +#define Eurydice_slice_of_boxed_array(ptr_, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(ptr_), .len = len_}) + +// CORE STUFF (conversions, endianness, ...) + +// We slap extern "C" on declarations that intend to implement a prototype +// generated by Eurydice, because Eurydice prototypes are always emitted within +// an extern "C" block, UNLESS you use -fcxx17-compat, in which case, you must +// pass -DKRML_CXX17_COMPAT="" to your C++ compiler. +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +extern "C" { +#endif + +static inline void core_num__u64__to_le_bytes(uint64_t v, uint8_t buf[8]) { + store64_le(buf, v); +} + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t buf[8]) { + return load64_le(buf); +} + +// unsigned overflow wraparound semantics in C +static inline uint16_t core_num__u16__wrapping_add(uint16_t x, uint16_t y) { + return x + y; +} +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x, uint8_t y) { + return x - y; +} +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1) { + return (x0 << x1 | x0 >> (64 - x1)); +} + +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +} +#endif + +// ITERATORS + +#define Eurydice_range_iter_next(iter_ptr, t, ret_t) \ + (((iter_ptr)->start >= (iter_ptr)->end) \ + ? (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 0, \ + EURYDICE_CFIELD(.f0 =) 0}) \ + : (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 1, \ + EURYDICE_CFIELD(.f0 =)(iter_ptr)->start++})) + +#define core_iter_range___core__iter__traits__iterator__Iterator_A__for_core__ops__range__Range_A__TraitClause_0___6__next \ + Eurydice_range_iter_next + +// See note in karamel/lib/Inlining.ml if you change this +#define Eurydice_into_iter(x, t, _ret_t, _) (x) +#define core_iter_traits_collect___core__iter__traits__collect__IntoIterator_Clause1_Item__I__for_I__1__into_iter \ + Eurydice_into_iter + +typedef struct { + Eurydice_slice s; + size_t index; +} Eurydice_slice_iterator; + +#define core_slice___Slice_T___iter(x, t, _ret_t) \ + ((Eurydice_slice_iterator){.s = x, .index = 0}) +#define core_slice_iter_Iter Eurydice_slice_iterator +#define core_slice_iter__core__slice__iter__Iter__a__T__181__next(iter, t, \ + ret_t) \ + (((iter)->index == (iter)->s.len) \ + ? (KRML_CLITERAL(ret_t){.tag = core_option_None}) \ + : (KRML_CLITERAL(ret_t){ \ + .tag = core_option_Some, \ + .f0 = ((iter)->index++, \ + &((t *)((iter)->s.ptr))[(iter)->index - 1])})) +#define core_option__core__option__Option_T__TraitClause_0___is_some(X, _0, \ + _1) \ + ((X)->tag == 1) +// STRINGS + +typedef const char *Prims_string; + +// MISC (UNTESTED) + +typedef void *core_fmt_Formatter; +typedef void *core_fmt_Arguments; +typedef void *core_fmt_rt_Argument; +#define core_fmt_rt__core__fmt__rt__Argument__a__1__new_display(x1, x2, x3, \ + x4) \ + NULL + +// BOXES + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem_core.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem_core_H +#define libcrux_mlkem_core_H + + +#if defined(__cplusplus) +extern "C" { +#endif + +/** +A monomorphic instance of core.ops.range.Range +with types size_t + +*/ +typedef struct core_ops_range_Range_08_s { + size_t start; + size_t end; +} core_ops_range_Range_08; + +static inline uint16_t core_num__u16__wrapping_add(uint16_t x0, uint16_t x1); + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t x0[8U]); + +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1); + +static inline void core_num__u64__to_le_bytes(uint64_t x0, uint8_t x1[8U]); + +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x0, uint8_t x1); + +#define LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT ((size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT ((size_t)256U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * (size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_G_DIGEST_SIZE ((size_t)64U) + +#define LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE ((size_t)32U) + +/** + K * BITS_PER_RING_ELEMENT / 8 + + [eurydice] Note that we can't use const generics here because that breaks + C extraction with eurydice. +*/ +static inline size_t libcrux_ml_kem_constants_ranked_bytes_per_ring_element( + size_t rank) { + return rank * LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U; +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_classify_27_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_declassify_d8_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint8_t libcrux_secrets_int_as_u8_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_90( + (uint8_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_classify_27_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_declassify_d8_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u8} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_59(uint8_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_90(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_classify_27_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_declassify_d8_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_36(int32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_a8(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_declassify_d8_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_classify_27_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint16_t libcrux_secrets_int_as_u16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_de( + (uint16_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_declassify_d8_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_classify_27_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE uint64_t libcrux_secrets_int_as_u64_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_49( + (uint64_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_classify_27_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_declassify_d8_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u64} +*/ +static KRML_MUSTINLINE uint32_t libcrux_secrets_int_as_u32_a3(uint64_t self) { + return libcrux_secrets_int_public_integers_classify_27_df( + (uint32_t)libcrux_secrets_int_public_integers_declassify_d8_49(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +typedef struct libcrux_ml_kem_utils_extraction_helper_Keypair768_s { + uint8_t fst[1152U]; + uint8_t snd[1184U]; +} libcrux_ml_kem_utils_extraction_helper_Keypair768; + +#define Ok 0 +#define Err 1 + +typedef uint8_t Result_b2_tags; + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_fd +with const generics +- SIZE= 1184 +*/ +static inline libcrux_ml_kem_types_MlKemPublicKey_30 +libcrux_ml_kem_types_from_fd_d0(uint8_t value[1184U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1184U]; + memcpy(copy_of_value, value, (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPublicKey_30 lit; + memcpy(lit.value, copy_of_value, (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl +{libcrux_ml_kem::types::MlKemKeyPair} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_17 +with const generics +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_types_from_17_74(libcrux_ml_kem_types_MlKemPrivateKey_d9 sk, + libcrux_ml_kem_types_MlKemPublicKey_30 pk) { + return (KRML_CLITERAL(libcrux_ml_kem_mlkem768_MlKem768KeyPair){.sk = sk, + .pk = pk}); +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPrivateKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_77 +with const generics +- SIZE= 2400 +*/ +static inline libcrux_ml_kem_types_MlKemPrivateKey_d9 +libcrux_ml_kem_types_from_77_28(uint8_t value[2400U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[2400U]; + memcpy(copy_of_value, value, (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 lit; + memcpy(lit.value, copy_of_value, (size_t)2400U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_fb_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[32U]; + TryFromSliceError case_Err; + } val; +} Result_fb; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_b3(Result_fb self, uint8_t ret[32U]) { + if (self.tag == Ok) { + uint8_t f0[32U]; + memcpy(f0, self.val.case_Ok, (size_t)32U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)32U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_e0 +with const generics +- SIZE= 1088 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768Ciphertext +libcrux_ml_kem_types_from_e0_80(uint8_t value[1088U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1088U]; + memcpy(copy_of_value, value, (size_t)1088U * sizeof(uint8_t)); + libcrux_ml_kem_mlkem768_MlKem768Ciphertext lit; + memcpy(lit.value, copy_of_value, (size_t)1088U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_e6 +with const generics +- SIZE= 1184 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_e6_d0( + libcrux_ml_kem_types_MlKemPublicKey_30 *self) { + return self->value; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_a9 +with const generics +- SIZE= 1088 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_a9_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return self->value; +} + +/** +A monomorphic instance of libcrux_ml_kem.utils.prf_input_inc +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE uint8_t libcrux_ml_kem_utils_prf_input_inc_e0( + uint8_t (*prf_inputs)[33U], uint8_t domain_separator) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + prf_inputs[i0][32U] = domain_separator; + domain_separator = (uint32_t)domain_separator + 1U; + } + return domain_separator; +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 33 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_c8( + Eurydice_slice slice, uint8_t ret[33U]) { + uint8_t out[33U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)33U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 34 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_b6( + Eurydice_slice slice, uint8_t ret[34U]) { + uint8_t out[34U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)34U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::convert::AsRef<@Slice> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_ref_d3 +with const generics +- SIZE= 1088 +*/ +static inline Eurydice_slice libcrux_ml_kem_types_as_ref_d3_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return Eurydice_array_to_slice((size_t)1088U, self->value, uint8_t); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_15( + Eurydice_slice slice, uint8_t ret[1120U]) { + uint8_t out[1120U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)1120U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 64 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_slice slice, uint8_t ret[64U]) { + uint8_t out[64U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)64U * sizeof(uint8_t)); +} + +typedef struct Eurydice_slice_uint8_t_x4_s { + Eurydice_slice fst; + Eurydice_slice snd; + Eurydice_slice thd; + Eurydice_slice f3; +} Eurydice_slice_uint8_t_x4; + +typedef struct Eurydice_slice_uint8_t_x2_s { + Eurydice_slice fst; + Eurydice_slice snd; +} Eurydice_slice_uint8_t_x2; + +/** + Unpack an incoming private key into it's different parts. + + We have this here in types to extract into a common core for C. +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.unpack_private_key +with const generics +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline Eurydice_slice_uint8_t_x4 +libcrux_ml_kem_types_unpack_private_key_b4(Eurydice_slice private_key) { + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + private_key, (size_t)1152U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice secret_key0 = uu____0.snd; + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + secret_key0, (size_t)1184U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key = uu____1.fst; + Eurydice_slice secret_key = uu____1.snd; + Eurydice_slice_uint8_t_x2 uu____2 = Eurydice_slice_split_at( + secret_key, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key_hash = uu____2.fst; + Eurydice_slice implicit_rejection_value = uu____2.snd; + return ( + KRML_CLITERAL(Eurydice_slice_uint8_t_x4){.fst = ind_cpa_secret_key, + .snd = ind_cpa_public_key, + .thd = ind_cpa_public_key_hash, + .f3 = implicit_rejection_value}); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[24size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d2(uint8_t self[24U], + uint8_t ret[24U]) { + memcpy(ret, self, (size_t)24U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[20size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_57(uint8_t self[20U], + uint8_t ret[20U]) { + memcpy(ret, self, (size_t)20U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[8size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_76(uint8_t self[8U], + uint8_t ret[8U]) { + memcpy(ret, self, (size_t)8U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[2size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d4(uint8_t self[2U], + uint8_t ret[2U]) { + memcpy(ret, self, (size_t)2U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t[16size_t] + +*/ +static KRML_MUSTINLINE void libcrux_secrets_int_public_integers_classify_27_46( + int16_t self[16U], int16_t ret[16U]) { + memcpy(ret, self, (size_t)16U * sizeof(int16_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types uint8_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_90(Eurydice_slice self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types int16_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_39(Eurydice_slice self) { + return self; +} + +/** +A monomorphic instance of core.result.Result +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_0a_s { + Result_b2_tags tag; + union { + int16_t case_Ok[16U]; + TryFromSliceError case_Err; + } val; +} Result_0a; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_00(Result_0a self, int16_t ret[16U]) { + if (self.tag == Ok) { + int16_t f0[16U]; + memcpy(f0, self.val.case_Ok, (size_t)16U * sizeof(int16_t)); + memcpy(ret, f0, (size_t)16U * sizeof(int16_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_15_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[8U]; + TryFromSliceError case_Err; + } val; +} Result_15; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_68(Result_15 self, uint8_t ret[8U]) { + if (self.tag == Ok) { + uint8_t f0[8U]; + memcpy(f0, self.val.case_Ok, (size_t)8U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)8U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem_core_H_DEFINED +#endif /* libcrux_mlkem_core_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_ct_ops.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_ct_ops_H +#define libcrux_ct_ops_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** + Return 1 if `value` is not zero and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_inz(uint8_t value) { + uint16_t value0 = (uint16_t)value; + uint8_t result = + (uint8_t)((uint32_t)core_num__u16__wrapping_add(~value0, 1U) >> 8U); + return (uint32_t)result & 1U; +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_is_non_zero(uint8_t value) { + return libcrux_ml_kem_constant_time_ops_inz(value); +} + +/** + Return 1 if the bytes of `lhs` and `rhs` do not exactly + match and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t libcrux_ml_kem_constant_time_ops_compare( + Eurydice_slice lhs, Eurydice_slice rhs) { + uint8_t r = 0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(lhs, uint8_t); i++) { + size_t i0 = i; + uint8_t nr = (uint32_t)r | + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) ^ + (uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *)); + r = nr; + } + return libcrux_ml_kem_constant_time_ops_is_non_zero(r); +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs) { + return libcrux_ml_kem_constant_time_ops_compare(lhs, rhs); +} + +/** + If `selector` is not zero, return the bytes in `rhs`; return the bytes in + `lhs` otherwise. +*/ +static KRML_NOINLINE void libcrux_ml_kem_constant_time_ops_select_ct( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + uint8_t mask = core_num__u8__wrapping_sub( + libcrux_ml_kem_constant_time_ops_is_non_zero(selector), 1U); + uint8_t out[32U] = {0U}; + for (size_t i = (size_t)0U; i < LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE; + i++) { + size_t i0 = i; + uint8_t outi = + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) & + (uint32_t)mask) | + ((uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *) & + (uint32_t)~mask); + out[i0] = outi; + } + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + libcrux_ml_kem_constant_time_ops_select_ct(lhs, rhs, selector, ret); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + Eurydice_slice lhs_c, Eurydice_slice rhs_c, Eurydice_slice lhs_s, + Eurydice_slice rhs_s, uint8_t ret[32U]) { + uint8_t selector = + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + lhs_c, rhs_c); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + lhs_s, rhs_s, selector, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_ct_ops_H_DEFINED +#endif /* libcrux_ct_ops_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_sha3_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_sha3_portable_H +#define libcrux_sha3_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_zero_d2(void) { + return 0ULL; +} + +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable__veor5q_u64( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return (((a ^ b) ^ c) ^ d) ^ e; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor5_d2( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return libcrux_sha3_simd_portable__veor5q_u64(a, b, c, d, e); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_76(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)1); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vrax1q_u64(uint64_t a, uint64_t b) { + uint64_t uu____0 = a; + return uu____0 ^ libcrux_sha3_simd_portable_rotate_left_76(b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left1_and_xor_d2(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vrax1q_u64(a, b); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vbcaxq_u64(uint64_t a, uint64_t b, uint64_t c) { + return a ^ (b & ~c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_and_not_xor_d2(uint64_t a, uint64_t b, uint64_t c) { + return libcrux_sha3_simd_portable__vbcaxq_u64(a, b, c); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__veorq_n_u64(uint64_t a, uint64_t c) { + return a ^ c; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_constant_d2(uint64_t a, uint64_t c) { + return libcrux_sha3_simd_portable__veorq_n_u64(a, c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor_d2(uint64_t a, + uint64_t b) { + return a ^ b; +} + +static const uint64_t + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[24U] = { + 1ULL, + 32898ULL, + 9223372036854808714ULL, + 9223372039002292224ULL, + 32907ULL, + 2147483649ULL, + 9223372039002292353ULL, + 9223372036854808585ULL, + 138ULL, + 136ULL, + 2147516425ULL, + 2147483658ULL, + 2147516555ULL, + 9223372036854775947ULL, + 9223372036854808713ULL, + 9223372036854808579ULL, + 9223372036854808578ULL, + 9223372036854775936ULL, + 32778ULL, + 9223372039002259466ULL, + 9223372039002292353ULL, + 9223372036854808704ULL, + 2147483649ULL, + 9223372039002292232ULL}; + +typedef struct size_t_x2_s { + size_t fst; + size_t snd; +} size_t_x2; + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.KeccakState +with types uint64_t +with const generics +- $1size_t +*/ +typedef struct libcrux_sha3_generic_keccak_KeccakState_17_s { + uint64_t st[25U]; +} libcrux_sha3_generic_keccak_KeccakState_17; + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.new_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_generic_keccak_new_80_04(void) { + libcrux_sha3_generic_keccak_KeccakState_17 lit; + uint64_t repeat_expression[25U]; + for (size_t i = (size_t)0U; i < (size_t)25U; i++) { + repeat_expression[i] = libcrux_sha3_simd_portable_zero_d2(); + } + memcpy(lit.st, repeat_expression, (size_t)25U * sizeof(uint64_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_sha3.traits.get_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE uint64_t *libcrux_sha3_traits_get_ij_04(uint64_t *arr, + size_t i, + size_t j) { + return &arr[(size_t)5U * j + i]; +} + +/** +A monomorphic instance of libcrux_sha3.traits.set_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_traits_set_ij_04(uint64_t *arr, + size_t i, size_t j, + uint64_t value) { + arr[(size_t)5U * j + i] = value; +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_f8( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_f8(self->st, input[0U], start); +} + +/** +This function found in impl {core::ops::index::Index<(usize, usize), T> for +libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.index_c2 +with types uint64_t +with const generics +- N= 1 +*/ +static inline uint64_t *libcrux_sha3_generic_keccak_index_c2_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t_x2 index) { + return libcrux_sha3_traits_get_ij_04(self->st, index.fst, index.snd); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.theta_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_theta_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t ret[5U]) { + uint64_t c[5U] = { + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U])}; + uint64_t uu____0 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)0U + (size_t)4U) % (size_t)5U], + c[((size_t)0U + (size_t)1U) % (size_t)5U]); + uint64_t uu____1 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)1U + (size_t)4U) % (size_t)5U], + c[((size_t)1U + (size_t)1U) % (size_t)5U]); + uint64_t uu____2 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)2U + (size_t)4U) % (size_t)5U], + c[((size_t)2U + (size_t)1U) % (size_t)5U]); + uint64_t uu____3 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)3U + (size_t)4U) % (size_t)5U], + c[((size_t)3U + (size_t)1U) % (size_t)5U]); + ret[0U] = uu____0; + ret[1U] = uu____1; + ret[2U] = uu____2; + ret[3U] = uu____3; + ret[4U] = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)4U + (size_t)4U) % (size_t)5U], + c[((size_t)4U + (size_t)1U) % (size_t)5U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.set_80 +with types uint64_t +with const generics +- N= 1 +*/ +static inline void libcrux_sha3_generic_keccak_set_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i, size_t j, + uint64_t v) { + libcrux_sha3_traits_set_ij_04(self->st, i, j, v); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_02(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)36); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_02(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_02(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ac(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)3); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ac(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ac(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_020(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)41); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_020(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_020(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_a9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)18); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_a9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_a9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_76(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_76(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_58(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)44); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_58(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_58(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_e0(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)10); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_e0(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_e0(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_63(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)45); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_63(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_63(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6a(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)2); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6a(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6a(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ab(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)62); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ab(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ab(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_5b(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)6); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_5b(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_5b(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6f(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)43); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6f(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6f(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_62(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)15); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_62(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_62(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_23(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)61); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_23(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_23(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_37(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)28); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_37(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_37(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_bb(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)55); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_bb(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_bb(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_b9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)25); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_b9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_b9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_54(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)21); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_54(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_54(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_4c(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)56); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_4c(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_4c(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ce(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)27); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ce(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ce(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_77(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)20); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_77(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_77(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_25(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)39); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_25(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_25(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_af(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)8); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_af(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_af(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_fd(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)14); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_fd(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_fd(a, b); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.rho_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_rho_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t t[5U]) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____0, (size_t)1U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_02( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____1 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____1, (size_t)2U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ac( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____2 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____2, (size_t)3U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_020( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____3 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____3, (size_t)4U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_a9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____4 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____4, (size_t)0U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_76( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____5 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____5, (size_t)1U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_58( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____6 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____6, (size_t)2U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_e0( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____7 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____7, (size_t)3U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_63( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____8 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____8, (size_t)4U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6a( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____9 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____9, (size_t)0U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ab( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____10 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____10, (size_t)1U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_5b( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____11 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____11, (size_t)2U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6f( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____12 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____12, (size_t)3U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_62( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____13 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____13, (size_t)4U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_23( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____14 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____14, (size_t)0U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_37( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____15 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____15, (size_t)1U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_bb( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____16 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____16, (size_t)2U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_b9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____17 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____17, (size_t)3U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_54( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____18 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____18, (size_t)4U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_4c( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____19 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____19, (size_t)0U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ce( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____20 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____20, (size_t)1U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_77( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____21 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____21, (size_t)2U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_25( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____22 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____22, (size_t)3U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_af( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____23 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____23, (size_t)4U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_fd( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U], + t[4U])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.pi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_pi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.chi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_chi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)5U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)5U; i++) { + size_t j = i; + libcrux_sha3_generic_keccak_set_80_04( + self, i1, j, + libcrux_sha3_simd_portable_and_not_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = i1, .snd = j}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)2U) % (size_t)5U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)1U) % (size_t)5U}))[0U])); + } + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.iota_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_iota_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_constant_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[i])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.keccakf1600_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_keccakf1600_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + for (size_t i = (size_t)0U; i < (size_t)24U; i++) { + size_t i0 = i; + uint64_t t[5U]; + libcrux_sha3_generic_keccak_theta_80_04(self, t); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + uint64_t uu____1[5U]; + memcpy(uu____1, t, (size_t)5U * sizeof(uint64_t)); + libcrux_sha3_generic_keccak_rho_80_04(uu____0, uu____1); + libcrux_sha3_generic_keccak_pi_80_04(self); + libcrux_sha3_generic_keccak_chi_80_04(self); + libcrux_sha3_generic_keccak_iota_80_04(self, i0); + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_f8(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_96( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[72U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)72U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_f8( + state, Eurydice_array_to_slice((size_t)72U, buffer, uint8_t), (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_96( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_96(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_96(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_f8( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_f8(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 72 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_96( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)72U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c6(&s, buf, i0 * (size_t)72U); + } + size_t rem = data_len % (size_t)72U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)72U; + size_t last = outlen - outlen % (size_t)72U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, (size_t)72U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, i0 * (size_t)72U, + (size_t)72U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 512 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha512(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_96(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_5b( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_5b(self->st, input[0U], start); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c60( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_5b(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_5b( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_5b(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e0(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha256(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad0( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad0(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e1( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad0(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 31 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad0( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e1(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHAKE256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_shake256( + Eurydice_slice digest, Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad0(data, digest); +} + +typedef libcrux_sha3_generic_keccak_KeccakState_17 + libcrux_sha3_portable_KeccakState; + +/** + Create a new SHAKE-128 state object. +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_portable_incremental_shake128_init(void) { + return libcrux_sha3_generic_keccak_new_80_04(); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_3a( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_c6( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[168U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)168U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_3a( + state, Eurydice_array_to_slice((size_t)168U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_c6(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 168 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e2( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_c6(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** + Absorb +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_absorb_final( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice data0) { + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = s; + Eurydice_slice uu____1[1U] = {data0}; + libcrux_sha3_generic_keccak_absorb_final_80_9e2( + uu____0, uu____1, (size_t)0U, Eurydice_slice_len(data0, uint8_t)); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_3a( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 168 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_3a(self->st, out, start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_first_three_blocks_b4 with const +generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out) { + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)0U, (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)168U, + (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)2U * (size_t)168U, + (size_t)168U); +} + +/** + Squeeze three blocks +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a(s, + out0); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_next_block_b4 with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, start, (size_t)168U); +} + +/** + Squeeze another block +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a(s, out0, + (size_t)0U); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_sha3_portable_H_DEFINED +#endif /* libcrux_sha3_portable_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem768_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem768_portable_H +#define libcrux_mlkem768_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +static inline void libcrux_ml_kem_hash_functions_portable_G( + Eurydice_slice input, uint8_t ret[64U]) { + uint8_t digest[64U] = {0U}; + libcrux_sha3_portable_sha512( + Eurydice_array_to_slice((size_t)64U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)64U * sizeof(uint8_t)); +} + +static inline void libcrux_ml_kem_hash_functions_portable_H( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_sha256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +static const int16_t libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[128U] = + {(int16_t)-1044, (int16_t)-758, (int16_t)-359, (int16_t)-1517, + (int16_t)1493, (int16_t)1422, (int16_t)287, (int16_t)202, + (int16_t)-171, (int16_t)622, (int16_t)1577, (int16_t)182, + (int16_t)962, (int16_t)-1202, (int16_t)-1474, (int16_t)1468, + (int16_t)573, (int16_t)-1325, (int16_t)264, (int16_t)383, + (int16_t)-829, (int16_t)1458, (int16_t)-1602, (int16_t)-130, + (int16_t)-681, (int16_t)1017, (int16_t)732, (int16_t)608, + (int16_t)-1542, (int16_t)411, (int16_t)-205, (int16_t)-1571, + (int16_t)1223, (int16_t)652, (int16_t)-552, (int16_t)1015, + (int16_t)-1293, (int16_t)1491, (int16_t)-282, (int16_t)-1544, + (int16_t)516, (int16_t)-8, (int16_t)-320, (int16_t)-666, + (int16_t)-1618, (int16_t)-1162, (int16_t)126, (int16_t)1469, + (int16_t)-853, (int16_t)-90, (int16_t)-271, (int16_t)830, + (int16_t)107, (int16_t)-1421, (int16_t)-247, (int16_t)-951, + (int16_t)-398, (int16_t)961, (int16_t)-1508, (int16_t)-725, + (int16_t)448, (int16_t)-1065, (int16_t)677, (int16_t)-1275, + (int16_t)-1103, (int16_t)430, (int16_t)555, (int16_t)843, + (int16_t)-1251, (int16_t)871, (int16_t)1550, (int16_t)105, + (int16_t)422, (int16_t)587, (int16_t)177, (int16_t)-235, + (int16_t)-291, (int16_t)-460, (int16_t)1574, (int16_t)1653, + (int16_t)-246, (int16_t)778, (int16_t)1159, (int16_t)-147, + (int16_t)-777, (int16_t)1483, (int16_t)-602, (int16_t)1119, + (int16_t)-1590, (int16_t)644, (int16_t)-872, (int16_t)349, + (int16_t)418, (int16_t)329, (int16_t)-156, (int16_t)-75, + (int16_t)817, (int16_t)1097, (int16_t)603, (int16_t)610, + (int16_t)1322, (int16_t)-1285, (int16_t)-1465, (int16_t)384, + (int16_t)-1215, (int16_t)-136, (int16_t)1218, (int16_t)-1335, + (int16_t)-874, (int16_t)220, (int16_t)-1187, (int16_t)-1659, + (int16_t)-1185, (int16_t)-1530, (int16_t)-1278, (int16_t)794, + (int16_t)-1510, (int16_t)-854, (int16_t)-870, (int16_t)478, + (int16_t)-108, (int16_t)-308, (int16_t)996, (int16_t)991, + (int16_t)958, (int16_t)-1460, (int16_t)1522, (int16_t)1628}; + +static KRML_MUSTINLINE int16_t libcrux_ml_kem_polynomial_zeta(size_t i) { + return libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[i]; +} + +#define LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS \ + ((int16_t)1353) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS ((int16_t)3329) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R \ + (62209U) + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_s { + int16_t elements[16U]; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + Eurydice_slice array) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + Result_0a dst; + Eurydice_slice_to_array2( + &dst, Eurydice_slice_subslice3(array, (size_t)0U, (size_t)16U, int16_t *), + Eurydice_slice, int16_t[16U], TryFromSliceError); + unwrap_26_00(dst, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_from_i16_array_b8(Eurydice_slice array) { + return libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + libcrux_secrets_int_classify_public_classify_ref_9b_39(array)); +} + +typedef struct int16_t_x8_s { + int16_t fst; + int16_t snd; + int16_t thd; + int16_t f3; + int16_t f4; + int16_t f5; + int16_t f6; + int16_t f7; +} int16_t_x8; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_zero(void) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + int16_t buf[16U] = {0U}; + libcrux_secrets_int_public_integers_classify_27_46(buf, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ZERO_b8(void) { + return libcrux_ml_kem_vector_portable_vector_type_zero(); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_add( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] + rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_add_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_add(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_sub( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] - rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_sub_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_sub(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] * c; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + return libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant(vec, c); +} + +/** + Note: This function is not secret independent + Only use with public values. +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + if (libcrux_secrets_int_public_integers_declassify_d8_39( + vec.elements[i0]) >= (int16_t)3329) { + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] - (int16_t)3329; + } + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_cond_subtract_3329_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v) { + return libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329(v); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER \ + ((int32_t)20159) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT ((int32_t)26) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R \ + ((int32_t)1 << (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT) + +/** + Signed Barrett Reduction + + Given an input `value`, `barrett_reduce` outputs a representative `result` + such that: + + - result ≡ value (mod FIELD_MODULUS) + - the absolute value of `result` is bound as follows: + + `|result| ≤ FIELD_MODULUS / 2 · (|value|/BARRETT_R + 1) + + Note: The input bound is 28296 to prevent overflow in the multiplication of + quotient by FIELD_MODULUS + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + int16_t value) { + int32_t t = libcrux_secrets_int_as_i32_f5(value) * + LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER + + (LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R >> 1U); + int16_t quotient = libcrux_secrets_int_as_i16_36( + t >> (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT); + return value - quotient * LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t vi = + libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + vec.elements[i0]); + vec.elements[i0] = vi; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce(vector); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT (16U) + +/** + Signed Montgomery Reduction + + Given an input `value`, `montgomery_reduce` outputs a representative `o` + such that: + + - o ≡ value · MONTGOMERY_R^(-1) (mod FIELD_MODULUS) + - the absolute value of `o` is bound as follows: + + `|result| ≤ ceil(|value| / MONTGOMERY_R) + 1665 + + In particular, if `|value| ≤ FIELD_MODULUS-1 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS-1`. And, if `|value| ≤ pow2 16 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS + 1664 + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + int32_t value) { + int32_t k = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(value)) * + libcrux_secrets_int_as_i32_b8( + libcrux_secrets_int_public_integers_classify_27_df( + LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R)); + int32_t k_times_modulus = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(k)) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + int16_t c = libcrux_secrets_int_as_i16_36( + k_times_modulus >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + int16_t value_high = libcrux_secrets_int_as_i16_36( + value >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + return value_high - c; +} + +/** + If `fe` is some field element 'x' of the Kyber field and `fer` is congruent to + `y · MONTGOMERY_R`, this procedure outputs a value that is congruent to + `x · y`, as follows: + + `fe · fer ≡ x · y · MONTGOMERY_R (mod FIELD_MODULUS)` + + `montgomery_reduce` takes the value `x · y · MONTGOMERY_R` and outputs a + representative `x · y · MONTGOMERY_R * MONTGOMERY_R^{-1} ≡ x · y (mod + FIELD_MODULUS)`. +*/ +static KRML_MUSTINLINE int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + int16_t fe, int16_t fer) { + int32_t product = + libcrux_secrets_int_as_i32_f5(fe) * libcrux_secrets_int_as_i32_f5(fer); + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + product); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec.elements[i0], c); + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector, + int16_t constant) { + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + vector, libcrux_secrets_int_public_integers_classify_27_39(constant)); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] & c; + } + return vec; +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.arithmetic.shift_right +with const generics +- SHIFT_BY= 15 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = vec.elements[i0] >> (uint32_t)(int32_t)15; + } + return vec; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef(a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector fm = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + t, LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS); + return libcrux_ml_kem_vector_portable_arithmetic_add(a, &fm); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_to_unsigned_representative_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + a); +} + +/** + The `compress_*` functions implement the `Compress` function specified in the + NIST FIPS 203 standard (Page 18, Expression 4.5), which is defined as: + + ```plaintext + Compress_d: ℤq -> ℤ_{2ᵈ} + Compress_d(x) = ⌈(2ᵈ/q)·x⌋ + ``` + + Since `⌈x⌋ = ⌊x + 1/2⌋` we have: + + ```plaintext + Compress_d(x) = ⌊(2ᵈ/q)·x + 1/2⌋ + = ⌊(2^{d+1}·x + q) / 2q⌋ + ``` + + For further information about the function implementations, consult the + `implementation_notes.pdf` document in this directory. + + The NIST FIPS 203 standard can be found at + . +*/ +static inline uint8_t +libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + uint16_t fe) { + int16_t shifted = + libcrux_secrets_int_public_integers_classify_27_39((int16_t)1664) - + libcrux_secrets_int_as_i16_ca(fe); + int16_t mask = shifted >> 15U; + int16_t shifted_to_positive = mask ^ shifted; + int16_t shifted_positive_in_range = shifted_to_positive - (int16_t)832; + int16_t r0 = shifted_positive_in_range >> 15U; + int16_t r1 = r0 & (int16_t)1; + return libcrux_secrets_int_as_u8_f5(r1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + a.elements[i0] = libcrux_secrets_int_as_i16_59( + libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_1(a); +} + +static KRML_MUSTINLINE uint32_t +libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + uint8_t n, uint32_t value) { + return value & ((1U << (uint32_t)n) - 1U); +} + +static inline int16_t +libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + uint8_t coefficient_bits, uint16_t fe) { + uint64_t compressed = libcrux_secrets_int_as_u64_ca(fe) + << (uint32_t)coefficient_bits; + compressed = compressed + 1664ULL; + compressed = compressed * 10321340ULL; + compressed = compressed >> 35U; + return libcrux_secrets_int_as_i16_b8( + libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + coefficient_bits, libcrux_secrets_int_as_u32_a3(compressed))); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector z = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_vector_type_PortableVector s = + libcrux_ml_kem_vector_portable_arithmetic_sub(z, &a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector res = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + s, (int16_t)1665); + return res; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_1(a); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t t = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec->elements[j], + libcrux_secrets_int_public_integers_classify_27_39(zeta)); + int16_t a_minus_t = vec->elements[i] - t; + int16_t a_plus_t = vec->elements[i] + t; + vec->elements[j] = a_minus_t; + vec->elements[i] = a_plus_t; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step(a, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step(a, zeta0, zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step(a, zeta); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_inv_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t a_minus_b = vec->elements[j] - vec->elements[i]; + int16_t a_plus_b = vec->elements[j] + vec->elements[i]; + int16_t o0 = libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + a_plus_b); + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + a_minus_b, libcrux_secrets_int_public_integers_classify_27_39(zeta)); + vec->elements[i] = o0; + vec->elements[j] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + a, zeta0, zeta1, zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step(a, zeta0, + zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step(a, zeta); +} + +/** + Compute the product of two Kyber binomials with respect to the + modulus `X² - zeta`. + + This function almost implements Algorithm 11 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: a₀, a₁, b₀, b₁ ∈ ℤq. + Input: γ ∈ ℤq. + Output: c₀, c₁ ∈ ℤq. + + c₀ ← a₀·b₀ + a₁·b₁·γ + c₁ ← a₀·b₁ + a₁·b₀ + return c₀, c₁ + ``` + We say "almost" because the coefficients output by this function are in + the Montgomery domain (unlike in the specification). + + The NIST FIPS 203 standard can be found at + . +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *b, int16_t zeta, + size_t i, libcrux_ml_kem_vector_portable_vector_type_PortableVector *out) { + int16_t ai = a->elements[(size_t)2U * i]; + int16_t bi = b->elements[(size_t)2U * i]; + int16_t aj = a->elements[(size_t)2U * i + (size_t)1U]; + int16_t bj = b->elements[(size_t)2U * i + (size_t)1U]; + int32_t ai_bi = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bi); + int32_t aj_bj_ = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bj); + int16_t aj_bj = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + aj_bj_); + int32_t aj_bj_zeta = libcrux_secrets_int_as_i32_f5(aj_bj) * + libcrux_secrets_int_as_i32_f5(zeta); + int32_t ai_bi_aj_bj = ai_bi + aj_bj_zeta; + int16_t o0 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bi_aj_bj); + int32_t ai_bj = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bj); + int32_t aj_bi = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bi); + int32_t ai_bj_aj_bi = ai_bj + aj_bi; + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bj_aj_bi); + out->elements[(size_t)2U * i] = o0; + out->elements[(size_t)2U * i + (size_t)1U] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_multiply( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + int16_t nzeta0 = -zeta0; + int16_t nzeta1 = -zeta1; + int16_t nzeta2 = -zeta2; + int16_t nzeta3 = -zeta3; + libcrux_ml_kem_vector_portable_vector_type_PortableVector out = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta0), + (size_t)0U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta0), + (size_t)1U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta1), + (size_t)2U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta1), + (size_t)3U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta2), + (size_t)4U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta2), + (size_t)5U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta3), + (size_t)6U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta3), + (size_t)7U, &out); + return out; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_multiply_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_multiply(lhs, rhs, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[2U]) { + uint8_t result0 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[0U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[1U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[2U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[3U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[4U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[5U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[6U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[7U]) << 7U; + uint8_t result1 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[8U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[9U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[10U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[11U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[12U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[13U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[14U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[15U]) << 7U; + ret[0U] = result0; + ret[1U] = result1; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + uint8_t ret0[2U]; + libcrux_ml_kem_vector_portable_serialize_serialize_1(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d4(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + libcrux_ml_kem_vector_portable_serialize_1(a, ret); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_1(Eurydice_slice v) { + int16_t result0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) & 1U); + int16_t result1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 7U & + 1U); + int16_t result8 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) & 1U); + int16_t result9 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result10 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result11 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result12 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result13 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result14 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result15 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 7U & + 1U); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {result0, result1, result2, result3, result4, result5, + result6, result7, result8, result9, result10, result11, + result12, result13, result14, result15}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_1( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_1(a); +} + +typedef struct uint8_t_x4_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; +} uint8_t_x4; + +static KRML_MUSTINLINE uint8_t_x4 +libcrux_ml_kem_vector_portable_serialize_serialize_4_int(Eurydice_slice v) { + uint8_t result0 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)0U, int16_t, int16_t *)); + uint8_t result1 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)2U, int16_t, int16_t *)); + uint8_t result2 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)5U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)4U, int16_t, int16_t *)); + uint8_t result3 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)7U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)6U, int16_t, int16_t *)); + return (KRML_CLITERAL(uint8_t_x4){ + .fst = result0, .snd = result1, .thd = result2, .f3 = result3}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[8U]) { + uint8_t_x4 result0_3 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)8U, + int16_t *)); + uint8_t_x4 result4_7 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)16U, + int16_t *)); + ret[0U] = result0_3.fst; + ret[1U] = result0_3.snd; + ret[2U] = result0_3.thd; + ret[3U] = result0_3.f3; + ret[4U] = result4_7.fst; + ret[5U] = result4_7.snd; + ret[6U] = result4_7.thd; + ret[7U] = result4_7.f3; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + uint8_t ret0[8U]; + libcrux_ml_kem_vector_portable_serialize_serialize_4(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_76(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_4_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + libcrux_ml_kem_vector_portable_serialize_4(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice bytes) { + int16_t v0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) & + 15U); + int16_t v1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) & + 15U); + int16_t v3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) & + 15U); + int16_t v5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) & + 15U); + int16_t v7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) >> + 4U & + 15U); + return (KRML_CLITERAL(int16_t_x8){.fst = v0, + .snd = v1, + .thd = v2, + .f3 = v3, + .f4 = v4, + .f5 = v5, + .f6 = v6, + .f7 = v7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_4(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)4U, uint8_t *)); + int16_t_x8 v8_15 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)4U, (size_t)8U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_4( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_4(a); +} + +typedef struct uint8_t_x5_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; + uint8_t f4; +} uint8_t_x5; + +static KRML_MUSTINLINE uint8_t_x5 +libcrux_ml_kem_vector_portable_serialize_serialize_10_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)63) + << 2U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U & + (int16_t)3); + uint8_t r2 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) & (int16_t)15) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 6U & + (int16_t)15); + uint8_t r3 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) & (int16_t)3) + << 6U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) >> 4U & + (int16_t)63); + uint8_t r4 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) >> 2U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x5){ + .fst = r0, .snd = r1, .thd = r2, .f3 = r3, .f4 = r4}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[20U]) { + uint8_t_x5 r0_4 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)4U, + int16_t *)); + uint8_t_x5 r5_9 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)8U, + int16_t *)); + uint8_t_x5 r10_14 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)12U, + int16_t *)); + uint8_t_x5 r15_19 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)16U, + int16_t *)); + ret[0U] = r0_4.fst; + ret[1U] = r0_4.snd; + ret[2U] = r0_4.thd; + ret[3U] = r0_4.f3; + ret[4U] = r0_4.f4; + ret[5U] = r5_9.fst; + ret[6U] = r5_9.snd; + ret[7U] = r5_9.thd; + ret[8U] = r5_9.f3; + ret[9U] = r5_9.f4; + ret[10U] = r10_14.fst; + ret[11U] = r10_14.snd; + ret[12U] = r10_14.thd; + ret[13U] = r10_14.f3; + ret[14U] = r10_14.f4; + ret[15U] = r15_19.fst; + ret[16U] = r15_19.snd; + ret[17U] = r15_19.thd; + ret[18U] = r15_19.f3; + ret[19U] = r15_19.f4; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + uint8_t ret0[20U]; + libcrux_ml_kem_vector_portable_serialize_serialize_10(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_57(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_10_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + libcrux_ml_kem_vector_portable_serialize_10(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice bytes) { + int16_t r0 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r1 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) >> + 2U); + int16_t r2 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) >> + 4U); + int16_t r3 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)4U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) >> + 6U); + int16_t r4 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)5U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r5 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) >> + 2U); + int16_t r6 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) >> + 4U); + int16_t r7 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)9U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) >> + 6U); + return (KRML_CLITERAL(int16_t_x8){.fst = r0, + .snd = r1, + .thd = r2, + .f3 = r3, + .f4 = r4, + .f5 = r5, + .f6 = r6, + .f7 = r7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_10(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)10U, uint8_t *)); + int16_t_x8 v8_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)10U, (size_t)20U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_10( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_10(a); +} + +typedef struct uint8_t_x3_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; +} uint8_t_x3; + +static KRML_MUSTINLINE uint8_t_x3 +libcrux_ml_kem_vector_portable_serialize_serialize_12_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U | + (Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)15) + << 4U); + uint8_t r2 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 4U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x3){.fst = r0, .snd = r1, .thd = r2}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[24U]) { + uint8_t_x3 r0_2 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)2U, + int16_t *)); + uint8_t_x3 r3_5 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)2U, (size_t)4U, + int16_t *)); + uint8_t_x3 r6_8 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)6U, + int16_t *)); + uint8_t_x3 r9_11 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)6U, (size_t)8U, + int16_t *)); + uint8_t_x3 r12_14 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)10U, + int16_t *)); + uint8_t_x3 r15_17 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)10U, (size_t)12U, + int16_t *)); + uint8_t_x3 r18_20 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)14U, + int16_t *)); + uint8_t_x3 r21_23 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)14U, (size_t)16U, + int16_t *)); + ret[0U] = r0_2.fst; + ret[1U] = r0_2.snd; + ret[2U] = r0_2.thd; + ret[3U] = r3_5.fst; + ret[4U] = r3_5.snd; + ret[5U] = r3_5.thd; + ret[6U] = r6_8.fst; + ret[7U] = r6_8.snd; + ret[8U] = r6_8.thd; + ret[9U] = r9_11.fst; + ret[10U] = r9_11.snd; + ret[11U] = r9_11.thd; + ret[12U] = r12_14.fst; + ret[13U] = r12_14.snd; + ret[14U] = r12_14.thd; + ret[15U] = r15_17.fst; + ret[16U] = r15_17.snd; + ret[17U] = r15_17.thd; + ret[18U] = r18_20.fst; + ret[19U] = r18_20.snd; + ret[20U] = r18_20.thd; + ret[21U] = r21_23.fst; + ret[22U] = r21_23.snd; + ret[23U] = r21_23.thd; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + uint8_t ret0[24U]; + libcrux_ml_kem_vector_portable_serialize_serialize_12(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d2(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_12_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + libcrux_ml_kem_vector_portable_serialize_12(a, ret); +} + +typedef struct int16_t_x2_s { + int16_t fst; + int16_t snd; +} int16_t_x2; + +static KRML_MUSTINLINE int16_t_x2 +libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice bytes) { + int16_t byte0 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)); + int16_t byte1 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)); + int16_t byte2 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)); + int16_t r0 = (byte1 & (int16_t)15) << 8U | (byte0 & (int16_t)255); + int16_t r1 = byte2 << 4U | (byte1 >> 4U & (int16_t)15); + return (KRML_CLITERAL(int16_t_x2){.fst = r0, .snd = r1}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_12(Eurydice_slice bytes) { + int16_t_x2 v0_1 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)3U, uint8_t *)); + int16_t_x2 v2_3 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)3U, (size_t)6U, uint8_t *)); + int16_t_x2 v4_5 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)6U, (size_t)9U, uint8_t *)); + int16_t_x2 v6_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)9U, (size_t)12U, uint8_t *)); + int16_t_x2 v8_9 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)12U, (size_t)15U, uint8_t *)); + int16_t_x2 v10_11 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)15U, (size_t)18U, uint8_t *)); + int16_t_x2 v12_13 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)18U, (size_t)21U, uint8_t *)); + int16_t_x2 v14_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)21U, (size_t)24U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_1.fst, v0_1.snd, v2_3.fst, v2_3.snd, v4_5.fst, + v4_5.snd, v6_7.fst, v6_7.snd, v8_9.fst, v8_9.snd, + v10_11.fst, v10_11.snd, v12_13.fst, v12_13.snd, + v14_15.fst, v14_15.snd}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_12( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_12(a); +} + +static KRML_MUSTINLINE size_t +libcrux_ml_kem_vector_portable_sampling_rej_sample(Eurydice_slice a, + Eurydice_slice result) { + size_t sampled = (size_t)0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(a, uint8_t) / (size_t)3U; + i++) { + size_t i0 = i; + int16_t b1 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)0U, + uint8_t, uint8_t *); + int16_t b2 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)1U, + uint8_t, uint8_t *); + int16_t b3 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)2U, + uint8_t, uint8_t *); + int16_t d1 = (b2 & (int16_t)15) << 8U | b1; + int16_t d2 = b3 << 4U | b2 >> 4U; + if (d1 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d1; + sampled++; + } + } + if (d2 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d2; + sampled++; + } + } + } + return sampled; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline size_t libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_slice a, Eurydice_slice out) { + return libcrux_ml_kem_vector_portable_sampling_rej_sample(a, out); +} + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR ((size_t)10U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_RANK ((size_t)3U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE * LIBCRUX_ML_KEM_MLKEM768_RANK) + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR ((size_t)4U) + +#define LIBCRUX_ML_KEM_MLKEM768_C2_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_SIZE + LIBCRUX_ML_KEM_MLKEM768_C2_SIZE) + +#define LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE + (size_t)32U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA1 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA2 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_IMPLICIT_REJECTION_HASH_INPUT_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE) + +typedef libcrux_ml_kem_types_MlKemPrivateKey_d9 + libcrux_ml_kem_mlkem768_MlKem768PrivateKey; + +typedef libcrux_ml_kem_types_MlKemPublicKey_30 + libcrux_ml_kem_mlkem768_MlKem768PublicKey; + +#define LIBCRUX_ML_KEM_MLKEM768_RANKED_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE) + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.PolynomialRingElement +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct libcrux_ml_kem_polynomial_PolynomialRingElement_1d_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficients[16U]; +} libcrux_ml_kem_polynomial_PolynomialRingElement_1d; + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPrivateKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d secret_as_ntt[3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0; + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_d6_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::ind_cpa::decrypt::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt.call_mut_0b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + Call [`deserialize_to_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + Eurydice_slice secret_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice_subslice3( + secret_key, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *)); + secret_as_ntt[i0] = uu____0; + } +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::ind_cpa::deserialize_then_decompress_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u.call_mut_35 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)10); + decompressed = decompressed >> (uint32_t)((int32_t)10 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_10 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)20U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)20U, + i0 * (size_t)20U + (size_t)20U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_10_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea(serialized); +} + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector snd; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2; + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_layer_int_vec_step +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8(b, + zeta_r); + b = libcrux_ml_kem_vector_portable_sub_b8(a, &t); + a = libcrux_ml_kem_vector_portable_add_b8(a, &t); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer, size_t _initial_coefficient_bound) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = offset / (size_t)16U; + size_t step_vec = step / (size_t)16U; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U)); + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)3U)); + zeta_i[0U] = zeta_i[0U] + (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_barrett_reduce_b8( + myself->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self) { + libcrux_ml_kem_polynomial_poly_barrett_reduce_ea(self); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- VECTOR_U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_vector_u_0a( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = (size_t)0U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)7U, + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)4U, + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea(&zeta_i, re, (size_t)5U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea(&zeta_i, re, (size_t)6U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea(&zeta_i, re, (size_t)7U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Call [`deserialize_then_decompress_ring_element_u`] on each ring element + in the `ciphertext`. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c( + uint8_t *ciphertext, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + u_as_ntt[i] = + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + &lvalue, i); + } + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice((size_t)1088U, ciphertext, uint8_t), + uint8_t) / + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U); + i++) { + size_t i0 = i; + Eurydice_slice u_bytes = Eurydice_array_to_subslice3( + ciphertext, + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U), + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U) + + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U, + uint8_t *); + u_as_ntt[i0] = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + u_bytes); + libcrux_ml_kem_ntt_ntt_vector_u_0a(&u_as_ntt[i0]); + } + memcpy( + ret, u_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)4); + decompressed = decompressed >> (uint32_t)((int32_t)4 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.deserialize_then_decompress_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)8U; i++) { + size_t i0 = i; + Eurydice_slice bytes = Eurydice_slice_subslice3( + serialized, i0 * (size_t)8U, i0 * (size_t)8U + (size_t)8U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_4_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea(serialized); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** + Given two `KyberPolynomialRingElement`s in their NTT representations, + compute their product. Given two polynomials in the NTT domain `f^` and `ĵ`, + the `iᵗʰ` coefficient of the product `k̂` is determined by the calculation: + + ```plaintext + ĥ[2·i] + ĥ[2·i + 1]X = (f^[2·i] + f^[2·i + 1]X)·(ĝ[2·i] + ĝ[2·i + 1]X) mod (X² + - ζ^(2·BitRev₇(i) + 1)) + ``` + + This function almost implements Algorithm 10 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: Two arrays fˆ ∈ ℤ₂₅₆ and ĝ ∈ ℤ₂₅₆. + Output: An array ĥ ∈ ℤq. + + for(i ← 0; i < 128; i++) + (ĥ[2i], ĥ[2i+1]) ← BaseCaseMultiply(fˆ[2i], fˆ[2i+1], ĝ[2i], ĝ[2i+1], + ζ^(2·BitRev₇(i) + 1)) end for return ĥ + ``` + We say "almost" because the coefficients of the ring element output by + this function are in the Montgomery domain. + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d out = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_multiply_b8( + &myself->coefficients[i0], &rhs->coefficients[i0], + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)1U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)2U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)3U)); + out.coefficients[i0] = uu____0; + } + return out; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + return libcrux_ml_kem_polynomial_ntt_multiply_ea(self, rhs); +} + +/** + Given two polynomial ring elements `lhs` and `rhs`, compute the pointwise + sum of their constituent coefficients. +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)16U, myself->coefficients, + libcrux_ml_kem_vector_portable_vector_type_PortableVector), + libcrux_ml_kem_vector_portable_vector_type_PortableVector); + i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &rhs->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_add_to_ring_element_1b(self, rhs); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)3U)); + zeta_i[0U] = zeta_i[0U] - (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U)); + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.invert_ntt.inv_ntt_layer_int_vec_step_reduce with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector a_minus_b = + libcrux_ml_kem_vector_portable_sub_b8(b, &a); + a = libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_add_b8(a, &b)); + b = libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + a_minus_b, zeta_r); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = + offset / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + size_t step_vec = + step / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_montgomery +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT / (size_t)2U; + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)4U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)5U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)6U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)7U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + b.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector diff = + libcrux_ml_kem_vector_portable_sub_b8(myself->coefficients[i0], + &coefficient_normal_form); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(diff); + b.coefficients[i0] = red; + } + return b; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + return libcrux_ml_kem_polynomial_subtract_reduce_ea(self, b); +} + +/** + The following functions compute various expressions involving + vectors and matrices. The computation of these expressions has been + abstracted away into these functions in order to save on loop iterations. + Compute v − InverseNTT(sᵀ ◦ NTT(u)) +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_message +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_message_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *v, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *u_as_ntt) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&secret_as_ntt[i0], + &u_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_subtract_reduce_d6_ea(v, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.to_unsigned_field_modulus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_to_unsigned_representative_b8(a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_message_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, uint8_t ret[32U]) { + uint8_t serialized[32U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_compress_1_b8(coefficient); + uint8_t bytes[2U]; + libcrux_ml_kem_vector_portable_serialize_1_b8(coefficient_compressed, + bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, uint8_t *), + Eurydice_array_to_slice((size_t)2U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)32U * sizeof(uint8_t)); +} + +/** + This function implements Algorithm 14 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE decryption algorithm. + + Algorithm 14 is reproduced below: + + ```plaintext + Input: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + Input: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + Output: message m ∈ 𝔹^{32}. + + c₁ ← c[0 : 32dᵤk] + c₂ ← c[32dᵤk : 32(dᵤk + dᵥ)] + u ← Decompress_{dᵤ}(ByteDecode_{dᵤ}(c₁)) + v ← Decompress_{dᵥ}(ByteDecode_{dᵥ}(c₂)) + ŝ ← ByteDecode₁₂(dkₚₖₑ) + w ← v - NTT-¹(ŝᵀ ◦ NTT(u)) + m ← ByteEncode₁(Compress₁(w)) + return m + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_unpacked_42( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *secret_key, + uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c(ciphertext, u_as_ntt); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, + (size_t)960U, uint8_t, size_t, + uint8_t[])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message = + libcrux_ml_kem_matrix_compute_message_1b(&v, secret_key->secret_as_ntt, + u_as_ntt); + uint8_t ret0[32U]; + libcrux_ml_kem_serialize_compress_then_serialize_message_ea(message, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_42( + Eurydice_slice secret_key, uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 + secret_key_unpacked; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(&lvalue, i); + } + memcpy( + secret_key_unpacked.secret_as_ntt, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + secret_key, secret_key_unpacked.secret_as_ntt); + uint8_t ret1[32U]; + libcrux_ml_kem_ind_cpa_decrypt_unpacked_42(&secret_key_unpacked, ciphertext, + ret1); + memcpy(ret, ret1, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.G_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_slice input, uint8_t ret[64U]) { + libcrux_ml_kem_hash_functions_portable_G(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_9e( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_PRF_9e(input, ret); +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPublicKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d t_as_ntt[3U]; + uint8_t seed_for_A[32U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d A[3U][3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0; + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPublicKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_8b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + uu____0[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + uint8_t uu____1[32U] = {0U}; + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 lit; + memcpy( + lit.t_as_ntt, uu____0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + memcpy(lit.seed_for_A, uu____1, (size_t)32U * sizeof(uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression0[3U][3U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy(repeat_expression0[i0], repeat_expression, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + } + memcpy(lit.A, repeat_expression0, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U])); + return lit; +} + +/** + Only use with public values. + + This MUST NOT be used with secret inputs, like its caller + `deserialize_ring_elements_reduced`. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_reduced_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_cond_subtract_3329_b8(coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + See [deserialize_ring_elements_reduced_out]. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *deserialized_pk) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(public_key, uint8_t) / + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT; + i++) { + size_t i0 = i; + Eurydice_slice ring_element = Eurydice_slice_subslice3( + public_key, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT + + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + ring_element); + deserialized_pk[i0] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PortableHash +with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_hash_functions_portable_PortableHash_88_s { + libcrux_sha3_generic_keccak_KeccakState_17 shake128_state[3U]; +} libcrux_ml_kem_hash_functions_portable_PortableHash_88; + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + uint8_t (*input)[34U]) { + libcrux_ml_kem_hash_functions_portable_PortableHash_88 shake128_state; + libcrux_sha3_generic_keccak_KeccakState_17 repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_sha3_portable_incremental_shake128_init(); + } + memcpy(shake128_state.shake128_state, repeat_expression, + (size_t)3U * sizeof(libcrux_sha3_generic_keccak_KeccakState_17)); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_absorb_final( + &shake128_state.shake128_state[i0], + Eurydice_array_to_slice((size_t)34U, input[i0], uint8_t)); + } + return shake128_state; +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final_4a with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + uint8_t (*input)[34U]) { + return libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + input); +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks with +const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][504U]) { + uint8_t out[3U][504U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)504U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[504U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks_4a +with const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][504U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + self, ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 504 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + uint8_t (*randomness)[504U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)504U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][168U]) { + uint8_t out[3U][168U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)168U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[168U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block_4a with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][168U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0(self, + ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 168 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + uint8_t (*randomness)[168U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_ea(Eurydice_slice a) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_from_i16_array_b8( + Eurydice_slice_subslice3(a, i0 * (size_t)16U, + (i0 + (size_t)1U) * (size_t)16U, + int16_t *)); + result.coefficients[i0] = uu____0; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_d6_ea(Eurydice_slice a) { + return libcrux_ml_kem_polynomial_from_i16_array_ea(a); +} + +/** +This function found in impl {core::ops::function::FnMut<(@Array), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::sampling::sample_from_xof::closure[TraitClause@0, TraitClause@1, TraitClause@2, TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof.call_mut_e7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + void **_, int16_t tupled_args[272U]) { + int16_t s[272U]; + memcpy(s, tupled_args, (size_t)272U * sizeof(int16_t)); + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_subslice3(s, (size_t)0U, (size_t)256U, int16_t *)); +} + +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_sampling_sample_from_xof_2b( + uint8_t (*seeds)[34U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + size_t sampled_coefficients[3U] = {0U}; + int16_t out[3U][272U] = {{0U}}; + libcrux_ml_kem_hash_functions_portable_PortableHash_88 xof_state = + libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + seeds); + uint8_t randomness0[3U][504U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + &xof_state, randomness0); + bool done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + randomness0, sampled_coefficients, out); + while (true) { + if (done) { + break; + } else { + uint8_t randomness[3U][168U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + &xof_state, randomness); + done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + randomness, sampled_coefficients, out); + } + } + /* Passing arrays by value in Rust generates a copy in C */ + int16_t copy_of_out[3U][272U]; + memcpy(copy_of_out, out, (size_t)3U * sizeof(int16_t[272U])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + &lvalue, copy_of_out[i]); + } + memcpy( + ret, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.matrix.sample_matrix_A +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_sample_matrix_A_2b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*A_transpose)[3U], + uint8_t *seed, bool transpose) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + uint8_t seeds[3U][34U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)34U, seed, seeds[i], uint8_t, void *); + } + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t j = i; + seeds[j][32U] = (uint8_t)i1; + seeds[j][33U] = (uint8_t)j; + } + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sampled[3U]; + libcrux_ml_kem_sampling_sample_from_xof_2b(seeds, sampled); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, sampled, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sample = sampled[j]; + if (transpose) { + A_transpose[j][i1] = sample; + } else { + A_transpose[i1][j] = sample; + } + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f( + Eurydice_slice public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + *unpacked_public_key) { + Eurydice_slice uu____0 = Eurydice_slice_subslice_to( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + uu____0, unpacked_public_key->t_as_ntt); + Eurydice_slice seed = Eurydice_slice_subslice_from( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + unpacked_public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, false); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f( + Eurydice_slice public_key) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f(public_key, + &unpacked_public_key); + return unpacked_public_key; +} + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector[3size_t], +libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct tuple_ed_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d fst[3U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d snd; +} tuple_ed; + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_f1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + uint8_t out[3U][128U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, out[i0], uint8_t), + Eurydice_array_to_slice((size_t)33U, input[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[128U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + libcrux_ml_kem_hash_functions_portable_PRFxN_41(input, ret); +} + +/** + Given a series of uniformly random bytes in `randomness`, for some number + `eta`, the `sample_from_binomial_distribution_{eta}` functions sample a ring + element from a binomial distribution centered at 0 that uses two sets of `eta` + coin flips. If, for example, `eta = ETA`, each ring coefficient is a value `v` + such such that `v ∈ {-ETA, -ETA + 1, ..., 0, ..., ETA + 1, ETA}` and: + + ```plaintext + - If v < 0, Pr[v] = Pr[-v] + - If v >= 0, Pr[v] = BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / 2 ^ (2 * ETA) + ``` + + The values `v < 0` are mapped to the appropriate `KyberFieldElement`. + + The expected value is: + + ```plaintext + E[X] = (-ETA)Pr[-ETA] + (-(ETA - 1))Pr[-(ETA - 1)] + ... + (ETA - 1)Pr[ETA - 1] + + (ETA)Pr[ETA] = 0 since Pr[-v] = Pr[v] when v < 0. + ``` + + And the variance is: + + ```plaintext + Var(X) = E[(X - E[X])^2] + = E[X^2] + = sum_(v=-ETA to ETA)v^2 * (BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / + 2^(2 * ETA)) = ETA / 2 + ``` + + This function implements Algorithm 7 of the NIST FIPS 203 + standard, which is reproduced below: + + ```plaintext + Input: byte array B ∈ 𝔹^{64η}. + Output: array f ∈ ℤ₂₅₆. + + b ← BytesToBits(B) + for (i ← 0; i < 256; i++) + x ← ∑(j=0 to η - 1) b[2iη + j] + y ← ∑(j=0 to η - 1) b[2iη + η + j] + f[i] ← x−y mod q + end for + return f + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution_2 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + Eurydice_slice randomness) { + int16_t sampled_i16s[256U] = {0U}; + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len(randomness, uint8_t) / (size_t)4U; i0++) { + size_t chunk_number = i0; + Eurydice_slice byte_chunk = Eurydice_slice_subslice3( + randomness, chunk_number * (size_t)4U, + chunk_number * (size_t)4U + (size_t)4U, uint8_t *); + uint32_t random_bits_as_u32 = + (((uint32_t)Eurydice_slice_index(byte_chunk, (size_t)0U, uint8_t, + uint8_t *) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)1U, uint8_t, + uint8_t *) + << 8U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)2U, uint8_t, + uint8_t *) + << 16U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)3U, uint8_t, + uint8_t *) + << 24U; + uint32_t even_bits = random_bits_as_u32 & 1431655765U; + uint32_t odd_bits = random_bits_as_u32 >> 1U & 1431655765U; + uint32_t coin_toss_outcomes = even_bits + odd_bits; + for (uint32_t i = 0U; i < 32U / 4U; i++) { + uint32_t outcome_set = i; + uint32_t outcome_set0 = outcome_set * 4U; + int16_t outcome_1 = + (int16_t)(coin_toss_outcomes >> (uint32_t)outcome_set0 & 3U); + int16_t outcome_2 = + (int16_t)(coin_toss_outcomes >> (uint32_t)(outcome_set0 + 2U) & 3U); + size_t offset = (size_t)(outcome_set0 >> 2U); + sampled_i16s[(size_t)8U * chunk_number + offset] = outcome_1 - outcome_2; + } + } + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_slice((size_t)256U, sampled_i16s, int16_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- ETA= 2 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_slice randomness) { + return libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + randomness); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_7_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t step = LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT / (size_t)2U; + for (size_t i = (size_t)0U; i < step; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + re->coefficients[j + step], (int16_t)-1600); + re->coefficients[j + step] = + libcrux_ml_kem_vector_portable_sub_b8(re->coefficients[j], &t); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____1 = + libcrux_ml_kem_vector_portable_add_b8(re->coefficients[j], &t); + re->coefficients[j] = uu____1; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_binomially_sampled_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + libcrux_ml_kem_ntt_ntt_at_layer_7_ea(re); + size_t zeta_i = (size_t)1U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)11207U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)11207U + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + &zeta_i, re, (size_t)4U, (size_t)11207U + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + &zeta_i, re, (size_t)11207U + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + &zeta_i, re, (size_t)11207U + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + &zeta_i, re, (size_t)11207U + (size_t)5U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Sample a vector of ring elements from a centered binomial distribution and + convert them into their NTT representations. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_vector_cbd_then_ntt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA= 2 +- ETA_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re_as_ntt, + uint8_t *prf_input, uint8_t domain_separator) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + re_as_ntt[i0] = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea(&re_as_ntt[i0]); + } + return domain_separator; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure#1[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_dd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + Sample a vector of ring elements from a centered binomial distribution. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_ring_element_cbd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA2_RANDOMNESS_SIZE= 128 +- ETA2= 2 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + uint8_t *prf_input, uint8_t domain_separator, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + error_1[i0] = uu____0; + } + return domain_separator; +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_a6( + Eurydice_slice input, uint8_t ret[128U]) { + uint8_t digest[128U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)128U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_slice input, uint8_t ret[128U]) { + libcrux_ml_kem_hash_functions_portable_PRF_a6(input, ret); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::matrix::compute_vector_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u.call_mut_a8 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(void **_, + size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + myself->coefficients[j], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_error_reduce_ea(self, error); +} + +/** + Compute u := InvertNTT(Aᵀ ◦ r̂) + e₁ +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_vector_u_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*a_as_ntt)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + result[i] = + libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(&lvalue, i); + } + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, a_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i0++) { + size_t i1 = i0; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = a_as_ntt[i1]; + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *a_element = &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(a_element, &r_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result[i1], + &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result[i1]); + libcrux_ml_kem_polynomial_add_error_reduce_d6_ea(&result[i1], &error_1[i1]); + } + memcpy( + ret, result, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)10, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_ef(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_10 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_10_ff( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t serialized[320U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0])); + uint8_t bytes[20U]; + libcrux_ml_kem_vector_portable_serialize_10_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)20U * i0, + (size_t)20U * i0 + (size_t)20U, uint8_t *), + Eurydice_array_to_slice((size_t)20U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)320U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t uu____0[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_10_ff(re, uu____0); + memcpy(ret, uu____0, (size_t)320U * sizeof(uint8_t)); +} + +/** + Call [`compress_then_serialize_ring_element_u`] on each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.compress_then_serialize_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- OUT_LEN= 960 +- COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d input[3U], + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, input, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = input[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * ((size_t)960U / (size_t)3U), + (i0 + (size_t)1U) * ((size_t)960U / (size_t)3U), uint8_t *); + uint8_t ret[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe(&re, + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)320U, ret, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_ed libcrux_ml_kem_ind_cpa_encrypt_c1_85( + Eurydice_slice randomness, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix)[3U], + Eurydice_slice ciphertext) { + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(randomness, prf_input); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + r_as_ntt[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(&lvalue, i); + } + uint8_t domain_separator0 = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(r_as_ntt, prf_input, + 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_1[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_1[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(&lvalue, i); + } + uint8_t domain_separator = libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + prf_input, domain_separator0, error_1); + prf_input[32U] = domain_separator; + uint8_t prf_output[128U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_array_to_slice((size_t)33U, prf_input, uint8_t), prf_output); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_output, uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u[3U]; + libcrux_ml_kem_matrix_compute_vector_u_1b(matrix, r_as_ntt, error_1, u); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + memcpy( + uu____0, u, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43(uu____0, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + libcrux_ml_kem_polynomial_PolynomialRingElement_1d copy_of_r_as_ntt[3U]; + memcpy( + copy_of_r_as_ntt, r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + tuple_ed lit; + memcpy( + lit.fst, copy_of_r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + lit.snd = error_2; + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea( + uint8_t *serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_deserialize_1_b8( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, + uint8_t *)); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_1_b8(coefficient_compressed); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + result.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum1 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &message->coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum2 = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, &sum1); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum2); + result.coefficients[i0] = red; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + return libcrux_ml_kem_polynomial_add_message_error_reduce_ea(self, message, + result); +} + +/** + Compute InverseNTT(tᵀ ◦ r̂) + e₂ + message +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_ring_element_v +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_ring_element_v_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&t_as_ntt[i0], + &r_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + error_2, message, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)4, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_d1(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_4_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, + Eurydice_slice serialized) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0])); + uint8_t bytes[8U]; + libcrux_ml_kem_vector_portable_serialize_4_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_slice_subslice3(serialized, (size_t)8U * i0, + (size_t)8U * i0 + (size_t)8U, uint8_t *), + Eurydice_array_to_slice((size_t)8U, bytes, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +- OUT_LEN= 128 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, Eurydice_slice out) { + libcrux_ml_kem_serialize_compress_then_serialize_4_ea(re, out); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- V_COMPRESSION_FACTOR= 4 +- C2_LEN= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + uint8_t *message, Eurydice_slice ciphertext) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message_as_ring_element = + libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea(message); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_matrix_compute_ring_element_v_1b( + t_as_ntt, r_as_ntt, error_2, &message_as_ring_element); + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + v, ciphertext); +} + +/** + This function implements Algorithm 13 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE encryption algorithm. + + Algorithm 13 is reproduced below: + + ```plaintext + Input: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Input: message m ∈ 𝔹^{32}. + Input: encryption randomness r ∈ 𝔹^{32}. + Output: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + + N ← 0 + t̂ ← ByteDecode₁₂(ekₚₖₑ[0:384k]) + ρ ← ekₚₖₑ[384k: 384k + 32] + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + r[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(r,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e₁[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + N ← N + 1 + end for + e₂ ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + r̂ ← NTT(r) + u ← NTT-¹(Âᵀ ◦ r̂) + e₁ + μ ← Decompress₁(ByteDecode₁(m))) + v ← NTT-¹(t̂ᵀ ◦ rˆ) + e₂ + μ + c₁ ← ByteEncode_{dᵤ}(Compress_{dᵤ}(u)) + c₂ ← ByteEncode_{dᵥ}(Compress_{dᵥ}(v)) + return c ← (c₁ ‖ c₂) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + uint8_t *message, Eurydice_slice randomness, uint8_t ret[1088U]) { + uint8_t ciphertext[1088U] = {0U}; + tuple_ed uu____0 = libcrux_ml_kem_ind_cpa_encrypt_c1_85( + randomness, public_key->A, + Eurydice_array_to_subslice3(ciphertext, (size_t)0U, (size_t)960U, + uint8_t *)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + memcpy( + r_as_ntt, uu____0.fst, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____1 = + public_key->t_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____2 = r_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____3 = &error_2; + uint8_t *uu____4 = message; + libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + uu____1, uu____2, uu____3, uu____4, + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, (size_t)960U, + uint8_t, size_t, uint8_t[])); + memcpy(ret, ciphertext, (size_t)1088U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_slice public_key, uint8_t *message, Eurydice_slice randomness, + uint8_t ret[1088U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f(public_key); + uint8_t ret0[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a(&unpacked_public_key, message, + randomness, ret0); + memcpy(ret, ret0, (size_t)1088U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.kdf_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_slice shared_secret, uint8_t *_, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + shared_secret, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** + This code verifies on some machines, runs out of memory on others +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.decapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_decapsulate_62( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + Eurydice_slice_uint8_t_x4 uu____0 = + libcrux_ml_kem_types_unpack_private_key_b4( + Eurydice_array_to_slice((size_t)2400U, private_key->value, uint8_t)); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice ind_cpa_public_key = uu____0.snd; + Eurydice_slice ind_cpa_public_key_hash = uu____0.thd; + Eurydice_slice implicit_rejection_value = uu____0.f3; + uint8_t decrypted[32U]; + libcrux_ml_kem_ind_cpa_decrypt_42(ind_cpa_secret_key, ciphertext->value, + decrypted); + uint8_t to_hash0[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, decrypted, uint8_t), to_hash0); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)64U, to_hash0, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]), + ind_cpa_public_key_hash, uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash0, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret0 = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t to_hash[1120U]; + libcrux_ml_kem_utils_into_padded_array_15(implicit_rejection_value, to_hash); + Eurydice_slice uu____2 = Eurydice_array_to_subslice_from( + (size_t)1120U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]); + Eurydice_slice_copy(uu____2, libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + uint8_t); + uint8_t implicit_rejection_shared_secret0[32U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_array_to_slice((size_t)1120U, to_hash, uint8_t), + implicit_rejection_shared_secret0); + uint8_t expected_ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a(ind_cpa_public_key, decrypted, + pseudorandomness, expected_ciphertext); + uint8_t implicit_rejection_shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret0, + uint8_t), + libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + implicit_rejection_shared_secret); + uint8_t shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + shared_secret0, libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + shared_secret); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + Eurydice_array_to_slice((size_t)1088U, expected_ciphertext, uint8_t), + Eurydice_array_to_slice((size_t)32U, shared_secret, uint8_t), + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret, + uint8_t), + ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** + Portable decapsulate +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.decapsulate with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static inline void +libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_decapsulate_62(private_key, ciphertext, ret); +} + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + private_key, ciphertext, ret); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.entropy_preprocess_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_slice randomness, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + randomness, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.H_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_H(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.encapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_c2 libcrux_ml_kem_ind_cca_encapsulate_ca( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + uint8_t randomness0[32U]; + libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_array_to_slice((size_t)32U, randomness, uint8_t), randomness0); + uint8_t to_hash[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, randomness0, uint8_t), to_hash); + Eurydice_slice uu____0 = Eurydice_array_to_subslice_from( + (size_t)64U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + size_t, uint8_t[]); + uint8_t ret0[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + ret0); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)32U, ret0, uint8_t), uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + randomness0, pseudorandomness, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_ciphertext[1088U]; + memcpy(copy_of_ciphertext, ciphertext, (size_t)1088U * sizeof(uint8_t)); + tuple_c2 lit; + lit.fst = libcrux_ml_kem_types_from_e0_80(copy_of_ciphertext); + uint8_t ret[32U]; + libcrux_ml_kem_variant_kdf_39_d6(shared_secret, ciphertext, ret); + memcpy(lit.snd, ret, (size_t)32U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.encapsulate with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline tuple_c2 +libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_encapsulate_ca(public_key, randomness); +} + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + public_key, randomness); +} + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPrivateKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_70 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(void) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 lit; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy( + lit.secret_as_ntt, repeat_expression, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.cpa_keygen_seed_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_cpa_keygen_seed_39_9c( + Eurydice_slice key_generation_seed, uint8_t ret[64U]) { + uint8_t seed[33U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + seed, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *), + key_generation_seed, uint8_t); + seed[LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE] = + (uint8_t)(size_t)3U; + uint8_t ret0[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)33U, seed, uint8_t), ret0); + memcpy(ret, ret0, (size_t)64U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@3]> for +libcrux_ml_kem::ind_cpa::generate_keypair_unpacked::closure[TraitClause@0, TraitClause@1, +TraitClause@2, TraitClause@3, TraitClause@4, TraitClause@5]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.generate_keypair_unpacked.call_mut_73 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.to_standard_domain +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_polynomial_to_standard_domain_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + vector, + LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_polynomial_to_standard_domain_ea( + myself->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_standard_error_reduce_ea(self, error); +} + +/** + Compute  ◦ ŝ + ê +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_As_plus_e +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_As_plus_e_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix_A)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *s_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_as_ntt) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, matrix_A, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = matrix_A[i0]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + t_as_ntt[i0] = uu____0; + for (size_t i1 = (size_t)0U; + i1 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i1++) { + size_t j = i1; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *matrix_element = + &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(matrix_element, + &s_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&t_as_ntt[i0], + &product); + } + libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + &t_as_ntt[i0], &error_as_ntt[i0]); + } +} + +/** + This function implements most of Algorithm 12 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE key generation + algorithm. + + We say "most of" since Algorithm 12 samples the required randomness within + the function itself, whereas this implementation expects it to be provided + through the `key_generation_seed` parameter. + + Algorithm 12 is reproduced below: + + ```plaintext + Output: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + + d ←$ B + (ρ,σ) ← G(d) + N ← 0 + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + s[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(σ,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(σ,N)) + N ← N + 1 + end for + ŝ ← NTT(s) + ê ← NTT(e) + t̂ ← Â◦ŝ + ê + ekₚₖₑ ← ByteEncode₁₂(t̂) ‖ ρ + dkₚₖₑ ← ByteEncode₁₂(ŝ) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + Eurydice_slice key_generation_seed, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key) { + uint8_t hashed[64U]; + libcrux_ml_kem_variant_cpa_keygen_seed_39_9c(key_generation_seed, hashed); + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), (size_t)32U, + uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice seed_for_A = uu____0.fst; + Eurydice_slice seed_for_secret_and_error = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed_for_A, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, true); + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(seed_for_secret_and_error, + prf_input); + uint8_t domain_separator = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + private_key->secret_as_ntt, prf_input, 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_as_ntt[i] = + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c(&lvalue, + i); + } + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(error_as_ntt, prf_input, + domain_separator); + libcrux_ml_kem_matrix_compute_As_plus_e_1b( + public_key->t_as_ntt, public_key->A, private_key->secret_as_ntt, + error_as_ntt); + uint8_t uu____2[32U]; + Result_fb dst; + Eurydice_slice_to_array2(&dst, seed_for_A, Eurydice_slice, uint8_t[32U], + TryFromSliceError); + unwrap_26_b3(dst, uu____2); + memcpy(public_key->seed_for_A, uu____2, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.serialize_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[384U]) { + uint8_t serialized[384U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0]); + uint8_t bytes[24U]; + libcrux_ml_kem_vector_portable_serialize_12_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)24U * i0, + (size_t)24U * i0 + (size_t)24U, uint8_t *), + Eurydice_array_to_slice((size_t)24U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)384U * sizeof(uint8_t)); +} + +/** + Call [`serialize_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_vector_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *key, + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = key[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + uint8_t ret[384U]; + libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea(&re, ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)384U, ret, uint8_t), uint8_t); + } +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t *serialized) { + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + t_as_ntt, + Eurydice_array_to_subslice3( + serialized, (size_t)0U, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t *)); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)1184U, serialized, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + seed_for_a, uint8_t); +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t ret[1184U]) { + uint8_t public_key_serialized[1184U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89(t_as_ntt, seed_for_a, + public_key_serialized); + memcpy(ret, public_key_serialized, (size_t)1184U * sizeof(uint8_t)); +} + +/** + Serialize the secret key from the unpacked key pair generation. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_unpacked_secret_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key) { + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + public_key->t_as_ntt, + Eurydice_array_to_slice((size_t)32U, public_key->seed_for_A, uint8_t), + public_key_serialized); + uint8_t secret_key_serialized[1152U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + private_key->secret_as_ntt, + Eurydice_array_to_slice((size_t)1152U, secret_key_serialized, uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[1152U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key_serialized[1184U]; + memcpy(copy_of_public_key_serialized, public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_utils_extraction_helper_Keypair768 lit; + memcpy(lit.fst, copy_of_secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + memcpy(lit.snd, copy_of_public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_generate_keypair_ea(Eurydice_slice key_generation_seed) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 private_key = + libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(); + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 public_key = + libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + key_generation_seed, &private_key, &public_key); + return libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c(&public_key, + &private_key); +} + +/** + Serialize the secret key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key_mut +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t *serialized) { + size_t pointer = (size_t)0U; + uint8_t *uu____0 = serialized; + size_t uu____1 = pointer; + size_t uu____2 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, uu____1, uu____2 + Eurydice_slice_len(private_key, uint8_t), + uint8_t *), + private_key, uint8_t); + pointer = pointer + Eurydice_slice_len(private_key, uint8_t); + uint8_t *uu____3 = serialized; + size_t uu____4 = pointer; + size_t uu____5 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____3, uu____4, uu____5 + Eurydice_slice_len(public_key, uint8_t), + uint8_t *), + public_key, uint8_t); + pointer = pointer + Eurydice_slice_len(public_key, uint8_t); + Eurydice_slice uu____6 = Eurydice_array_to_subslice3( + serialized, pointer, pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, + uint8_t *); + uint8_t ret[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0(public_key, ret); + Eurydice_slice_copy( + uu____6, Eurydice_array_to_slice((size_t)32U, ret, uint8_t), uint8_t); + pointer = pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE; + uint8_t *uu____7 = serialized; + size_t uu____8 = pointer; + size_t uu____9 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____7, uu____8, + uu____9 + Eurydice_slice_len(implicit_rejection_value, uint8_t), + uint8_t *), + implicit_rejection_value, uint8_t); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t ret[2400U]) { + uint8_t out[2400U] = {0U}; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + private_key, public_key, implicit_rejection_value, out); + memcpy(ret, out, (size_t)2400U * sizeof(uint8_t)); +} + +/** + Packed API + + Generate a key pair. + + Depending on the `Vector` and `Hasher` used, this requires different hardware + features +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_generate_keypair_15(uint8_t *randomness) { + Eurydice_slice ind_cpa_keypair_randomness = Eurydice_array_to_subslice3( + randomness, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *); + Eurydice_slice implicit_rejection_value = Eurydice_array_to_subslice_from( + (size_t)64U, randomness, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t, + size_t, uint8_t[]); + libcrux_ml_kem_utils_extraction_helper_Keypair768 uu____0 = + libcrux_ml_kem_ind_cpa_generate_keypair_ea(ind_cpa_keypair_randomness); + uint8_t ind_cpa_private_key[1152U]; + memcpy(ind_cpa_private_key, uu____0.fst, (size_t)1152U * sizeof(uint8_t)); + uint8_t public_key[1184U]; + memcpy(public_key, uu____0.snd, (size_t)1184U * sizeof(uint8_t)); + uint8_t secret_key_serialized[2400U]; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_array_to_slice((size_t)1152U, ind_cpa_private_key, uint8_t), + Eurydice_array_to_slice((size_t)1184U, public_key, uint8_t), + implicit_rejection_value, secret_key_serialized); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[2400U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 private_key = + libcrux_ml_kem_types_from_77_28(copy_of_secret_key_serialized); + libcrux_ml_kem_types_MlKemPrivateKey_d9 uu____2 = private_key; + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key[1184U]; + memcpy(copy_of_public_key, public_key, (size_t)1184U * sizeof(uint8_t)); + return libcrux_ml_kem_types_from_17_74( + uu____2, libcrux_ml_kem_types_from_fd_d0(copy_of_public_key)); +} + +/** + Portable generate key pair. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.generate_keypair with const +generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_generate_keypair_15(randomness); +} + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + randomness); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::serialize::deserialize_ring_elements_reduced_out::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out.call_mut_0b with +types libcrux_ml_kem_vector_portable_vector_type_PortableVector with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + This function deserializes ring elements and reduces the result by the field + modulus. + + This function MUST NOT be used on secret inputs. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + deserialized_pk[i] = + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + &lvalue, i); + } + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + public_key, deserialized_pk); + memcpy( + ret, deserialized_pk, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** + Validate an ML-KEM public key. + + This implements the Modulus check in 7.2 2. + Note that the size check in 7.2 1 is covered by the `PUBLIC_KEY_SIZE` in the + `public_key` type. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.validate_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool libcrux_ml_kem_ind_cca_validate_public_key_89( + uint8_t *public_key) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_array_to_subslice_to( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + deserialized_pk); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____0 = deserialized_pk; + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + uu____0, + Eurydice_array_to_subslice_from( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + public_key_serialized); + return Eurydice_array_eq((size_t)1184U, public_key, public_key_serialized, + uint8_t); +} + +/** + Public key validation +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.validate_public_key with const +generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + uint8_t *public_key) { + return libcrux_ml_kem_ind_cca_validate_public_key_89(public_key); +} + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) { + return libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + public_key->value); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem768_portable_H_DEFINED +#endif /* libcrux_mlkem768_portable_H */ + + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 diff --git a/src/external/sntrup761.c b/src/external/sntrup761.c new file mode 100644 index 00000000..2f45622d --- /dev/null +++ b/src/external/sntrup761.c @@ -0,0 +1,1058 @@ +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func (void *ctx, size_t length, uint8_t *dst); + +void +sntrup761_keypair (uint8_t *pk, uint8_t *sk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_enc (uint8_t *c, uint8_t *k, const uint8_t *pk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_dec (uint8_t *k, const uint8_t *c, const uint8_t *sk); + +extern int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +#define MAX_LEN 761 + +/* from supercop-20201130/crypto_sort/int32/portable4/int32_minmax.inc */ +#define int32_MINMAX(a,b) \ +do { \ + int64_t ab = (int64_t)b ^ (int64_t)a; \ + int64_t c = (int64_t)b - (int64_t)a; \ + c ^= ab & (c ^ b); \ + c >>= 31; \ + c &= ab; \ + a ^= c; \ + b ^= c; \ +} while(0) + +/* from supercop-20201130/crypto_sort/int32/portable4/sort.c */ +static void +crypto_sort_int32 (void *array, long long n) +{ + long long top, p, q, r, i, j; + int32_t *x = array; + + if (n < 2) + return; + top = 1; + while (top < n - top) + top += top; + + for (p = top; p >= 1; p >>= 1) + { + i = 0; + while (i + 2 * p <= n) + { + for (j = i; j < i + p; ++j) + int32_MINMAX (x[j], x[j + p]); + i += 2 * p; + } + for (j = i; j < n - p; ++j) + int32_MINMAX (x[j], x[j + p]); + + i = 0; + j = 0; + for (q = top; q > p; q >>= 1) + { + if (j != i) + for (;;) + { + int32_t a; + if (j == n - q) + goto done; + a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + if (j == i + p) + { + i += 2 * p; + break; + } + } + while (i + p <= n - q) + { + for (j = i; j < i + p; ++j) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + } + i += 2 * p; + } + /* now i + p > n - q */ + j = i; + while (j < n - q) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + } + + done:; + } + } +} + +/* from supercop-20201130/crypto_sort/uint32/useint32/sort.c */ + +/* can save time by vectorizing xor loops */ +/* can save time by integrating xor loops with int32_sort */ + +static void +crypto_sort_uint32 (void *array, long long n) +{ + uint32_t *x = array; + long long j; + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; + crypto_sort_int32 (array, n); + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/uint32.c */ + +/* +CPU division instruction typically takes time depending on x. +This software is designed to take time independent of x. +Time still varies depending on m; user must ensure that m is constant. +Time also varies on CPUs where multiplication is variable-time. +There could be more CPU issues. +There could also be compiler issues. +*/ + +static void +uint32_divmod_uint14 (uint32_t * q, uint16_t * r, uint32_t x, uint16_t m) +{ + uint32_t v = 0x80000000; + uint32_t qpart; + uint32_t mask; + + v /= m; + + /* caller guarantees m > 0 */ + /* caller guarantees m < 16384 */ + /* vm <= 2^31 <= vm+m-1 */ + /* xvm <= 2^31 x <= xvm+x(m-1) */ + + *q = 0; + + qpart = (x * (uint64_t) v) >> 31; + /* 2^31 qpart <= xv <= 2^31 qpart + 2^31-1 */ + /* 2^31 qpart m <= xvm <= 2^31 qpart m + (2^31-1)m */ + /* 2^31 qpart m <= 2^31 x <= 2^31 qpart m + (2^31-1)m + x(m-1) */ + /* 0 <= 2^31 newx <= (2^31-1)m + x(m-1) */ + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= (1-1/2^31)(2^14-1) + (2^32-1)((2^14-1)-1)/2^31 */ + + x -= qpart * m; + *q += qpart; + /* x <= 49146 */ + + qpart = (x * (uint64_t) v) >> 31; + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= m + 49146(2^14-1)/2^31 */ + /* 0 <= newx <= m + 0.4 */ + /* 0 <= newx <= m */ + + x -= qpart * m; + *q += qpart; + /* x <= m */ + + x -= m; + *q += 1; + mask = -(x >> 31); + x += mask & (uint32_t) m; + *q += mask; + /* x < m */ + + *r = x; +} + + +static uint16_t +uint32_mod_uint14 (uint32_t x, uint16_t m) +{ + uint32_t q; + uint16_t r; + uint32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/int32.c */ + +static void +int32_divmod_uint14 (int32_t * q, uint16_t * r, int32_t x, uint16_t m) +{ + uint32_t uq, uq2; + uint16_t ur, ur2; + uint32_t mask; + + uint32_divmod_uint14 (&uq, &ur, 0x80000000 + (uint32_t) x, m); + uint32_divmod_uint14 (&uq2, &ur2, 0x80000000, m); + ur -= ur2; + uq -= uq2; + mask = -(uint32_t) (ur >> 15); + ur += mask & m; + uq += mask; + *r = ur; + *q = uq; +} + + +static uint16_t +int32_mod_uint14 (int32_t x, uint16_t m) +{ + int32_t q; + uint16_t r; + int32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/paramsmenu.h */ +#define p 761 +#define q 4591 +#define Rounded_bytes 1007 +#define Rq_bytes 1158 +#define w 286 + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.h */ + +/* Decode(R,s,M,len) */ +/* assumes 0 < M[i] < 16384 */ +/* produces 0 <= R[i] < M[i] */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.c */ + +static void +Decode (uint16_t * out, const unsigned char *S, const uint16_t * M, + long long len) +{ + if (len == 1) + { + if (M[0] == 1) + *out = 0; + else if (M[0] <= 256) + *out = uint32_mod_uint14 (S[0], M[0]); + else + *out = uint32_mod_uint14 (S[0] + (((uint16_t) S[1]) << 8), M[0]); + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + uint16_t bottomr[MAX_LEN / 2]; + uint32_t bottomt[MAX_LEN / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m = M[i] * (uint32_t) M[i + 1]; + if (m > 256 * 16383) + { + bottomt[i / 2] = 256 * 256; + bottomr[i / 2] = S[0] + 256 * S[1]; + S += 2; + M2[i / 2] = (((m + 255) >> 8) + 255) >> 8; + } + else if (m >= 16384) + { + bottomt[i / 2] = 256; + bottomr[i / 2] = S[0]; + S += 1; + M2[i / 2] = (m + 255) >> 8; + } + else + { + bottomt[i / 2] = 1; + bottomr[i / 2] = 0; + M2[i / 2] = m; + } + } + if (i < len) + M2[i / 2] = M[i]; + Decode (R2, S, M2, (len + 1) / 2); + for (i = 0; i < len - 1; i += 2) + { + uint32_t r = bottomr[i / 2]; + uint32_t r1; + uint16_t r0; + r += bottomt[i / 2] * R2[i / 2]; + uint32_divmod_uint14 (&r1, &r0, r, M[i]); + r1 = uint32_mod_uint14 (r1, M[i + 1]); /* only needed for invalid inputs */ + *out++ = r0; + *out++ = r1; + } + if (i < len) + *out++ = R2[i / 2]; + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.h */ + +/* Encode(s,R,M,len) */ +/* assumes 0 <= R[i] < M[i] < 16384 */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.c */ + +/* 0 <= R[i] < M[i] < 16384 */ +static void +Encode (unsigned char *out, const uint16_t * R, const uint16_t * M, + long long len) +{ + if (len == 1) + { + uint16_t r = R[0]; + uint16_t m = M[0]; + while (m > 1) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m0 = M[i]; + uint32_t r = R[i] + R[i + 1] * m0; + uint32_t m = M[i + 1] * m0; + while (m >= 16384) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + R2[i / 2] = r; + M2[i / 2] = m; + } + if (i < len) + { + R2[i / 2] = R[i]; + M2[i / 2] = M[i]; + } + Encode (out, R2, M2, (len + 1) / 2); + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/kem.c */ + +/* ----- masks */ + +/* return -1 if x!=0; else return 0 */ +static int +int16_t_nonzero_mask (int16_t x) +{ + uint16_t u = x; /* 0, else 1...65535 */ + uint32_t v = u; /* 0, else 1...65535 */ + v = -v; /* 0, else 2^32-65535...2^32-1 */ + v >>= 31; /* 0, else 1 */ + return -v; /* 0, else -1 */ +} + +/* return -1 if x<0; otherwise return 0 */ +static int +int16_t_negative_mask (int16_t x) +{ + uint16_t u = x; + u >>= 15; + return -(int) u; + /* alternative with gcc -fwrapv: */ + /* x>>15 compiles to CPU's arithmetic right shift */ +} + +/* ----- arithmetic mod 3 */ + +typedef int8_t small; + +/* F3 is always represented as -1,0,1 */ +/* so ZZ_fromF3 is a no-op */ + +/* x must not be close to top int16_t */ +static small +F3_freeze (int16_t x) +{ + return int32_mod_uint14 (x + 1, 3) - 1; +} + +/* ----- arithmetic mod q */ + +#define q12 ((q-1)/2) +typedef int16_t Fq; +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +static Fq +Fq_freeze (int32_t x) +{ + return int32_mod_uint14 (x + q12, q) - q12; +} + +static Fq +Fq_recip (Fq a1) +{ + int i = 1; + Fq ai = a1; + + while (i < q - 2) + { + ai = Fq_freeze (a1 * (int32_t) ai); + i += 1; + } + return ai; +} + +/* ----- small polynomials */ + +/* 0 if Weightw_is(r), else -1 */ +static int +Weightw_mask (small * r) +{ + int weight = 0; + int i; + + for (i = 0; i < p; ++i) + weight += r[i] & 1; + return int16_t_nonzero_mask (weight - w); +} + +/* R3_fromR(R_fromRq(r)) */ +static void +R3_fromRq (small * out, const Fq * r) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = F3_freeze (r[i]); +} + +/* h = f*g in the ring R3 */ +static void +R3_mult (small * h, const small * f, const small * g) +{ + small fg[p + p - 1]; + small result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = F3_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = F3_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* returns 0 if recip succeeded; else -1 */ +static int +R3_recip (small * out, const small * in) +{ + small f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int sign, swap, t; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = 1; + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + sign = -g[0] * f[0]; + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + for (i = 0; i < p + 1; ++i) + g[i] = F3_freeze (g[i] + sign * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = F3_freeze (r[i] + sign * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + sign = f[0]; + for (i = 0; i < p; ++i) + out[i] = sign * v[p - 1 - i]; + + return int16_t_nonzero_mask (delta); +} + +/* ----- polynomials mod q */ + +/* h = f*g in the ring Rq */ +static void +Rq_mult_small (Fq * h, const Fq * f, const small * g) +{ + Fq fg[p + p - 1]; + Fq result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = Fq_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = Fq_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* h = 3f in Rq */ +static void +Rq_mult3 (Fq * h, const Fq * f) +{ + int i; + + for (i = 0; i < p; ++i) + h[i] = Fq_freeze (3 * f[i]); +} + +/* out = 1/(3*in) in Rq */ +/* returns 0 if recip succeeded; else -1 */ +static int +Rq_recip3 (Fq * out, const small * in) +{ + Fq f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int swap, t; + int32_t f0, g0; + Fq scale; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = Fq_recip (3); + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + f0 = f[0]; + g0 = g[0]; + for (i = 0; i < p + 1; ++i) + g[i] = Fq_freeze (f0 * g[i] - g0 * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = Fq_freeze (f0 * r[i] - g0 * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + scale = Fq_recip (f[0]); + for (i = 0; i < p; ++i) + out[i] = Fq_freeze (scale * (int32_t) v[p - 1 - i]); + + return int16_t_nonzero_mask (delta); +} + +/* ----- rounded polynomials mod q */ + +static void +Round (Fq * out, const Fq * a) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = a[i] - F3_freeze (a[i]); +} + +/* ----- sorting to generate short polynomial */ + +static void +Short_fromlist (small * out, const uint32_t * in) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < w; ++i) + L[i] = in[i] & (uint32_t) - 2; + for (i = w; i < p; ++i) + L[i] = (in[i] & (uint32_t) - 3) | 1; + crypto_sort_uint32 (L, p); + for (i = 0; i < p; ++i) + out[i] = (L[i] & 3) - 1; +} + +/* ----- underlying hash function */ + +#define Hash_bytes 32 + +/* e.g., b = 0 means out = Hash0(in) */ +static void +Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen) +{ +#define MAX_X_LEN 1158 + unsigned char x[MAX_X_LEN + 1]; + unsigned char h[64]; + int i; + + x[0] = b; + for (i = 0; i < inlen; ++i) + x[i + 1] = in[i]; + sha512 (x, inlen + 1, h); + for (i = 0; i < 32; ++i) + out[i] = h[i]; +} + +/* ----- higher-level randomness */ + +static uint32_t +urandom32 (void *random_ctx, sntrup761_random_func * random) +{ + unsigned char c[4]; + uint32_t out[4]; + + random (random_ctx, 4, c); + out[0] = (uint32_t) c[0]; + out[1] = ((uint32_t) c[1]) << 8; + out[2] = ((uint32_t) c[2]) << 16; + out[3] = ((uint32_t) c[3]) << 24; + return out[0] + out[1] + out[2] + out[3]; +} + +static void +Short_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < p; ++i) + L[i] = urandom32 (random_ctx, random); + Short_fromlist (out, L); +} + +static void +Small_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + int i; + + for (i = 0; i < p; ++i) + out[i] = (((urandom32 (random_ctx, random) & 0x3fffffff) * 3) >> 30) - 1; +} + +/* ----- Streamlined NTRU Prime Core */ + +/* h,(f,ginv) = KeyGen() */ +static void +KeyGen (Fq * h, small * f, small * ginv, void *random_ctx, + sntrup761_random_func * random) +{ + small g[p]; + Fq finv[p]; + + for (;;) + { + Small_random (g, random_ctx, random); + if (R3_recip (ginv, g) == 0) + break; + } + Short_random (f, random_ctx, random); + Rq_recip3 (finv, f); /* always works */ + Rq_mult_small (h, finv, g); +} + +/* c = Encrypt(r,h) */ +static void +Encrypt (Fq * c, const small * r, const Fq * h) +{ + Fq hr[p]; + + Rq_mult_small (hr, h, r); + Round (c, hr); +} + +/* r = Decrypt(c,(f,ginv)) */ +static void +Decrypt (small * r, const Fq * c, const small * f, const small * ginv) +{ + Fq cf[p]; + Fq cf3[p]; + small e[p]; + small ev[p]; + int mask; + int i; + + Rq_mult_small (cf, c, f); + Rq_mult3 (cf3, cf); + R3_fromRq (e, cf3); + R3_mult (ev, e, ginv); + + mask = Weightw_mask (ev); /* 0 if weight w, else -1 */ + for (i = 0; i < w; ++i) + r[i] = ((ev[i] ^ 1) & ~mask) ^ 1; + for (i = w; i < p; ++i) + r[i] = ev[i] & ~mask; +} + +/* ----- encoding small polynomials (including short polynomials) */ + +#define Small_bytes ((p+3)/4) + +/* these are the only functions that rely on p mod 4 = 1 */ + +static void +Small_encode (unsigned char *s, const small * f) +{ + small x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *f++ + 1; + x += (*f++ + 1) << 2; + x += (*f++ + 1) << 4; + x += (*f++ + 1) << 6; + *s++ = x; + } + x = *f++ + 1; + *s++ = x; +} + +static void +Small_decode (small * f, const unsigned char *s) +{ + unsigned char x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *s++; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + } + x = *s++; + *f++ = ((small) (x & 3)) - 1; +} + +/* ----- encoding general polynomials */ + +static void +Rq_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = r[i] + q12; + for (i = 0; i < p; ++i) + M[i] = q; + Encode (s, R, M, p); +} + +static void +Rq_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = q; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = ((Fq) R[i]) - q12; +} + +/* ----- encoding rounded polynomials */ + +static void +Rounded_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = ((r[i] + q12) * 10923) >> 15; + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Encode (s, R, M, p); +} + +static void +Rounded_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = R[i] * 3 - q12; +} + +/* ----- Streamlined NTRU Prime Core plus encoding */ + +typedef small Inputs[p]; /* passed by reference */ +#define Inputs_random Short_random +#define Inputs_encode Small_encode +#define Inputs_bytes Small_bytes + +#define Ciphertexts_bytes Rounded_bytes +#define SecretKeys_bytes (2*Small_bytes) +#define PublicKeys_bytes Rq_bytes + +/* pk,sk = ZKeyGen() */ +static void +ZKeyGen (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + Fq h[p]; + small f[p], v[p]; + + KeyGen (h, f, v, random_ctx, random); + Rq_encode (pk, h); + Small_encode (sk, f); + sk += Small_bytes; + Small_encode (sk, v); +} + +/* C = ZEncrypt(r,pk) */ +static void +ZEncrypt (unsigned char *C, const Inputs r, const unsigned char *pk) +{ + Fq h[p]; + Fq c[p]; + Rq_decode (h, pk); + Encrypt (c, r, h); + Rounded_encode (C, c); +} + +/* r = ZDecrypt(C,sk) */ +static void +ZDecrypt (Inputs r, const unsigned char *C, const unsigned char *sk) +{ + small f[p], v[p]; + Fq c[p]; + + Small_decode (f, sk); + sk += Small_bytes; + Small_decode (v, sk); + Rounded_decode (c, C); + Decrypt (r, c, f, v); +} + +/* ----- confirmation hash */ + +#define Confirm_bytes 32 + +/* h = HashConfirm(r,pk,cache); cache is Hash4(pk) */ +static void +HashConfirm (unsigned char *h, const unsigned char *r, + /* const unsigned char *pk, */ const unsigned char *cache) +{ + unsigned char x[Hash_bytes * 2]; + int i; + + Hash_prefix (x, 3, r, Inputs_bytes); + for (i = 0; i < Hash_bytes; ++i) + x[Hash_bytes + i] = cache[i]; + Hash_prefix (h, 2, x, sizeof x); +} + +/* ----- session-key hash */ + +/* k = HashSession(b,y,z) */ +static void +HashSession (unsigned char *k, int b, const unsigned char *y, + const unsigned char *z) +{ + unsigned char x[Hash_bytes + Ciphertexts_bytes + Confirm_bytes]; + int i; + + Hash_prefix (x, 3, y, Inputs_bytes); + for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i) + x[Hash_bytes + i] = z[i]; + Hash_prefix (k, b, x, sizeof x); +} + +/* ----- Streamlined NTRU Prime */ + +/* pk,sk = KEM_KeyGen() */ +void +sntrup761_keypair (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + int i; + + ZKeyGen (pk, sk, random_ctx, random); + sk += SecretKeys_bytes; + for (i = 0; i < PublicKeys_bytes; ++i) + *sk++ = pk[i]; + random (random_ctx, Inputs_bytes, sk); + sk += Inputs_bytes; + Hash_prefix (sk, 4, pk, PublicKeys_bytes); +} + +/* c,r_enc = Hide(r,pk,cache); cache is Hash4(pk) */ +static void +Hide (unsigned char *c, unsigned char *r_enc, const Inputs r, + const unsigned char *pk, const unsigned char *cache) +{ + Inputs_encode (r_enc, r); + ZEncrypt (c, r, pk); + c += Ciphertexts_bytes; + HashConfirm (c, r_enc, cache); +} + +/* c,k = Encap(pk) */ +void +sntrup761_enc (unsigned char *c, unsigned char *k, const unsigned char *pk, + void *random_ctx, sntrup761_random_func * random) +{ + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cache[Hash_bytes]; + + Hash_prefix (cache, 4, pk, PublicKeys_bytes); + Inputs_random (r, random_ctx, random); + Hide (c, r_enc, r, pk, cache); + HashSession (k, 1, r_enc, c); +} + +/* 0 if matching ciphertext+confirm, else -1 */ +static int +Ciphertexts_diff_mask (const unsigned char *c, const unsigned char *c2) +{ + uint16_t differentbits = 0; + int len = Ciphertexts_bytes + Confirm_bytes; + + while (len-- > 0) + differentbits |= (*c++) ^ (*c2++); + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +/* k = Decap(c,sk) */ +void +sntrup761_dec (unsigned char *k, const unsigned char *c, const unsigned char *sk) +{ + const unsigned char *pk = sk + SecretKeys_bytes; + const unsigned char *rho = pk + PublicKeys_bytes; + const unsigned char *cache = rho + Inputs_bytes; + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cnew[Ciphertexts_bytes + Confirm_bytes]; + int mask; + int i; + + ZDecrypt (r, c, sk); + Hide (cnew, r_enc, r, pk, cache); + mask = Ciphertexts_diff_mask (c, cnew); + for (i = 0; i < Inputs_bytes; ++i) + r_enc[i] ^= mask & (r_enc[i] ^ rho[i]); + HashSession (k, 1 + mask, r_enc, c); +} diff --git a/src/gcrypt_missing.c b/src/gcrypt_missing.c index 21a63a9b..5f84e6b6 100644 --- a/src/gcrypt_missing.c +++ b/src/gcrypt_missing.c @@ -47,7 +47,7 @@ int ssh_gcry_dec2bn(bignum *bn, const char *data) { char *ssh_gcry_bn2dec(bignum bn) { bignum bndup, num, ten; - char *ret; + char *ret = NULL; int count, count2; int size, rsize; char decnum; diff --git a/src/getpass.c b/src/getpass.c index 6be33c77..2edab7d5 100644 --- a/src/getpass.c +++ b/src/getpass.c @@ -46,7 +46,7 @@ */ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { - char *tmp; + char *tmp = NULL; char *ptr = NULL; int ok = 0; @@ -63,7 +63,7 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) fprintf(stdout, "%s", prompt); } fflush(stdout); - if (fgets(tmp, len, stdin) == NULL) { + if (fgets(tmp, (int)len, stdin) == NULL) { free(tmp); return 0; } @@ -78,7 +78,7 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) } if (verify) { - char *key_string; + char *key_string = NULL; key_string = calloc(1, len); if (key_string == NULL) { @@ -87,8 +87,8 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) fprintf(stdout, "\nVerifying, please re-enter. %s", prompt); fflush(stdout); - if (! fgets(key_string, len, stdin)) { - explicit_bzero(key_string, len); + if (!fgets(key_string, (int)len, stdin)) { + ssh_burn(key_string, len); SAFE_FREE(key_string); clearerr(stdin); continue; @@ -99,17 +99,17 @@ static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) fprintf(stdout, "\n"); if (strcmp(buf, key_string)) { printf("\n\07\07Mismatch - try again\n"); - explicit_bzero(key_string, len); + ssh_burn(key_string, len); SAFE_FREE(key_string); fflush(stdout); continue; } - explicit_bzero(key_string, len); + ssh_burn(key_string, len); SAFE_FREE(key_string); } ok = 1; } - explicit_bzero(tmp, len); + ssh_burn(tmp, len); free(tmp); return ok; @@ -152,7 +152,7 @@ int ssh_getpass(const char *prompt, SetConsoleMode(h, mode); if (!ok) { - explicit_bzero(buf, len); + ssh_burn(buf, len); return -1; } @@ -257,8 +257,8 @@ int ssh_getpass(const char *prompt, } /* disable nonblocking I/O */ - if (fd & O_NDELAY) { - ok = fcntl(0, F_SETFL, fd & ~O_NDELAY); + if (fd & O_NONBLOCK) { + ok = fcntl(0, F_SETFL, fd & ~O_NONBLOCK); if (ok < 0) { perror("fcntl"); return -1; @@ -273,7 +273,7 @@ int ssh_getpass(const char *prompt, } /* close fd */ - if (fd & O_NDELAY) { + if (fd & O_NONBLOCK) { ok = fcntl(0, F_SETFL, fd); if (ok < 0) { perror("fcntl"); @@ -282,7 +282,7 @@ int ssh_getpass(const char *prompt, } if (!ok) { - explicit_bzero(buf, len); + ssh_burn(buf, len); return -1; } diff --git a/src/gssapi.c b/src/gssapi.c index 5254d38d..cf42bbec 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -21,6 +21,7 @@ #include "config.h" +#include #include #include #ifdef HAVE_UNISTD_H @@ -29,46 +30,23 @@ #include +#include +#include +#include #include #include +#include #include -#include -#include -#include #include -#include - -/** current state of an GSSAPI authentication */ -enum ssh_gssapi_state_e { - SSH_GSSAPI_STATE_NONE, /* no status */ - SSH_GSSAPI_STATE_RCV_TOKEN, /* Expecting a token */ - SSH_GSSAPI_STATE_RCV_MIC, /* Expecting a MIC */ -}; - -struct ssh_gssapi_struct{ - enum ssh_gssapi_state_e state; /* current state */ - struct gss_OID_desc_struct mech; /* mechanism being elected for auth */ - gss_cred_id_t server_creds; /* credentials of server */ - gss_cred_id_t client_creds; /* creds delegated by the client */ - gss_ctx_id_t ctx; /* the authentication context */ - gss_name_t client_name; /* Identity of the client */ - char *user; /* username of client */ - char *canonic_user; /* canonic form of the client's username */ - char *service; /* name of the service */ - struct { - gss_name_t server_name; /* identity of server */ - OM_uint32 flags; /* flags used for init context */ - gss_OID oid; /* mech being used for authentication */ - gss_cred_id_t creds; /* creds used to initialize context */ - gss_cred_id_t client_deleg_creds; /* delegated creds (const, not freeable) */ - } client; -}; +#include +static gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; /** @internal * @initializes a gssapi context for authentication */ -static int ssh_gssapi_init(ssh_session session) +int +ssh_gssapi_init(ssh_session session) { if (session->gssapi != NULL) return SSH_OK; @@ -84,21 +62,59 @@ static int ssh_gssapi_init(ssh_session session) return SSH_OK; } +void +ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat) +{ + gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; + OM_uint32 dummy_min; + OM_uint32 message_context = 0; + + do { + gss_display_status(&dummy_min, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); + + do { + gss_display_status(&dummy_min, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); +} + /** @internal * @frees a gssapi context */ -static void ssh_gssapi_free(ssh_session session) +void +ssh_gssapi_free(ssh_session session) { OM_uint32 min; if (session->gssapi == NULL) return; SAFE_FREE(session->gssapi->user); - SAFE_FREE(session->gssapi->mech.elements); + + gss_release_name(&min, &session->gssapi->client.server_name); gss_release_cred(&min,&session->gssapi->server_creds); if (session->gssapi->client.creds != session->gssapi->client.client_deleg_creds) { gss_release_cred(&min, &session->gssapi->client.creds); } + gss_release_oid(&min, &session->gssapi->client.oid); + gss_delete_sec_context(&min, &session->gssapi->ctx, GSS_C_NO_BUFFER); + + SAFE_FREE(session->gssapi->canonic_user); SAFE_FREE(session->gssapi); } @@ -132,57 +148,49 @@ static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) #endif /* WITH_SERVER */ -static void ssh_gssapi_log_error(int verb, - const char *msg, - int maj_stat, - int min_stat) +#ifdef WITH_SERVER + +/** @internal + * @brief get all the oids server supports + * @param[out] selected OID set of supported oids + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_server_oids(gss_OID_set *selected) { - gss_buffer_desc msg_maj = { - .length = 0, - }; - gss_buffer_desc msg_min = { - .length = 0, - }; - OM_uint32 dummy_maj, dummy_min; - OM_uint32 message_context = 0; + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported; /* oids supported by server */ - dummy_maj = gss_display_status(&dummy_min, - maj_stat, - GSS_C_GSS_CODE, - GSS_C_NO_OID, - &message_context, - &msg_maj); - if (dummy_maj != 0) { - goto out; + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; } - dummy_maj = gss_display_status(&dummy_min, - min_stat, - GSS_C_MECH_CODE, - GSS_C_NO_OID, - &message_context, - &msg_min); - if (dummy_maj != 0) { - goto out; + for (i = 0; i < supported->count; ++i) { + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + SAFE_FREE(ptr); + continue; + } + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + SAFE_FREE(ptr); } - SSH_LOG(verb, - "GSSAPI(%s): %s - %s", - msg, - (const char *)msg_maj.value, - (const char *)msg_min.value); + *selected = supported; -out: - if (msg_maj.value) { - gss_release_buffer(&dummy_min, &msg_maj); - } - if (msg_min.value) { - gss_release_buffer(&dummy_min, &msg_min); - } + return SSH_OK; } -#ifdef WITH_SERVER - /** @internal * @brief handles an user authentication using GSSAPI */ @@ -190,12 +198,9 @@ 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 */ + char *hostname = NULL; OM_uint32 maj_stat, min_stat; size_t i; - char *ptr; gss_OID_set supported; /* oids supported by server */ gss_OID_set both_supported; /* oids supported by both client and server */ gss_OID_set selected; /* oid selected for authentication */ @@ -203,41 +208,45 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, size_t oid_count=0; struct gss_OID_desc_struct oid; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; - if (ssh_callbacks_exists(session->server_callbacks, gssapi_select_oid_function)){ - ssh_string oid_s = session->server_callbacks->gssapi_select_oid_function(session, - user, n_oid, oids, - session->server_callbacks->userdata); - if (oid_s != NULL){ - if (ssh_gssapi_init(session) == SSH_ERROR) - return SSH_ERROR; - session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return rc; + } + + /* Callback should select oid and acquire credential */ + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_select_oid_function)) { + ssh_string oid_s = NULL; + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(user); + oid_s = session->server_callbacks->gssapi_select_oid_function( + session, + user, + n_oid, + oids, + session->server_callbacks->userdata); + if (oid_s != NULL) { rc = ssh_gssapi_send_response(session, oid_s); - SSH_STRING_FREE(oid_s); return rc; } else { - return ssh_auth_reply_default(session,0); + return ssh_auth_reply_default(session, 0); } } + /* Default implementation for selecting oid and acquiring credential */ gss_create_empty_oid_set(&min_stat, &both_supported); - maj_stat = gss_indicate_mechs(&min_stat, &supported); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_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); + /* Get the server supported oids */ + rc = ssh_gssapi_server_oids(&supported); + if (rc != SSH_OK) { return SSH_ERROR; } - for (i=0; i < supported->count; ++i){ - ptr = ssh_get_hexa(supported->elements[i].elements, supported->elements[i].length); - SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); - free(ptr); - } - + /* Loop through client supported oids */ for (i=0 ; i< n_oid ; ++i){ unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); size_t len = ssh_string_len(oids[i]); @@ -249,8 +258,10 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); continue; } + /* Convert oid from string to gssapi format */ oid.elements = &oid_s[2]; oid.length = len - 2; + /* Check if this client oid is supported by server */ gss_test_oid_set_member(&min_stat,&oid,supported,&present); if(present){ gss_add_oid_set_member(&min_stat,&oid,&both_supported); @@ -264,34 +275,33 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, 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) { - gss_release_oid_set(&min_stat, &both_supported); + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); 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_DEBUG, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "importing name", - maj_stat, - min_stat); + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); gss_release_oid_set(&min_stat, &both_supported); - return -1; + return SSH_ERROR; } - maj_stat = gss_acquire_cred(&min_stat, server_name, 0, - both_supported, GSS_C_ACCEPT, - &session->gssapi->server_creds, &selected, NULL); - gss_release_name(&min_stat, &server_name); + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + both_supported, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + &selected, + NULL); gss_release_oid_set(&min_stat, &both_supported); - if (maj_stat != GSS_S_COMPLETE) { - 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, @@ -299,8 +309,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, ssh_auth_reply_default(session,0); return SSH_ERROR; } - - SSH_LOG(SSH_LOG_DEBUG, "acquiring credentials %d, %d", maj_stat, min_stat); + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); /* finding which OID from client we selected */ for (i=0 ; i< n_oid ; ++i){ @@ -322,26 +331,18 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, break; } } - session->gssapi->mech.length = oid.length; - session->gssapi->mech.elements = malloc(oid.length); - if (session->gssapi->mech.elements == NULL){ - ssh_set_error_oom(session); - gss_release_oid_set(&min_stat, &selected); - return SSH_ERROR; - } - memcpy(session->gssapi->mech.elements, oid.elements, oid.length); gss_release_oid_set(&min_stat, &selected); session->gssapi->user = strdup(user); - session->gssapi->service = service_name; session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; return ssh_gssapi_send_response(session, oids[i]); } -static char *ssh_gssapi_name_to_char(gss_name_t name) +char * +ssh_gssapi_name_to_char(gss_name_t name) { gss_buffer_desc buffer; OM_uint32 maj_stat, min_stat; - char *ptr; + char *ptr = NULL; maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); ssh_gssapi_log_error(SSH_LOG_DEBUG, "converting name", @@ -359,9 +360,10 @@ static char *ssh_gssapi_name_to_char(gss_name_t name) } -SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ - ssh_string token; - char *hexa; +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server) +{ + ssh_string token = NULL; + char *hexa = NULL; OM_uint32 maj_stat, min_stat; gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; gss_name_t client_name = GSS_C_NO_NAME; @@ -385,13 +387,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ } if (ssh_callbacks_exists(session->server_callbacks, gssapi_accept_sec_ctx_function)){ - ssh_string out_token=NULL; + ssh_string out_token = NULL; rc = session->server_callbacks->gssapi_accept_sec_ctx_function(session, token, &out_token, session->server_callbacks->userdata); if (rc == SSH_ERROR){ ssh_auth_reply_default(session, 0); - ssh_gssapi_free(session); - session->gssapi=NULL; return SSH_PACKET_USED; } if (ssh_string_len(out_token) != 0){ @@ -405,13 +405,12 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ } ssh_packet_send(session); SSH_STRING_FREE(out_token); - } else { - session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; } + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; return SSH_PACKET_USED; } hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); - SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s", hexa); SAFE_FREE(hexa); input_token.length = ssh_string_len(token); input_token.value = ssh_string_data(token); @@ -430,13 +429,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ } if (GSS_ERROR(maj_stat)){ ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Gssapi error", + "accepting token failed", maj_stat, min_stat); gss_release_buffer(&min_stat, &output_token); ssh_auth_reply_default(session,0); - ssh_gssapi_free(session); - session->gssapi=NULL; return SSH_PACKET_USED; } @@ -452,16 +449,15 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ 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); + gss_release_name(&min_stat, &client_name); - if(maj_stat == GSS_S_COMPLETE){ + if (maj_stat == GSS_S_COMPLETE) { session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; } return SSH_PACKET_USED; @@ -469,7 +465,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server){ #endif /* WITH_SERVER */ -static ssh_buffer ssh_gssapi_build_mic(ssh_session session) +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context) { struct ssh_crypto_struct *crypto = NULL; ssh_buffer mic_buffer = NULL; @@ -489,11 +485,12 @@ static ssh_buffer ssh_gssapi_build_mic(ssh_session session) rc = ssh_buffer_pack(mic_buffer, "dPbsss", crypto->session_id_len, - crypto->session_id_len, crypto->session_id, + crypto->session_id_len, + crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, session->gssapi->user, "ssh-connection", - "gssapi-with-mic"); + context); if (rc != SSH_OK) { ssh_set_error_oom(session); SSH_BUFFER_FREE(mic_buffer); @@ -507,7 +504,7 @@ static ssh_buffer ssh_gssapi_build_mic(ssh_session session) SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) { - ssh_string mic_token; + ssh_string mic_token = NULL; 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; @@ -516,24 +513,27 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) (void)user; (void)type; - SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_MIC"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_USERAUTH_GSSAPI_MIC"); mic_token = ssh_buffer_get_ssh_string(packet); if (mic_token == NULL) { ssh_set_error(session, SSH_FATAL, "Missing MIC in packet"); goto error; } - if (session->gssapi == NULL - || session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { - ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state"); + if (session->gssapi == NULL || + session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state"); goto error; } - mic_buffer = ssh_gssapi_build_mic(session); + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); if (mic_buffer == NULL) { ssh_set_error_oom(session); goto error; } - if (ssh_callbacks_exists(session->server_callbacks, gssapi_verify_mic_function)){ + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_verify_mic_function)) { int rc = session->server_callbacks->gssapi_verify_mic_function(session, mic_token, ssh_buffer_get(mic_buffer), ssh_buffer_get_len(mic_buffer), session->server_callbacks->userdata); @@ -546,7 +546,11 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) mic_token_buf.length = ssh_string_len(mic_token); mic_token_buf.value = ssh_string_data(mic_token); - maj_stat = gss_verify_mic(&min_stat, session->gssapi->ctx, &mic_buf, &mic_token_buf, NULL); + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &mic_token_buf, + NULL); ssh_gssapi_log_error(SSH_LOG_DEBUG, "verifying MIC", maj_stat, @@ -578,7 +582,6 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) ssh_auth_reply_default(session,0); end: - ssh_gssapi_free(session); if (mic_buffer != NULL) { SSH_BUFFER_FREE(mic_buffer); } @@ -614,12 +617,14 @@ ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) */ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) { + int rc; + if (session == NULL) { return; } if (session->gssapi == NULL) { - ssh_gssapi_init(session); - if (session->gssapi == NULL) { + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { return; } } @@ -660,9 +665,182 @@ ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) return SSH_ERROR; } -/** @brief returns the OIDs of the mechs that have usable credentials +/** @internal + * @brief Get the base64 encoding of md5 of the oid to add as suffix to GSSAPI + * key exchange algorithms. + * + * @param[in] oid The OID as a ssh_string + * + * @returns the hash or NULL on error + */ +char *ssh_gssapi_oid_hash(ssh_string oid) +{ + unsigned char *h = NULL; + int rc; + char *base64 = NULL; + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return NULL; + } + + rc = md5(ssh_string_data(oid), ssh_string_len(oid), h); + if (rc != SSH_OK) { + SAFE_FREE(h); + return NULL; + } + + base64 = (char *)bin_to_base64(h, 16); + SAFE_FREE(h); + return base64; +} + +/** @internal + * @brief Check if client has GSSAPI mechanisms configured + * + * @param[in] session The SSH session + * + * @returns SSH_OK if any one of the mechanisms is configured or NULL */ -static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) +int ssh_gssapi_check_client_config(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported = GSS_C_NO_OID_SET; + gss_name_t client_id = GSS_C_NO_NAME; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + struct ssh_gssapi_struct *gssapi = NULL; + int ret = SSH_ERROR; + gss_OID_set one_oidset = GSS_C_NO_OID_SET; + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i) { + gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (gssapi == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + gssapi->server_creds = GSS_C_NO_CREDENTIAL; + gssapi->client_creds = GSS_C_NO_CREDENTIAL; + gssapi->ctx = GSS_C_NO_CONTEXT; + gssapi->state = SSH_GSSAPI_STATE_NONE; + + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + ret = SSH_ERROR; + goto end; + } + + gss_create_empty_oid_set(&min_stat, &one_oidset); + gss_add_oid_set_member(&min_stat, &supported->elements[i], &one_oidset); + + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, + &namebuf, + GSS_C_NT_USER_NAME, + &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, + client_id, + GSS_C_INDEFINITE, + one_oidset, + GSS_C_INITIATE, + &gssapi->client.creds, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ret = ssh_gssapi_import_name(gssapi, session->opts.host); + if (ret != SSH_OK) { + goto end; + } + + maj_stat = + ssh_gssapi_init_ctx(gssapi, &input_token, &output_token, &oflags); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "initializing context", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + free(ptr); + + /* If at least one mechanism is configured then return successfully */ + ret = SSH_OK; + + end: + if (ret == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "GSSAPI not configured correctly"); + } + SAFE_FREE(gssapi->user); + + gss_release_oid_set(&min_stat, &one_oidset); + + gss_release_name(&min_stat, &gssapi->client.server_name); + gss_release_cred(&min_stat, &gssapi->server_creds); + gss_release_cred(&min_stat, &gssapi->client.creds); + gss_release_oid(&min_stat, &gssapi->client.oid); + gss_release_buffer(&min_stat, &output_token); + gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER); + + SAFE_FREE(gssapi->canonic_user); + SAFE_FREE(gssapi); + + if (ret == SSH_OK) { + break; + } + } + gss_release_oid_set(&min_stat, &supported); + + return ret; +} + +/** @internal + * @brief acquires a credential and returns a set of mechanisms for which it is + * valid + * + * @param[in] session The SSH session + * @param[out] valid_oids The set of OIDs for which the credential is valid + * + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids) { OM_uint32 maj_stat, min_stat, lifetime; gss_OID_set actual_mechs = GSS_C_NO_OID_SET; @@ -670,9 +848,13 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) gss_name_t client_id = GSS_C_NO_NAME; gss_OID oid; unsigned int i; - char *ptr; + char *ptr = NULL; int ret; + if (session == NULL || session->gssapi == NULL) { + return SSH_ERROR; + } + if (session->gssapi->client.client_deleg_creds == NULL) { if (session->opts.gss_client_identity != NULL) { namebuf.value = (void *)session->opts.gss_client_identity; @@ -691,6 +873,10 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) &session->gssapi->client.creds, &actual_mechs, NULL); if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); ret = SSH_ERROR; goto end; } @@ -705,6 +891,7 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) goto end; } } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); gss_create_empty_oid_set(&min_stat, valid_oids); @@ -732,6 +919,189 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) return ret; } +/** @internal + * @brief Add suffixes of oid hash to each GSSAPI key exchange algorithm + * @param[in] session current session handler + * @returns string suffixed kex algorithms or NULL on error + */ +char *ssh_gssapi_kex_mechs(ssh_session session) +{ + size_t i, j; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + struct ssh_tokens_st *algs = NULL; + char *oid_hash = NULL; + const char *gss_algs = session->opts.gssapi_key_exchange_algs; + char *new_gss_algs = NULL; + char gss_kex_algs[8000] = {0}; + OM_uint32 min_stat; + size_t offset = 0; + + /* Get supported oids */ + if (session->server) { +#ifdef WITH_SERVER + rc = ssh_gssapi_server_oids(&selected); + if (rc == SSH_ERROR) { + return NULL; + } +#endif + } else { + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return NULL; + } + } + ssh_gssapi_free(session); + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + /* Check if algorithms are valid */ + new_gss_algs = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, gss_algs); + if (gss_algs == NULL) { + ssh_set_error( + session, + SSH_FATAL, + "GSSAPI key exchange algorithms not supported or invalid"); + rc = SSH_ERROR; + goto out; + } + + algs = ssh_tokenize(new_gss_algs, ','); + if (algs == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't tokenize GSSAPI key exchange algs"); + rc = SSH_ERROR; + goto out; + } + for (i = 0; i < n_oids; ++i) { + oids[i] = ssh_string_new(selected->elements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, + selected->elements[i].elements, + selected->elements[i].length); + + /* Get the algorithm suffix */ + oid_hash = ssh_gssapi_oid_hash(oids[i]); + if (oid_hash == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + /* For each oid loop through the algorithms, append the oid and append + * the algorithms to a string */ + for (j = 0; algs->tokens[j]; j++) { + if (sizeof(gss_kex_algs) < offset) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + rc = snprintf(&gss_kex_algs[offset], + sizeof(gss_kex_algs) - offset, + "%s%s,", + algs->tokens[j], + oid_hash); + if (rc < 0 || rc >= (ssize_t)sizeof(gss_kex_algs)) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + /* + 1 for ',' */ + offset += strlen(algs->tokens[j]) + strlen(oid_hash) + 1; + } + SAFE_FREE(oid_hash); + SSH_STRING_FREE(oids[i]); + } + + rc = SSH_OK; + +out: + SAFE_FREE(oid_hash); + SAFE_FREE(oids); + SAFE_FREE(new_gss_algs); + gss_release_oid_set(&min_stat, &selected); + ssh_tokens_free(algs); + + if (rc != SSH_OK) { + return NULL; + } + + return strdup(gss_kex_algs); +} + +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host) +{ + gss_buffer_desc hostname; + char name_buf[256] = {0}; + OM_uint32 maj_stat, min_stat; + + /* import target host name */ + snprintf(name_buf, sizeof(name_buf), "host@%s", host); + + hostname.value = name_buf; + hostname.length = strlen(name_buf) + 1; + maj_stat = gss_import_name(&min_stat, + &hostname, + (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, + &gssapi->client.server_name); + SSH_LOG(SSH_LOG_DEBUG, "importing name: %s", name_buf); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "error importing name", + maj_stat, + min_stat); + } + + return maj_stat; +} + +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags) +{ + OM_uint32 maj_stat, min_stat; + + maj_stat = gss_init_sec_context(&min_stat, + gssapi->client.creds, + &gssapi->ctx, + gssapi->client.server_name, + gssapi->client.oid, + gssapi->client.flags, + 0, + NULL, + input_token, + NULL, + output_token, + ret_flags, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "initializing gssapi context", + maj_stat, + min_stat); + } + return maj_stat; +} + /** * @brief launches a gssapi-with-mic auth request * @returns SSH_AUTH_ERROR: A serious error happened\n @@ -746,11 +1116,11 @@ int ssh_gssapi_auth_mic(ssh_session session) ssh_string *oids = NULL; int rc; size_t n_oids = 0; - OM_uint32 maj_stat, min_stat; - char name_buf[256] = {0}; - gss_buffer_desc hostname; + OM_uint32 min_stat; const char *gss_host = session->opts.host; + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); rc = ssh_gssapi_init(session); if (rc == SSH_ERROR) { return SSH_AUTH_ERROR; @@ -759,20 +1129,9 @@ int ssh_gssapi_auth_mic(ssh_session session) if (session->opts.gss_server_identity != NULL) { gss_host = session->opts.gss_server_identity; } - /* import target host name */ - snprintf(name_buf, sizeof(name_buf), "host@%s", gss_host); - hostname.value = name_buf; - hostname.length = strlen(name_buf) + 1; - maj_stat = gss_import_name(&min_stat, &hostname, - (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, - &session->gssapi->client.server_name); - if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_DEBUG, "importing name %d, %d", maj_stat, min_stat); - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "importing name", - maj_stat, - min_stat); + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { return SSH_AUTH_DENIED; } @@ -785,7 +1144,7 @@ int ssh_gssapi_auth_mic(ssh_session session) 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); + rc = ssh_gssapi_client_identity(session, &selected); if (rc == SSH_ERROR) { return SSH_AUTH_DENIED; } @@ -828,6 +1187,50 @@ int ssh_gssapi_auth_mic(ssh_session session) return SSH_AUTH_ERROR; } +/** + * @brief Get the MIC for "gssapi-keyex" authentication. + * @returns SSH_ERROR: A serious error happened\n + * SSH_OK: MIC token is stored in mic_token_buf + */ +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf) +{ + ssh_buffer buf = NULL; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj_stat, min_stat; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &mic_buf, + mic_token_buf); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + SSH_BUFFER_FREE(buf); + + return SSH_OK; +} + static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) { gss_OID ret = NULL; @@ -866,11 +1269,11 @@ 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; + ssh_string oid_s = NULL; gss_uint32 maj_stat, min_stat; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; - char *hexa; + char *hexa = NULL; (void)type; (void)user; @@ -897,22 +1300,12 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ session->gssapi->client.flags |= GSS_C_DELEG_FLAG; } - /* prepare the first TOKEN response */ - maj_stat = gss_init_sec_context(&min_stat, - session->gssapi->client.creds, - &session->gssapi->ctx, - session->gssapi->client.server_name, - session->gssapi->client.oid, - session->gssapi->client.flags, - 0, NULL, &input_token, NULL, - &output_token, NULL, NULL); - if(GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Initializing gssapi context", - maj_stat, - min_stat); + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + if (GSS_ERROR(maj_stat)) { goto error; } + if (output_token.length != 0){ hexa = ssh_get_hexa(output_token.value, output_token.length); SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); @@ -935,8 +1328,6 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ error: session->auth.state = SSH_AUTH_STATE_ERROR; - ssh_gssapi_free(session); - session->gssapi = NULL; return SSH_PACKET_USED; } @@ -950,7 +1341,7 @@ static int ssh_gssapi_send_mic(ssh_session session) SSH_LOG(SSH_LOG_PACKET,"Sending SSH_MSG_USERAUTH_GSSAPI_MIC"); - mic_buffer = ssh_gssapi_build_mic(session); + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); if (mic_buffer == NULL) { ssh_set_error_oom(session); return SSH_ERROR; @@ -987,10 +1378,11 @@ static int ssh_gssapi_send_mic(ssh_session session) return ssh_packet_send(session); } -SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client) +{ int rc; - ssh_string token; - char *hexa; + ssh_string token = NULL; + char *hexa = NULL; OM_uint32 maj_stat, min_stat; gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; (void)user; @@ -1013,27 +1405,13 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); input_token.value = ssh_string_data(token); - maj_stat = gss_init_sec_context(&min_stat, - session->gssapi->client.creds, - &session->gssapi->ctx, - session->gssapi->client.server_name, - session->gssapi->client.oid, - session->gssapi->client.flags, - 0, NULL, &input_token, NULL, - &output_token, NULL, NULL); - - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "accepting token", - maj_stat, - min_stat); + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); SSH_STRING_FREE(token); - if (GSS_ERROR(maj_stat)){ - ssh_gssapi_log_error(SSH_LOG_DEBUG, - "Gssapi error", - maj_stat, - min_stat); + if (GSS_ERROR(maj_stat)) { goto error; } @@ -1064,7 +1442,5 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ error: session->auth.state = SSH_AUTH_STATE_ERROR; - ssh_gssapi_free(session); - session->gssapi = NULL; return SSH_PACKET_USED; } diff --git a/src/gzip.c b/src/gzip.c index cff15518..e814080a 100644 --- a/src/gzip.c +++ b/src/gzip.c @@ -26,13 +26,15 @@ #include #include -#include #include "libssh/buffer.h" #include "libssh/crypto.h" #include "libssh/priv.h" #include "libssh/session.h" +#ifdef WITH_ZLIB +#include + #ifndef BLOCKSIZE #define BLOCKSIZE 4092 #endif @@ -50,6 +52,7 @@ initcompress(ssh_session session, int level) status = deflateInit(stream, level); if (status != Z_OK) { + deflateEnd(stream); SAFE_FREE(stream); ssh_set_error(session, SSH_FATAL, @@ -158,6 +161,7 @@ initdecompress(ssh_session session) status = inflateInit(stream); if (status != Z_OK) { + inflateEnd(stream); SAFE_FREE(stream); ssh_set_error(session, SSH_FATAL, @@ -258,3 +262,41 @@ decompress_buffer(ssh_session session, ssh_buffer buf, size_t maxlen) SSH_BUFFER_FREE(dest); return 0; } + +void +compress_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->compress_out_ctx) { + deflateEnd(crypto->compress_out_ctx); + } + SAFE_FREE(crypto->compress_out_ctx); + + if (crypto->compress_in_ctx) { + inflateEnd(crypto->compress_in_ctx); + } + SAFE_FREE(crypto->compress_in_ctx); +} +#else /* WITH_ZLIB */ + +int +compress_buffer(UNUSED_PARAM(ssh_session session), UNUSED_PARAM(ssh_buffer buf)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} +int +decompress_buffer(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_buffer buf), + UNUSED_PARAM(size_t maxlen)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} + +void +compress_cleanup(UNUSED_PARAM(struct ssh_crypto_struct *crypto)) +{ + /* no-op */ +} + +#endif /* WITH_ZLIB */ diff --git a/src/hybrid_mlkem.c b/src/hybrid_mlkem.c new file mode 100644 index 00000000..2294561b --- /dev/null +++ b/src/hybrid_mlkem.c @@ -0,0 +1,908 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/pki.h" +#include "libssh/ssh2.h" + +/* sorry, this needs to come last to avoid header dependency issues */ +#include "libssh/bignum.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_hybrid_mlkem_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_client_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static ssh_string derive_curve25519_secret(ssh_session session) +{ + ssh_string secret = NULL; + int rc; + + secret = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (secret == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_curve25519_create_k(session, ssh_string_data(secret)); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Curve25519 secret derivation failed"); + ssh_string_free(secret); + return NULL; + } + + return secret; +} + +static ssh_string derive_nist_curve_secret(ssh_session session, + size_t secret_size) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string secret = NULL; + int rc; + + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ECDH secret derivation failed"); + return NULL; + } + + secret = ssh_make_padded_bignum_string(crypto->shared_secret, secret_size); + if (secret == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to encode the shared secret"); + } + + bignum_safe_free(crypto->shared_secret); + + return secret; +} + +static ssh_string derive_ecdh_secret(ssh_session session) +{ + ssh_string secret = NULL; + + switch (session->next_crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + secret = derive_curve25519_secret(session); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: + secret = derive_nist_curve_secret(session, NISTP256_SHARED_SECRET_SIZE); + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + secret = derive_nist_curve_secret(session, NISTP384_SHARED_SECRET_SIZE); + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + return NULL; + } + + return secret; +} + +static int derive_hybrid_secret(ssh_session session, + ssh_mlkem_shared_secret mlkem_shared_secret, + ssh_string ecdh_shared_secret) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer combined_secret = NULL; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + size_t digest_len; + int rc, ret = SSH_ERROR; + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + digest = sha256; + digest_len = SHA256_DIGEST_LEN; + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + digest = sha384; + digest_len = SHA384_DIGEST_LEN; + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Concatenate the two shared secrets */ + combined_secret = ssh_buffer_new(); + if (combined_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + ssh_buffer_set_secure(combined_secret); + + rc = ssh_buffer_pack(combined_secret, + "PP", + MLKEM_SHARED_SECRET_SIZE, + mlkem_shared_secret, + ssh_string_len(ecdh_shared_secret), + ssh_string_data(ecdh_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to concatenate shared secrets"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Concatenated shared secrets", + ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret)); +#endif + + /* Store the hashed combined shared secrets */ + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->hybrid_shared_secret); + crypto->hybrid_shared_secret = ssh_string_new(digest_len); + if (crypto->hybrid_shared_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = digest(ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret), + ssh_string_data(crypto->hybrid_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Shared secret hashing failed"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Hybrid shared secret", + ssh_string_data(crypto->hybrid_shared_secret), + digest_len); +#endif + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(combined_secret); + return ret; +} + +int ssh_client_hybrid_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer client_init_buffer = NULL; + int rc, ret = SSH_ERROR; + + SSH_LOG(SSH_LOG_TRACE, "Initializing hybrid ML-KEM key exchange"); + + /* Prepare a buffer to concatenate ML-KEM + ECDH public keys */ + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + /* Generate an ML-KEM keypair */ + rc = ssh_mlkem_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate an ML-KEM keypair"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Generate an ECDH keypair and concatenate the public keys */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + CURVE25519_PUBKEY_SIZE, + crypto->curve25519_client_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey), + ssh_string_data(crypto->ecdh_client_pubkey)); + + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct client init buffer"); + goto cleanup; + } + + /* Convert the client init buffer to an SSH string */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_string_new(ssh_buffer_get_len(client_init_buffer)); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_client_init, + ssh_buffer_get(client_init_buffer), + ssh_buffer_get_len(client_init_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert client init to string"); + goto cleanup; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEX_HYBRID_INIT, + crypto->hybrid_client_init); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(client_init_buffer); + return ret; +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_string ecdh_shared_secret = NULL; + ssh_buffer server_reply_buffer = NULL; + size_t read_len; + size_t ecdh_server_pubkey_size; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid server reply"); + + ssh_client_hybrid_mlkem_remove_callbacks(session); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to import public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server reply containing ML-KEM ciphertext + ECDH public key */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error(session, SSH_FATAL, "No server reply in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(server_reply_buffer, + ssh_string_data(crypto->hybrid_server_reply), + ssh_string_len(crypto->hybrid_server_reply)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack server reply to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Store ML-KEM ciphertext for decapsulation and sessionid calculation */ + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->mlkem_ciphertext), + mlkem_info->ciphertext_size); + if (read_len != mlkem_info->ciphertext_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM ciphertext from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Extract server ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(server_reply_buffer, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(server_reply_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the server reply buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_server_pubkey_size = ssh_buffer_get_len(server_reply_buffer); + ssh_string_free(crypto->ecdh_server_pubkey); + crypto->ecdh_server_pubkey = ssh_string_new(ecdh_server_pubkey_size); + if (crypto->ecdh_server_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->ecdh_server_pubkey), + ecdh_server_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Decapsulate ML-KEM shared secret */ + rc = ssh_mlkem_decapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM decapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get signature for verification */ + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + crypto->dh_server_signature = signature; + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_hybrid_mlkem_init, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_server_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string ecdh_shared_secret = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_buffer server_reply_buffer = NULL; + ssh_buffer client_init_buffer = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string signature = NULL; + ssh_string pubkey_blob = NULL; + size_t ecdh_client_pubkey_size; + size_t read_len; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid client init"); + + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Generate an ECDH keypair */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Get client init: ML-KEM public key + ECDH public key */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error(session, SSH_FATAL, "No client public keys in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(client_init_buffer, + ssh_string_data(crypto->hybrid_client_init), + ssh_string_len(crypto->hybrid_client_init)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack client init to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Extract client ML-KEM public key */ + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->mlkem_client_pubkey), + mlkem_info->pubkey_size); + if (read_len != mlkem_info->pubkey_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM pubkey from " + "the client init buffer, buffer too short"); + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Extract client ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(client_init_buffer, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the client init buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(client_init_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the client init buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_client_pubkey_size = ssh_buffer_get_len(client_init_buffer); + ssh_string_free(crypto->ecdh_client_pubkey); + crypto->ecdh_client_pubkey = ssh_string_new(ecdh_client_pubkey_size); + if (crypto->ecdh_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->ecdh_client_pubkey), + ecdh_client_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Encapsulate an ML-KEM shared secret using client's ML-KEM public key */ + rc = ssh_mlkem_encapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM encapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Create server reply: ML-KEM ciphertext + ECDH public key */ + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + CURVE25519_PUBKEY_SIZE, + crypto->curve25519_server_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->ecdh_server_pubkey), + ssh_string_data(crypto->ecdh_server_pubkey)); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct server reply buffer"); + goto cleanup; + } + + /* Convert the reply buffer to an SSH string for sending */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_string_new(ssh_buffer_get_len(server_reply_buffer)); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_server_reply, + ssh_buffer_get(server_reply_buffer), + ssh_buffer_get_len(server_reply_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert reply buffer to string"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add MSG_KEX_ECDH_REPLY header */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add MSG_KEX_HYBRID_REPLY to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server host key */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not get server key params"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Build session ID */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server public key to output */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server hostkey to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server reply */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server reply to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Sign the exchange hash */ + signature = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add signature */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, signature); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add signature to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_string_free(signature); + ssh_buffer_free(client_init_buffer); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_server_hybrid_mlkem_init(ssh_session session) +{ + SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM hybrid server callbacks"); + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); +} + +#endif /* WITH_SERVER */ diff --git a/src/kdf.c b/src/kdf.c index 44f06631..6bc477ce 100644 --- a/src/kdf.c +++ b/src/kdf.c @@ -58,65 +58,102 @@ 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, - uint8_t 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 @@ -124,6 +161,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, unsigned char digest[DIGEST_MAX_LEN]; size_t output_len = crypto->digest_len; ssh_mac_ctx ctx; + int rc; if (DIGEST_MAX_LEN < crypto->digest_len) { return -1; @@ -134,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, &key_type, 1); - ssh_mac_update(ctx, crypto->session_id, crypto->session_id_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; @@ -150,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 { diff --git a/src/kex-gss.c b/src/kex-gss.c new file mode 100644 index 00000000..86ea55f6 --- /dev/null +++ b/src/kex-gss.c @@ -0,0 +1,671 @@ +/* + * kex-gss.c - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * 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/gssapi.h" +#include +#include +#include + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/kex-gss.h" +#include "libssh/bignum.h" +#include "libssh/curve25519.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply); + +static ssh_packet_callback gss_kex_client_callbacks[] = { + ssh_packet_client_gss_kex_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callbacks = { + .start = SSH2_MSG_KEXGSS_COMPLETE, + .n_callbacks = 1, + .callbacks = gss_kex_client_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey); + +static ssh_packet_callback gss_kex_client_callback_hostkey[] = { + ssh_packet_client_gss_kex_hostkey, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callback_hostkey = { + .start = SSH2_MSG_KEXGSS_HOSTKEY, + .n_callbacks = 1, + .callbacks = gss_kex_client_callback_hostkey, + .user = NULL, +}; + +static ssh_string dh_init(ssh_session session) +{ + int rc, keypair; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum const_pubkey; +#endif + bignum pubkey = NULL; + ssh_string pubkey_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_SERVER_KEYPAIR; + } else { + keypair = DH_CLIENT_KEYPAIR; + } + + rc = ssh_dh_init_common(crypto); + if (rc != SSH_OK) { + goto end; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, keypair); + if (rc != SSH_OK) { + goto end; + } + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &const_pubkey); + bignum_dup(const_pubkey, &pubkey); +#else + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &pubkey); +#endif + if (rc != SSH_OK) { + goto end; + } + + pubkey_string = ssh_make_bignum_string(pubkey); + +end: + bignum_safe_free(pubkey); + return pubkey_string; +} + +static int dh_import_peer_key(ssh_session session, ssh_string peer_key) +{ + int rc, keypair; + bignum peer_key_bn; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_CLIENT_KEYPAIR; + } else { + keypair = DH_SERVER_KEYPAIR; + } + + peer_key_bn = ssh_make_string_bn(peer_key); + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, keypair, NULL, peer_key_bn); + if (rc != SSH_OK) { + bignum_safe_free(peer_key_bn); + } + + return rc; +} + +/** @internal + * @brief Starts gssapi key exchange + */ +int ssh_client_gss_kex_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + int rc, ret = SSH_ERROR; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + OM_uint32 maj_stat, min_stat; + const char *gss_host = session->opts.host; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + ssh_string pubkey = NULL; + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + pubkey = dh_init(session); + if (pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to generate DH keypair"); + goto out; + } + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate ECDH keypair"); + goto out; + } + pubkey = ssh_string_copy(crypto->ecdh_client_pubkey); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate Curve25519 keypair"); + goto out; + } + pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto out; + } + rc = ssh_string_fill(pubkey, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto out; + } + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto out; + } + + rc = ssh_gssapi_init(session); + if (rc != SSH_OK) { + goto out; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_gssapi_client_identity(session, &selected); + if (rc != SSH_OK) { + goto out; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + gss_release_oid_set(&min_stat, &selected); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "Initializing gssapi context", + maj_stat, + min_stat); + goto out; + } + if (!(oflags & GSS_C_INTEG_FLAG) || !(oflags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(init) integrity and mutual flags were not set"); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPS", + SSH2_MSG_KEXGSS_INIT, + output_token.length, + (size_t)output_token.length, + output_token.value, + pubkey); + if (rc != SSH_OK) { + goto out; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callbacks); + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callback_hostkey); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + ret = SSH_OK; + +out: + gss_release_buffer(&min_stat, &output_token); + ssh_string_free(pubkey); + return ret; +} + +void ssh_client_gss_kex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callbacks); +} + +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callback_hostkey); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string mic = NULL, otoken = NULL, server_pubkey = NULL; + uint8_t b; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + OM_uint32 maj_stat; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SSbS", &server_pubkey, &mic, &b, &otoken); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "No public key in server reply"); + goto error; + } + + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + session->gssapi_key_exchange_mic = mic; + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + if (maj_stat != GSS_S_COMPLETE) { + goto error; + } + SSH_STRING_FREE(otoken); + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = dh_import_peer_key(session, server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import server pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_CLIENT_KEYPAIR, + DH_SERVER_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + crypto->ecdh_server_pubkey = ssh_string_copy(server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + memcpy(crypto->curve25519_server_pubkey, + ssh_string_data(server_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + +error: + ssh_string_free(server_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey) +{ + ssh_string pubkey_blob = NULL; + int rc; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callback_hostkey(session); + + rc = ssh_buffer_unpack(packet, "S", &pubkey_blob); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH2_MSG_KEXGSS_HOSTKEY packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + return SSH_PACKET_USED; +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init); + +static ssh_packet_callback gss_kex_server_callbacks[] = { + ssh_packet_server_gss_kex_init, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_server_callbacks = { + .start = SSH2_MSG_KEXGSS_INIT, + .n_callbacks = 1, + .callbacks = gss_kex_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the gssapi kex callbacks + */ +void ssh_server_gss_kex_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_server_callbacks); +} + +/** @internal + * @brief processes a SSH_MSG_KEXGSS_INIT and sends + * the appropriate SSH_MSG_KEXGSS_COMPLETE + */ +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string client_pubkey = NULL; + ssh_string server_pubkey = NULL; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + ssh_string otoken = NULL; + ssh_string server_pubkey_blob = NULL; + OM_uint32 maj_stat, min_stat; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + char *hostname = NULL; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + rc = ssh_buffer_unpack(packet, "S", &otoken); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No token in client request"); + goto error; + } + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + + rc = ssh_buffer_unpack(packet, "S", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No public key in client request"); + goto error; + } + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + server_pubkey = dh_init(session); + if (server_pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not generate a DH keypair"); + goto error; + } + rc = dh_import_peer_key(session, client_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import client pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, + DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate an ECDH keypair"); + goto error; + } + crypto->ecdh_client_pubkey = ssh_string_copy(client_pubkey); + server_pubkey = ssh_string_copy(crypto->ecdh_server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate a Curve25519 keypair"); + goto error; + } + server_pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (server_pubkey == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_string_fill(server_pubkey, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto error; + } + memcpy(crypto->curve25519_client_pubkey, + ssh_string_data(client_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Also imports next_crypto->server_pubkey + * Can give error when using null hostkey */ + ssh_get_key_params(session, &privkey, &digest); + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + if (strcmp(crypto->kex_methods[SSH_HOSTKEYS], "null") != 0) { + rc = + ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEXGSS_HOSTKEY, + server_pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_HOSTKEY"); + SSH_STRING_FREE(server_pubkey_blob); + } + + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + goto error; + } + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto error; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + goto error; + } + + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + GSS_C_NO_OID_SET, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring credentials", + maj_stat, + min_stat); + goto error; + } + + maj_stat = gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client_name, + NULL /*mech_oid*/, + &output_token, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + goto error; + } + SSH_STRING_FREE(otoken); + gss_release_name(&min_stat, &client_name); + if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(accept) integrity and mutual flags were not set"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "token accepted"); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &msg, + &mic); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "creating mic failed", + maj_stat, + min_stat); + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSdPbdP", + SSH2_MSG_KEXGSS_COMPLETE, + server_pubkey, + mic.length, + (size_t)mic.length, + mic.value, + 1, + output_token.length, + (size_t)output_token.length, + output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + gss_release_buffer(&min_stat, &output_token); + gss_release_buffer(&min_stat, &mic); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_COMPLETE"); + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + return SSH_OK; +error: + SSH_STRING_FREE(server_pubkey_blob); + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXGSS_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init) +{ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXGSS_INIT"); + ssh_packet_remove_callbacks(session, &ssh_gss_kex_server_callbacks); + ssh_server_gss_kex_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/kex.c b/src/kex.c index 42d94ad7..1e297891 100644 --- a/src/kex.c +++ b/src/kex.c @@ -40,28 +40,27 @@ #include "libssh/ssh2.h" #include "libssh/string.h" #include "libssh/curve25519.h" +#include "libssh/sntrup761.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/kex-gss.h" #include "libssh/knownhosts.h" #include "libssh/misc.h" #include "libssh/pki.h" #include "libssh/bignum.h" #include "libssh/token.h" +#include "libssh/gssapi.h" -#ifdef WITH_BLOWFISH_CIPHER -# if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) -# define BLOWFISH "blowfish-cbc," -# else -# define BLOWFISH "" -# endif +#ifdef HAVE_BLOWFISH +# define BLOWFISH ",blowfish-cbc" #else # define BLOWFISH "" #endif #ifdef HAVE_LIBGCRYPT # define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ - "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" + "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBMBEDCRYPTO) # ifdef MBEDTLS_GCM_C @@ -69,23 +68,21 @@ # else # define GCM "" # endif /* MBEDTLS_GCM_C */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBCRYPTO) # ifdef HAVE_OPENSSL_AES_H # define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" # else /* HAVE_OPENSSL_AES_H */ # define AES "" # define AES_CBC "" # endif /* HAVE_OPENSSL_AES_H */ -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define DES_SUPPORTED ",3des-cbc" #endif /* HAVE_LIBCRYPTO */ #ifdef WITH_ZLIB @@ -102,6 +99,21 @@ #define CURVE25519 "" #endif /* HAVE_CURVE25519 */ +#ifdef HAVE_SNTRUP761 +#define SNTRUP761X25519 "sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com," +#else +#define SNTRUP761X25519 "" +#endif /* HAVE_SNTRUP761 */ + +#ifdef HAVE_MLKEM1024 +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," \ + "mlkem1024nistp384-sha384," +#else +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," +#endif /* HAVE_MLKEM1024 */ + #ifdef HAVE_ECC #define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," #define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ @@ -165,6 +177,8 @@ #define CHACHA20 "chacha20-poly1305@openssh.com," #define DEFAULT_KEY_EXCHANGE \ + HYBRID_MLKEM \ + SNTRUP761X25519 \ CURVE25519 \ ECDH \ "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ @@ -178,6 +192,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,"\ @@ -271,38 +288,58 @@ static const char *ssh_kex_descriptions[] = { NULL }; -const char *ssh_kex_get_default_methods(uint32_t algo) +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type) { - if (algo >= SSH_KEX_METHODS) { + if (type >= SSH_KEX_METHODS) { return NULL; } - return default_methods[algo]; + return default_methods[type]; } - -const char *ssh_kex_get_supported_method(uint32_t algo) +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type) { - if (algo >= SSH_KEX_METHODS) { + if (type >= SSH_KEX_METHODS) { return NULL; } - return supported_methods[algo]; + return supported_methods[type]; } -const char *ssh_kex_get_description(uint32_t algo) { - if (algo >= SSH_KEX_METHODS) { - return NULL; - } +const char *ssh_kex_get_description(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } - return ssh_kex_descriptions[algo]; + return ssh_kex_descriptions[type]; } -const char *ssh_kex_get_fips_methods(uint32_t algo) { - if (algo >= SSH_KEX_METHODS) { - return NULL; - } +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } - return fips_methods[algo]; + return fips_methods[type]; +} + +/** + * @brief Get a list of supported algorithms of a given type. This respects the + * FIPS mode status. + * + * @param[in] type The type of the algorithm to query (SSH_KEX, SSH_MAC_C_S, + * ...). + * + * @return The list of supported methods as comma-separated string, or NULL for + * unknown type. + */ +const char *ssh_get_supported_methods(enum ssh_kex_types_e type) +{ + if (ssh_fips_mode()) { + return ssh_kex_get_fips_methods(type); + } else { + return ssh_kex_get_supported_method(type); + } } /** @@ -317,7 +354,7 @@ static int cmp_first_kex_algo(const char *client_str, size_t client_kex_len; size_t server_kex_len; - char *colon; + char *colon = NULL; int is_wrong = 1; @@ -355,7 +392,6 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) int server_kex = session->server; ssh_string str = NULL; char *strings[SSH_KEX_METHODS] = {0}; - char *rsa_sig_ext = NULL; int rc = SSH_ERROR; size_t len; @@ -509,14 +545,34 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) 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 = 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 = 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(crypto->client_kex.methods[SSH_KEX], - KEX_EXTENSION_CLIENT); + ok = match_group(crypto->client_kex.methods[SSH_KEX], + KEX_EXTENSION_CLIENT); if (ok) { const char *hostkeys = NULL, *wanted_hostkeys = NULL; @@ -530,7 +586,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) */ hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; - ok = ssh_match_group(hostkeys, "rsa-sha2-512"); + ok = match_group(hostkeys, "rsa-sha2-512"); if (ok) { /* Check if rsa-sha2-512 is allowed by config */ if (wanted_hostkeys != NULL) { @@ -542,7 +598,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) SAFE_FREE(is_allowed); } } - ok = ssh_match_group(hostkeys, "rsa-sha2-256"); + ok = match_group(hostkeys, "rsa-sha2-256"); if (ok) { /* Check if rsa-sha2-256 is allowed by config */ if (wanted_hostkeys != NULL) { @@ -561,6 +617,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) */ if ((session->extensions & SSH_EXT_SIG_RSA_SHA256) && (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + char *rsa_sig_ext = NULL; session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", hostkeys); @@ -735,12 +792,11 @@ char *ssh_client_select_hostkeys(ssh_session session) 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; + const char *wanted = NULL; int ok; int i; - size_t kex_len, len; + bool gssapi_null_alg = false; + char *hostkeys = NULL; /* 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. */ @@ -753,6 +809,42 @@ int ssh_set_client_kex(ssh_session session) ssh_set_error(session, SSH_FATAL, "PRNG error"); return SSH_ERROR; } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + char *gssapi_algs = NULL; + + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + ok = ssh_gssapi_import_name(session->gssapi, session->opts.host); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + + /* Prefix the default algorithms with gsskex algs */ + if (ssh_fips_mode()) { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(fips_methods[SSH_KEX], + gssapi_algs); + } else { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(default_methods[SSH_KEX], + gssapi_algs); + } + + gssapi_null_alg = true; + + SAFE_FREE(gssapi_algs); + } +#endif /* Set the list of allowed algorithms in order of preference, if it hadn't * been set yet. */ @@ -766,6 +858,16 @@ int ssh_set_client_kex(ssh_session session) ssh_set_error_oom(session); return SSH_ERROR; } + if (gssapi_null_alg) { + hostkeys = + ssh_append_without_duplicates(client->methods[i], "null"); + if (hostkeys == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + SAFE_FREE(client->methods[i]); + client->methods[i] = hostkeys; + } continue; } @@ -789,23 +891,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; } @@ -828,6 +959,14 @@ kex_select_kex_type(const char *kex) { if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { return SSH_KEX_DH_GROUP1_SHA1; + } else if (strncmp(kex, "gss-group14-sha256-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP14_SHA256; + } else if (strncmp(kex, "gss-group16-sha512-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP16_SHA512; + } else if (strncmp(kex, "gss-nistp256-sha256-", 20) == 0) { + return SSH_GSS_KEX_ECDH_NISTP256_SHA256; + } else if (strncmp(kex, "gss-curve25519-sha256-", 22) == 0) { + return SSH_GSS_KEX_CURVE25519_SHA256; } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { return SSH_KEX_DH_GROUP14_SHA1; } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { @@ -852,6 +991,18 @@ kex_select_kex_type(const char *kex) return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; } else if (strcmp(kex, "curve25519-sha256") == 0) { return SSH_KEX_CURVE25519_SHA256; + } else if (strcmp(kex, "sntrup761x25519-sha512@openssh.com") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM; + } else if (strcmp(kex, "sntrup761x25519-sha512") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512; + } else if (strcmp(kex, "mlkem768x25519-sha256") == 0) { + return SSH_KEX_MLKEM768X25519_SHA256; + } else if (strcmp(kex, "mlkem768nistp256-sha256") == 0) { + return SSH_KEX_MLKEM768NISTP256_SHA256; +#ifdef HAVE_MLKEM1024 + } else if (strcmp(kex, "mlkem1024nistp384-sha384") == 0) { + return SSH_KEX_MLKEM1024NISTP384_SHA384; +#endif } /* should not happen. We should be getting only valid names at this stage */ return 0; @@ -873,6 +1024,14 @@ static void revert_kex_callbacks(ssh_session session) case SSH_KEX_DH_GROUP18_SHA512: ssh_client_dh_remove_callbacks(session); break; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: +#ifdef WITH_GSSAPI + ssh_client_gss_kex_remove_callbacks(session); +#endif /* WITH_GSSAPI */ + break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: @@ -892,6 +1051,19 @@ static void revert_kex_callbacks(ssh_session session) ssh_client_curve25519_remove_callbacks(session); break; #endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_client_sntrup761x25519_remove_callbacks(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_client_hybrid_mlkem_remove_callbacks(session); + break; } } @@ -908,11 +1080,19 @@ int ssh_kex_select_methods (ssh_session session) 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++) { @@ -992,7 +1172,7 @@ int ssh_send_kex(ssh_session session) rc = ssh_buffer_pack(session->out_buffer, "bP", SSH2_MSG_KEXINIT, - 16, + (size_t)16, kex->cookie); /* cookie */ if (rc != SSH_OK) goto error; @@ -1290,6 +1470,7 @@ int ssh_make_sessionid(ssh_session session) buf = ssh_buffer_new(); if (buf == NULL) { + ssh_set_error_oom(session); return rc; } @@ -1298,6 +1479,9 @@ int ssh_make_sessionid(ssh_session session) session->clientbanner, session->serverbanner); if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack client and server banner"); goto error; } @@ -1311,20 +1495,38 @@ int ssh_make_sessionid(ssh_session session) rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to get next server pubkey blob"); goto error; } + if (server_pubkey_blob == NULL) { + if ((session->server && ssh_kex_is_gss(session->next_crypto)) || + session->opts.gssapi_key_exchange) { + server_pubkey_blob = ssh_string_new(0); + if (server_pubkey_blob == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + } + rc = ssh_buffer_pack(buf, "dPdPS", ssh_buffer_get_len(client_hash), - ssh_buffer_get_len(client_hash), + (size_t)ssh_buffer_get_len(client_hash), ssh_buffer_get(client_hash), ssh_buffer_get_len(server_hash), - ssh_buffer_get_len(server_hash), + (size_t)ssh_buffer_get_len(server_hash), ssh_buffer_get(server_hash), server_pubkey_blob); SSH_STRING_FREE(server_pubkey_blob); if (rc != SSH_OK){ + ssh_set_error(session, + SSH_FATAL, + "Failed to pack hashes and pubkey blob"); goto error; } @@ -1332,7 +1534,9 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, NULL, &client_pubkey); @@ -1349,6 +1553,7 @@ int ssh_make_sessionid(ssh_session session) client_pubkey, server_pubkey); if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH pubkeys"); goto error; } #if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L @@ -1384,6 +1589,7 @@ int ssh_make_sessionid(ssh_session session) client_pubkey, server_pubkey); if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH GEX params"); goto error; } #if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L @@ -1396,6 +1602,7 @@ int ssh_make_sessionid(ssh_session session) case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_ECDH_SHA2_NISTP384: case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: if (session->next_crypto->ecdh_client_pubkey == NULL || session->next_crypto->ecdh_server_pubkey == NULL) { SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing"); @@ -1406,6 +1613,7 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->ecdh_client_pubkey, session->next_crypto->ecdh_server_pubkey); if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack ECDH pubkeys"); goto error; } break; @@ -1413,21 +1621,91 @@ int ssh_make_sessionid(ssh_session session) #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_GSS_KEX_CURVE25519_SHA256: rc = ssh_buffer_pack(buf, "dPdP", CURVE25519_PUBKEY_SIZE, - (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, CURVE25519_PUBKEY_SIZE, - (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_server_pubkey); + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack Curve25519 pubkeys"); goto error; } break; #endif /* HAVE_CURVE25519 */ +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "dPPdPP", + SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, + SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_CIPHERTEXT_SIZE, + session->next_crypto->sntrup761_ciphertext, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack SNTRU Prime params"); + goto error; + } + break; +#endif /* HAVE_SNTRUP761 */ + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->hybrid_client_init, + session->next_crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack ML-KEM individual components"); + goto error; + } + break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + rc = SSH_ERROR; + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm"); + goto error; + } + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "F", + session->next_crypto->shared_secret, + SHA512_DIGEST_LEN); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, "S", session->next_crypto->hybrid_shared_secret); + break; + default: + rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); + break; } - rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack shared secret"); goto error; } @@ -1435,6 +1713,8 @@ int ssh_make_sessionid(ssh_session session) ssh_log_hexdump("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); #endif + /* Set rc for the following switch statement in case we goto error. */ + rc = SSH_ERROR; switch (session->next_crypto->kex_type) { case SSH_KEX_DH_GROUP1_SHA1: case SSH_KEX_DH_GROUP14_SHA1: @@ -1452,9 +1732,14 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->secret_hash); break; case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: case SSH_KEX_ECDH_SHA2_NISTP256: case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA256: #endif /* WITH_GEX */ @@ -1469,6 +1754,9 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->secret_hash); break; case SSH_KEX_ECDH_SHA2_NISTP384: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA384; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); @@ -1480,8 +1768,11 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->secret_hash); break; case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: case SSH_KEX_DH_GROUP18_SHA512: case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; session->next_crypto->digest_type = SSH_KDF_SHA512; session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); @@ -1493,7 +1784,13 @@ int ssh_make_sessionid(ssh_session session) ssh_buffer_get_len(buf), session->next_crypto->secret_hash); break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm for hash computation"); + rc = SSH_ERROR; + goto error; } + /* During the first kex, secret hash and session ID are equal. However, after * a key re-exchange, a new secret hash is calculated. This hash will not replace * but complement existing session id. @@ -1502,6 +1799,7 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->session_id = malloc(session->next_crypto->digest_len); if (session->next_crypto->session_id == NULL) { ssh_set_error_oom(session); + rc = SSH_ERROR; goto error; } memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, @@ -1618,7 +1916,23 @@ int ssh_generate_session_keys(ssh_session session) size_t intkey_srv_to_cli_len = 0; int rc = -1; - k_string = ssh_make_bignum_string(crypto->shared_secret); + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + k_string = ssh_make_padded_bignum_string(crypto->shared_secret, + crypto->digest_len); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + k_string = ssh_string_copy(crypto->hybrid_shared_secret); + break; + default: + k_string = ssh_make_bignum_string(crypto->shared_secret); + break; + } if (k_string == NULL) { ssh_set_error_oom(session); goto error; @@ -1730,3 +2044,22 @@ int ssh_generate_session_keys(ssh_session session) return rc; } + +/** @internal + * @brief Check if a given crypto context has a GSSAPI KEX set + * + * @param[in] crypto The SSH crypto context + * @return true if the KEX of the context is a GSSAPI KEX, false otherwise + */ +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto) +{ + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + return true; + default: + return false; + } +} diff --git a/src/known_hosts.c b/src/known_hosts.c index a1d9a432..701576ce 100644 --- a/src/known_hosts.c +++ b/src/known_hosts.c @@ -79,11 +79,11 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, const char **found_type) { char buffer[MAX_LINE_SIZE] = {0}; - char *ptr; - struct ssh_tokens_st *tokens; + char *ptr = NULL; + struct ssh_tokens_st *tokens = NULL; if (*file == NULL) { - *file = fopen(filename,"r"); + *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); if (*file == NULL) { return NULL; } @@ -149,7 +149,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, static int check_public_key(ssh_session session, char **tokens) { ssh_string pubkey_blob = NULL; ssh_buffer pubkey_buffer; - char *pubkey_64; + char *pubkey_64 = NULL; int rc; /* ssh-rsa, ssh-ed25519, .. */ @@ -205,11 +205,11 @@ static int match_hashed_host(const char *host, const char *sourcehash) * hash := HMAC_SHA1(key=salt,data=host) */ unsigned char buffer[256] = {0}; - ssh_buffer salt; - ssh_buffer hash; - HMACCTX mac; - char *source; - char *b64hash; + ssh_buffer salt = NULL; + ssh_buffer hash = NULL; + HMACCTX mac = NULL; + char *source = NULL; + char *b64hash = NULL; int match, rc; size_t size; @@ -304,14 +304,14 @@ static int match_hashed_host(const char *host, const char *sourcehash) int ssh_is_server_known(ssh_session session) { FILE *file = NULL; - char *host; - char *hostport; - const char *type; + char *host = NULL; + char *hostport = NULL; + const char *type = NULL; int match; int i = 0; - char *files[3]; + char *files[3] = {0}; - struct ssh_tokens_st *tokens; + struct ssh_tokens_st *tokens = NULL; int ret = SSH_SERVER_NOT_KNOWN; @@ -443,12 +443,13 @@ int ssh_is_server_known(ssh_session session) * @deprecated Please use ssh_session_export_known_hosts_entry() * @brief This function is deprecated. */ -char * ssh_dump_knownhost(ssh_session session) { +char *ssh_dump_knownhost(ssh_session session) +{ ssh_key server_pubkey = NULL; - char *host; - char *hostport; - char *buffer; - char *b64_key; + char *host = NULL; + char *hostport = NULL; + char *buffer = NULL; + char *b64_key = NULL; int rc; if (session->opts.host == NULL) { @@ -513,9 +514,9 @@ char * ssh_dump_knownhost(ssh_session session) { */ int ssh_write_knownhost(ssh_session session) { - FILE *file; + FILE *file = NULL; char *buffer = NULL; - char *dir; + char *dir = NULL; int rc; if (session->opts.knownhosts == NULL) { diff --git a/src/knownhosts.c b/src/knownhosts.c index c073b266..43eecf90 100644 --- a/src/knownhosts.c +++ b/src/knownhosts.c @@ -61,7 +61,7 @@ static int hash_hostname(const char *name, size_t *hash_size) { int rc; - HMACCTX mac_ctx; + HMACCTX mac_ctx = NULL; mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); if (mac_ctx == NULL) { @@ -81,8 +81,8 @@ static int hash_hostname(const char *name, static int match_hashed_hostname(const char *host, const char *hashed_host) { - char *hashed; - char *b64_hash; + char *hashed = NULL; + char *b64_hash = NULL; ssh_buffer salt = NULL; ssh_buffer hash = NULL; unsigned char hashed_buf[256] = {0}; @@ -171,7 +171,7 @@ static int known_hosts_read_line(FILE *fp, size_t *buf_len, size_t *lineno) { - while (fgets(buf, buf_size, fp) != NULL) { + while (fgets(buf, (int)buf_size, fp) != NULL) { size_t len; if (buf[0] == '\0') { continue; @@ -216,11 +216,22 @@ ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, return 0; } -/* This method reads the known_hosts file referenced by the path +/** + * @internal + * + * @brief Read entries from filename to provided list + * + * This method reads the known_hosts file referenced by the path * in filename argument, and entries matching the match argument * will be added to the list in entries argument. * If the entries list is NULL, it will allocate a new list. Caller * is responsible to free it even if an error occurs. + * + * @param match[in] The host name (with port) to match against + * @param filename[in] The known hosts file to parse + * @param entries[in,out] The list of entries to append matching ones + * @return `SSH_OK` on missing file or success parsing, + * `SSH_ERROR` on error */ static int ssh_known_hosts_read_entries(const char *match, const char *filename, @@ -229,10 +240,10 @@ static int ssh_known_hosts_read_entries(const char *match, char line[MAX_LINE_SIZE]; size_t lineno = 0; size_t len = 0; - FILE *fp; + FILE *fp = NULL; int rc; - fp = fopen(filename, "r"); + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); if (fp == NULL) { char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", @@ -288,7 +299,7 @@ static int ssh_known_hosts_read_entries(const char *match, for (it = ssh_list_get_iterator(*entries); it != NULL; it = it->next) { - struct ssh_knownhosts_entry *entry2; + struct ssh_knownhosts_entry *entry2 = NULL; int cmp; entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it); cmp = ssh_known_hosts_entries_compare(entry, entry2); @@ -312,8 +323,8 @@ static int ssh_known_hosts_read_entries(const char *match, static char *ssh_session_get_host_port(ssh_session session) { - char *host_port; - char *host; + char *host_port = NULL; + char *host = NULL; if (session->opts.host == NULL) { ssh_set_error(session, @@ -346,6 +357,33 @@ static char *ssh_session_get_host_port(ssh_session session) /** * @internal + * + * @brief Free known hosts entries list + * + * @param[in] entry_list The list of ssh_knownhosts_entry items + */ +static void ssh_knownhosts_entries_free(struct ssh_list *entry_list) +{ + struct ssh_iterator *it = NULL; + + if (entry_list == NULL) { + return; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); +} +/** + * @internal + * * @brief Check which host keys should be preferred for the session. * * This checks the known_hosts file to find out which algorithms should be @@ -376,25 +414,23 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) } } - host_port = ssh_session_get_host_port(session); - if (host_port == NULL) { - return NULL; - } - list = ssh_list_new(); if (list == NULL) { ssh_set_error_oom(session); - SAFE_FREE(host_port); return NULL; } + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + goto error; + } + rc = ssh_known_hosts_read_entries(host_port, session->opts.knownhosts, &entry_list); if (rc != 0) { - ssh_list_free(entry_list); - ssh_list_free(list); - return NULL; + SAFE_FREE(host_port); + goto error; } rc = ssh_known_hosts_read_entries(host_port, @@ -402,21 +438,16 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); - ssh_list_free(list); - return NULL; + goto error; } if (entry_list == NULL) { - ssh_list_free(list); - return NULL; + goto error; } count = ssh_list_count(entry_list); if (count == 0) { - ssh_list_free(list); - ssh_list_free(entry_list); - return NULL; + goto error; } for (it = ssh_list_get_iterator(entry_list); @@ -460,6 +491,7 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) return list; error: + ssh_knownhosts_entries_free(entry_list); ssh_list_free(list); return NULL; } @@ -511,6 +543,7 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ /** * @internal + * * @brief Get the host keys algorithms identifiers from the known_hosts files * * This expands the signatures types that can be generated from the keys types @@ -530,7 +563,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) char *host_port = NULL; size_t count; bool needcomma = false; - char *names; + char *names = NULL; int rc; @@ -555,7 +588,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -564,7 +597,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -638,7 +671,7 @@ int ssh_known_hosts_parse_line(const char *hostname, { struct ssh_knownhosts_entry *e = NULL; char *known_host = NULL; - char *p; + char *p = NULL; char *save_tok = NULL; enum ssh_keytypes_e key_type; int match = 0; @@ -805,7 +838,6 @@ int ssh_known_hosts_parse_line(const char *hostname, enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) { struct ssh_list *entry_list = NULL; - struct ssh_iterator *it = NULL; char *host_port = NULL; bool global_known_hosts_found = false; bool known_hosts_found = false; @@ -866,7 +898,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -877,7 +909,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -889,16 +921,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) return SSH_KNOWN_HOSTS_UNKNOWN; } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_OK; } @@ -1085,13 +1108,13 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, filename, &entry_list); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } it = ssh_list_get_iterator(entry_list); if (it == NULL) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } @@ -1121,16 +1144,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, } } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return found; } @@ -1202,6 +1216,8 @@ ssh_session_get_known_hosts_entry(ssh_session session, } /** + * @internal + * * @brief Get the known_hosts entry for the current connected session * from the given known_hosts file. * diff --git a/src/legacy.c b/src/legacy.c index 72ff7f36..5fad635f 100644 --- a/src/legacy.c +++ b/src/legacy.c @@ -48,7 +48,7 @@ int ssh_auth_list(ssh_session session) { int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey) { - ssh_key key; + ssh_key key = NULL; int rc; (void) type; /* unused */ @@ -70,7 +70,7 @@ int ssh_userauth_pubkey(ssh_session session, ssh_string publickey, ssh_private_key privatekey) { - ssh_key key; + ssh_key key = NULL; int rc; (void) publickey; /* unused */ @@ -83,17 +83,21 @@ 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; -#ifndef HAVE_LIBCRYPTO - key->rsa = privatekey->rsa_priv; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = privatekey->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) key->key = privatekey->key_priv; +#else + key->rsa = privatekey->rsa_priv; #endif /* HAVE_LIBCRYPTO */ rc = ssh_userauth_publickey(session, username, key); -#ifndef HAVE_LIBCRYPTO - key->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) key->key = NULL; +#else + key->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -372,10 +376,11 @@ void publickey_free(ssh_public_key key) { SAFE_FREE(key); } -ssh_public_key publickey_from_privatekey(ssh_private_key prv) { - struct ssh_public_key_struct *p; - ssh_key privkey; - ssh_key pubkey; +ssh_public_key publickey_from_privatekey(ssh_private_key prv) +{ + struct ssh_public_key_struct *p = NULL; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; int rc; privkey = ssh_key_new(); @@ -386,17 +391,21 @@ 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; -#ifndef HAVE_LIBCRYPTO - privkey->rsa = prv->rsa_priv; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = prv->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) privkey->key = prv->key_priv; +#else + privkey->rsa = prv->rsa_priv; #endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); -#ifndef HAVE_LIBCRYPTO - privkey->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) privkey->key = NULL; +#else + privkey->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(privkey); if (rc < 0) { @@ -415,8 +424,8 @@ ssh_private_key privatekey_from_file(ssh_session session, const char *passphrase) { ssh_auth_callback auth_fn = NULL; void *auth_data = NULL; - ssh_private_key privkey; - ssh_key key; + ssh_private_key privkey = NULL; + ssh_key key = NULL; int rc; (void) type; /* unused */ @@ -432,7 +441,7 @@ ssh_private_key privatekey_from_file(ssh_session session, auth_fn, auth_data, &key); - if (rc == SSH_ERROR) { + if (rc != SSH_OK) { return NULL; } @@ -443,14 +452,15 @@ ssh_private_key privatekey_from_file(ssh_session session, } privkey->type = key->type; -#ifndef HAVE_LIBCRYPTO - privkey->rsa_priv = key->rsa; - - key->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) privkey->key_priv = key->key; - key->key = NULL; +#else + privkey->rsa_priv = key->rsa; + key->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -483,7 +493,7 @@ void privatekey_free(ssh_private_key prv) { ssh_string publickey_from_file(ssh_session session, const char *filename, int *type) { - ssh_key key; + ssh_key key = NULL; ssh_string key_str = NULL; int rc; @@ -516,9 +526,10 @@ int ssh_type_from_name(const char *name) { return ssh_key_type_from_name(name); } -ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { - struct ssh_public_key_struct *pubkey; - ssh_key key; +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) +{ + struct ssh_public_key_struct *pubkey = NULL; + ssh_key key = NULL; int rc; (void) session; /* unused */ @@ -537,12 +548,15 @@ ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { pubkey->type = key->type; pubkey->type_c = key->type_c; -#ifndef HAVE_LIBCRYPTO - pubkey->rsa_pub = key->rsa; - key->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + pubkey->rsa_pub = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) pubkey->key_pub = key->key; key->key = NULL; +#else + pubkey->rsa_pub = key->rsa; + key->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -550,9 +564,10 @@ ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { return pubkey; } -ssh_string publickey_to_string(ssh_public_key pubkey) { - ssh_key key; - ssh_string key_blob; +ssh_string publickey_to_string(ssh_public_key pubkey) +{ + ssh_key key = NULL; + ssh_string key_blob = NULL; int rc; if (pubkey == NULL) { @@ -567,10 +582,12 @@ ssh_string publickey_to_string(ssh_public_key pubkey) { key->type = pubkey->type; key->type_c = pubkey->type_c; -#ifndef HAVE_LIBCRYPTO - key->rsa = pubkey->rsa_pub; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = pubkey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) key->key = pubkey->key_pub; +#else + key->rsa = pubkey->rsa_pub; #endif /* HAVE_LIBCRYPTO */ rc = ssh_pki_export_pubkey_blob(key, &key_blob); @@ -578,10 +595,12 @@ ssh_string publickey_to_string(ssh_public_key pubkey) { key_blob = NULL; } -#ifndef HAVE_LIBCRYPTO - key->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) key->key = NULL; +#else + key->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(key); @@ -593,13 +612,13 @@ int ssh_publickey_to_file(ssh_session session, ssh_string pubkey, int type) { - FILE *fp; - char *user; + FILE *fp = NULL; + char *user = NULL; char buffer[1024]; - char host[256]; - unsigned char *pubkey_64; + char *host = NULL; + unsigned char *pubkey_64 = NULL; size_t len; - int rc; + if(session==NULL) return SSH_ERROR; if(file==NULL || pubkey==NULL){ @@ -617,8 +636,8 @@ int ssh_publickey_to_file(ssh_session session, return SSH_ERROR; } - rc = gethostname(host, sizeof(host)); - if (rc < 0) { + host = ssh_get_local_hostname(); + if (host == NULL) { SAFE_FREE(user); SAFE_FREE(pubkey_64); return SSH_ERROR; @@ -632,6 +651,7 @@ int ssh_publickey_to_file(ssh_session session, SAFE_FREE(pubkey_64); SAFE_FREE(user); + SAFE_FREE(host); SSH_LOG(SSH_LOG_RARE, "Trying to write public key file: %s", file); SSH_LOG(SSH_LOG_PACKET, "public key file content: %s", buffer); @@ -664,9 +684,9 @@ int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, ssh_string *publickey, int *type) { - char *pubkey_file; + char *pubkey_file = NULL; size_t len; - ssh_string pubkey_string; + ssh_string pubkey_string = NULL; int pubkey_type; if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) { diff --git a/src/libcrypto.c b/src/libcrypto.c index 2e635e66..e2e5f939 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -40,19 +40,25 @@ #endif #ifdef HAVE_LIBCRYPTO - +#ifdef LIBRESSL_VERSION_NUMBER +#include +#endif +#include +#include #include #include -#include #if OPENSSL_VERSION_NUMBER < 0x30000000L #include #include #else -#include #include +#include +#include #endif /* OPENSSL_VERSION_NUMBER */ #include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) #include +#endif #include "libcrypto-compat.h" @@ -93,7 +99,37 @@ void ssh_reseed(void){ #endif } -#ifndef WITH_PKCS11_PROVIDER +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +static OSSL_PROVIDER *provider = NULL; +static bool pkcs11_provider_failed = false; + +int pki_load_pkcs11_provider(void) +{ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 1) { + /* the provider is already available. + * Loaded through a configuration file? */ + return SSH_OK; + } + + if (pkcs11_provider_failed) { + /* the loading failed previously -- do not retry */ + return SSH_ERROR; + } + + provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (provider != NULL) { + return SSH_OK; + } + + SSH_LOG(SSH_LOG_TRACE, + "Failed to load the pkcs11 provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + return SSH_ERROR; +} +#else static ENGINE *engine = NULL; ENGINE *pki_get_engine(void) @@ -125,61 +161,8 @@ ENGINE *pki_get_engine(void) } return engine; } -#endif /* WITH_PKCS11_PROVIDER */ - -#ifdef HAVE_OPENSSL_ECC -static const EVP_MD *nid_to_evpmd(int nid) -{ - switch (nid) { - case NID_X9_62_prime256v1: - return EVP_sha256(); - case NID_secp384r1: - return EVP_sha384(); - case NID_secp521r1: - return EVP_sha512(); - default: - return NULL; - } - - return NULL; -} - -void evp(int nid, unsigned char *digest, size_t 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, size_t 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 /* HAVE_OPENSSL_ECC */ +#endif /* defined(WITH_PKCS11_PROVIDER) */ +#endif /* defined(WITH_PKCS11_URI) */ #ifdef HAVE_OPENSSL_EVP_KDF_CTX #if OPENSSL_VERSION_NUMBER < 0x30000000L @@ -219,17 +202,29 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, uint8_t key_type, unsigned char *output, size_t requested_len) { - int rc = -1; + int ret = SSH_ERROR, rv; #if OPENSSL_VERSION_NUMBER < 0x30000000L EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); #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(); + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM_BLD *param_bld = NULL; OSSL_PARAM *params = NULL; - const char *md = sshkdf_digest_to_md(crypto->digest_type); + const char *md = NULL; + EVP_KDF *kdf = NULL; + + md = sshkdf_digest_to_md(crypto->digest_type); + if (md == NULL) { + return -1; + } + kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + if (kdf == NULL) { + return -1; + } + ctx = EVP_KDF_CTX_new(kdf); EVP_KDF_free(kdf); + + param_bld = OSSL_PARAM_BLD_new(); if (param_bld == NULL) { EVP_KDF_CTX_free(ctx); return -1; @@ -241,81 +236,86 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, } #if OPENSSL_VERSION_NUMBER < 0x30000000L - rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_MD, sshkdf_digest_to_md(crypto->digest_type)); - if (rc != 1) { + if (rv != 1) { goto out; } - rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY, key, key_len); - if (rc != 1) { + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY, key, key_len); + if (rv != 1) { goto out; } - rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, - crypto->secret_hash, crypto->digest_len); - if (rc != 1) { + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rv != 1) { goto out; } - rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, key_type); - if (rc != 1) { + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, key_type); + if (rv != 1) { goto out; } - rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, - crypto->session_id, crypto->session_id_len); - if (rc != 1) { + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rv != 1) { goto out; } - rc = EVP_KDF_derive(ctx, output, requested_len); - if (rc != 1) { + rv = EVP_KDF_derive(ctx, output, requested_len); + if (rv != 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; + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_DIGEST, + md, + strlen(md)); + if (rv != 1) { goto out; } - rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_KDF_PARAM_KEY, - key, key_len); - if (rc != 1) { - rc = -1; + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_KEY, + key, + key_len); + if (rv != 1) { goto out; } - rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_KDF_PARAM_SSHKDF_XCGHASH, crypto->secret_hash, crypto->digest_len); - if (rc != 1) { - rc = -1; + if (rv != 1) { goto out; } - rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + rv = 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; + if (rv != 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; + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char *)&key_type, + 1); + if (rv != 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; + rv = EVP_KDF_derive(ctx, output, requested_len, params); + if (rv != 1) { goto out; } #endif /* OPENSSL_VERSION_NUMBER */ + ret = SSH_OK; out: #if OPENSSL_VERSION_NUMBER >= 0x30000000L @@ -323,8 +323,8 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, OSSL_PARAM_free(params); #endif EVP_KDF_CTX_free(ctx); - if (rc < 0) { - return rc; + if (ret < 0) { + return ret; } return 0; } @@ -351,7 +351,7 @@ HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) return NULL; } - pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, len); + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, (int)len); if (pkey == NULL) { goto error; } @@ -408,7 +408,7 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) if (cipher->ctx == NULL) { cipher->ctx = EVP_CIPHER_CTX_new(); } else { - EVP_CIPHER_CTX_init(cipher->ctx); + EVP_CIPHER_CTX_reset(cipher->ctx); } switch(cipher->ciphertype){ @@ -439,12 +439,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) case SSH_3DES_CBC: cipher->cipher = EVP_des_ede3_cbc(); break; -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH case SSH_BLOWFISH_CBC: cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ case SSH_AEAD_CHACHA20_POLY1305: SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); break; @@ -574,12 +574,11 @@ static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { } } -static int -evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, - void *in, - uint8_t *out, - size_t len, - uint64_t seq) +static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) { (void)cipher; (void)seq; @@ -590,13 +589,12 @@ evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, return SSH_OK; } -static void -evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, - void *in, - void *out, - size_t len, - uint8_t *tag, - uint64_t seq) +static void evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) { size_t authlen, aadlen; uint8_t lastiv[1]; @@ -610,10 +608,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, authlen = cipher->tag_size; /* increment IV */ - rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, - EVP_CTRL_GCM_IV_GEN, - 1, - lastiv); + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); if (rc == 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return; @@ -637,7 +632,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, (unsigned char *)out + aadlen, &tmplen, (unsigned char *)in + aadlen, - (int)len - aadlen); + (int)(len - aadlen)); outlen = tmplen; if (rc != 1 || outlen != (int)len - aadlen) { SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); @@ -645,9 +640,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, } /* compute tag */ - rc = EVP_EncryptFinal(cipher->ctx, - NULL, - &tmplen); + rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen); if (rc < 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); return; @@ -655,7 +648,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_GET_TAG, - authlen, + (int)authlen, (unsigned char *)tag); if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_GET_TAG failed"); @@ -663,12 +656,11 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, } } -static int -evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, - void *complete_packet, - uint8_t *out, - size_t encrypted_size, - uint64_t seq) +static int evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) { size_t authlen, aadlen; uint8_t lastiv[1]; @@ -681,10 +673,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, authlen = cipher->tag_size; /* increment IV */ - rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, - EVP_CTRL_GCM_IV_GEN, - 1, - lastiv); + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); if (rc == 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); return SSH_ERROR; @@ -693,8 +682,9 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, /* set tag for authentication */ rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_SET_TAG, - authlen, - (unsigned char *)complete_packet + aadlen + encrypted_size); + (int)authlen, + (unsigned char *)complete_packet + aadlen + + encrypted_size); if (rc == 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed"); return SSH_ERROR; @@ -718,7 +708,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, (unsigned char *)out, &outlen, (unsigned char *)complete_packet + aadlen, - encrypted_size /* already subtracted aadlen */); + (int)encrypted_size /* already subtracted aadlen */); if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); return SSH_ERROR; @@ -733,11 +723,10 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, } /* verify tag */ - rc = EVP_DecryptFinal(cipher->ctx, - NULL, - &outlen); + rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen); if (rc < 0) { - SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication"); + SSH_LOG(SSH_LOG_TRACE, + "EVP_DecryptFinal failed: Failed authentication"); return SSH_ERROR; } @@ -751,7 +740,10 @@ struct chacha20_poly1305_keysched { EVP_CIPHER_CTX *main_evp; /* cipher handle used for encrypting the length field */ EVP_CIPHER_CTX *header_evp; -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if defined(LIBRESSL_VERSION_NUMBER) + /* LibreSSL Poly1305 context */ + poly1305_context poly_ctx; +#elif OPENSSL_VERSION_NUMBER < 0x30000000L /* mac handle used for authenticating the packets */ EVP_PKEY_CTX *pctx; /* Poly1305 key */ @@ -764,8 +756,7 @@ struct chacha20_poly1305_keysched { #endif /* OPENSSL_VERSION_NUMBER */ }; -static void -chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +static void chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) { struct chacha20_poly1305_keysched *ctx = NULL; @@ -776,10 +767,12 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) ctx = cipher->chacha20_schedule; EVP_CIPHER_CTX_free(ctx->main_evp); - ctx->main_evp = NULL; + ctx->main_evp = NULL; EVP_CIPHER_CTX_free(ctx->header_evp); ctx->header_evp = NULL; -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing to free */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L /* ctx->pctx is freed as part of MD context */ EVP_PKEY_free(ctx->key); ctx->key = NULL; @@ -793,10 +786,9 @@ chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) SAFE_FREE(cipher->chacha20_schedule); } -static int -chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, - void *key, - UNUSED_PARAM(void *IV)) +static int chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) { struct chacha20_poly1305_keysched *ctx = NULL; uint8_t *u8key = key; @@ -833,9 +825,9 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); goto out; } - ret = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, + rv = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, u8key + CHACHA20_KEYLEN, NULL); - if (ret != 1) { + if (rv != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); goto out; } @@ -843,14 +835,16 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, /* 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 +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing, poly1305_context is stack based */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L ctx->mctx = EVP_MD_CTX_new(); if (ctx->mctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed"); return SSH_ERROR; } #else - mac = EVP_MAC_fetch(NULL, "poly1305", NULL); + mac = EVP_MAC_fetch(NULL, SN_poly1305, NULL); if (mac == NULL) { SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed"); goto out; @@ -875,10 +869,9 @@ chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; -static int -chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, - uint64_t seq, - int do_encrypt) +static int chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) { struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; uint8_t seqbuf[16] = {0}; @@ -908,10 +901,9 @@ chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, return SSH_OK; } -static int -chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, - uint64_t seq, - int do_encrypt) +static int chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) { struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; uint8_t poly_key[CHACHA20_BLOCKSIZE]; @@ -937,12 +929,17 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); #endif /* DEBUG_CRYPTO */ - /* Set the Poly1305 key */ -#if OPENSSL_VERSION_NUMBER < 0x30000000L +/* LibreSSL path: use direct Poly1305 implementation */ +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_init(&ctx->poly_ctx, poly_key); +/* Set the Poly1305 key */ +#elif 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); + ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, + NULL, + poly_key, + POLY1305_KEYLEN); if (ctx->key == NULL) { SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed"); goto out; @@ -954,9 +951,12 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, } } else { /* Updating the key is easier but less obvious */ - rv = EVP_PKEY_CTX_ctrl(ctx->pctx, -1, EVP_PKEY_OP_SIGNCTX, - EVP_PKEY_CTRL_SET_MAC_KEY, - POLY1305_KEYLEN, (void *)poly_key); + rv = EVP_PKEY_CTX_ctrl(ctx->pctx, + -1, + EVP_PKEY_OP_SIGNCTX, + EVP_PKEY_CTRL_SET_MAC_KEY, + POLY1305_KEYLEN, + (void *)poly_key); if (rv <= 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed"); goto out; @@ -972,7 +972,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, ret = SSH_OK; out: - explicit_bzero(poly_key, sizeof(poly_key)); + ssh_burn(poly_key, sizeof(poly_key)); return ret; } @@ -1000,7 +1000,7 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, return SSH_ERROR; } - rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, len); + rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, (int)len); if (rv != 1 || outlen != sizeof(uint32_t)) { SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return SSH_ERROR; @@ -1019,20 +1019,21 @@ chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, return SSH_OK; } -static int -chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, - void *complete_packet, - uint8_t *out, - size_t encrypted_size, - uint64_t seq) +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) { struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; - uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + - encrypted_size; + uint8_t *mac = + (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; uint8_t tag[POLY1305_TAGLEN] = {0}; int ret = SSH_ERROR; int rv, cmp, len = 0; +#if !defined(LIBRESSL_VERSION_NUMBER) size_t taglen = POLY1305_TAGLEN; +#endif /* Prepare the Poly1305 key */ rv = chacha20_poly1305_packet_setup(cipher, seq, 0); @@ -1046,7 +1047,13 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, #endif /* DEBUG_CRYPTO */ /* Calculate MAC of received data */ -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_update(&ctx->poly_ctx, + complete_packet, + encrypted_size + sizeof(uint32_t)); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); + +#elif OPENSSL_VERSION_NUMBER < 0x30000000L rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, encrypted_size + sizeof(uint32_t)); if (rv != 1) { @@ -1060,7 +1067,8 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, goto out; } #else - rv = EVP_MAC_update(ctx->mctx, complete_packet, + 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"); @@ -1087,9 +1095,11 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, } /* Decrypt the message */ - rv = EVP_CipherUpdate(ctx->main_evp, out, &len, + rv = EVP_CipherUpdate(ctx->main_evp, + out, + &len, (uint8_t *)complete_packet + sizeof(uint32_t), - encrypted_size); + (int)encrypted_size); if (rv != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); goto out; @@ -1106,17 +1116,18 @@ chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, return ret; } -static void -chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, - void *in, - void *out, - size_t len, - uint8_t *tag, - uint64_t seq) +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) { struct ssh_packet_header *in_packet = in, *out_packet = out; struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; +#if !defined(LIBRESSL_VERSION_NUMBER) size_t taglen = POLY1305_TAGLEN; +#endif int ret, outlen = 0; /* Prepare the Poly1305 key */ @@ -1128,7 +1139,8 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, #ifdef DEBUG_CRYPTO ssh_log_hexdump("plaintext length", - (unsigned char *)&in_packet->length, sizeof(uint32_t)); + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); #endif /* DEBUG_CRYPTO */ /* step 2, encrypt length field */ ret = EVP_CipherUpdate(ctx->header_evp, @@ -1142,7 +1154,8 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, } #ifdef DEBUG_CRYPTO ssh_log_hexdump("encrypted length", - (unsigned char *)&out_packet->length, outlen); + (unsigned char *)&out_packet->length, + outlen); #endif /* DEBUG_CRYPTO */ ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen); if (ret != 1 || outlen != 0) { @@ -1156,14 +1169,20 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, out_packet->payload, &outlen, in_packet->payload, - len - sizeof(uint32_t)); + (int)(len - sizeof(uint32_t))); if (ret != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); return; } /* step 4, compute the MAC */ -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if defined(LIBRESSL_VERSION_NUMBER) + + CRYPTO_poly1305_update(&ctx->poly_ctx, + (const unsigned char *)out_packet, + len); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); +#elif OPENSSL_VERSION_NUMBER < 0x30000000L ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); if (ret <= 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); @@ -1175,7 +1194,7 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, return; } #else - ret = EVP_MAC_update(ctx->mctx, (void*)out_packet, len); + ret = EVP_MAC_update(ctx->mctx, (void *)out_packet, len); if (ret != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); return; @@ -1191,11 +1210,10 @@ chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, #endif /* HAVE_OPENSSL_EVP_CHACHA20 */ #ifdef WITH_INSECURE_NONE -static void -none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), - void *in, - void *out, - size_t len) +static void none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) { memcpy(out, in, len); } @@ -1205,164 +1223,164 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), * The table of supported ciphers */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER - { - .name = "blowfish-cbc", - .blocksize = 8, - .ciphertype = SSH_BLOWFISH_CBC, - .keysize = 128, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, -#endif /* WITH_BLOWFISH_CIPHER */ +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .ciphertype = SSH_BLOWFISH_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAVE_BLOWFISH */ #ifdef HAS_AES - { - .name = "aes128-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES128_CTR, - .keysize = 128, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes192-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES192_CTR, - .keysize = 192, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes256-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES256_CTR, - .keysize = 256, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes128-cbc", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES128_CBC, - .keysize = 128, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes192-cbc", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES192_CBC, - .keysize = 192, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes256-cbc", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES256_CBC, - .keysize = 256, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes128-gcm@openssh.com", - .blocksize = AES_BLOCK_SIZE, - .lenfield_blocksize = 4, /* not encrypted, but authenticated */ - .ciphertype = SSH_AEAD_AES128_GCM, - .keysize = 128, - .tag_size = AES_GCM_TAGLEN, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .aead_encrypt = evp_cipher_aead_encrypt, - .aead_decrypt_length = evp_cipher_aead_get_length, - .aead_decrypt = evp_cipher_aead_decrypt, - .cleanup = evp_cipher_cleanup - }, - { - .name = "aes256-gcm@openssh.com", - .blocksize = AES_BLOCK_SIZE, - .lenfield_blocksize = 4, /* not encrypted, but authenticated */ - .ciphertype = SSH_AEAD_AES256_GCM, - .keysize = 256, - .tag_size = AES_GCM_TAGLEN, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .aead_encrypt = evp_cipher_aead_encrypt, - .aead_decrypt_length = evp_cipher_aead_get_length, - .aead_decrypt = evp_cipher_aead_decrypt, - .cleanup = evp_cipher_cleanup - }, + { + .name = "aes128-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CTR, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CTR, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CTR, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CBC, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES128_GCM, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES256_GCM, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, #endif /* HAS_AES */ #ifdef HAS_DES - { - .name = "3des-cbc", - .blocksize = 8, - .ciphertype = SSH_3DES_CBC, - .keysize = 192, - .set_encrypt_key = evp_cipher_set_encrypt_key, - .set_decrypt_key = evp_cipher_set_decrypt_key, - .encrypt = evp_cipher_encrypt, - .decrypt = evp_cipher_decrypt, - .cleanup = evp_cipher_cleanup - }, + { + .name = "3des-cbc", + .blocksize = 8, + .ciphertype = SSH_3DES_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, #endif /* HAS_DES */ - { + { #ifdef HAVE_OPENSSL_EVP_CHACHA20 - .ciphertype = SSH_AEAD_CHACHA20_POLY1305, - .name = "chacha20-poly1305@openssh.com", - .blocksize = CHACHA20_BLOCKSIZE/8, - .lenfield_blocksize = 4, - .keylen = sizeof(struct chacha20_poly1305_keysched), - .keysize = 2 * CHACHA20_KEYLEN * 8, - .tag_size = POLY1305_TAGLEN, - .set_encrypt_key = chacha20_poly1305_set_key, - .set_decrypt_key = chacha20_poly1305_set_key, - .aead_encrypt = chacha20_poly1305_aead_encrypt, - .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, - .aead_decrypt = chacha20_poly1305_aead_decrypt, - .cleanup = chacha20_poly1305_cleanup + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = CHACHA20_BLOCKSIZE / 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup #else - .name = "chacha20-poly1305@openssh.com" + .name = "chacha20-poly1305@openssh.com" #endif /* HAVE_OPENSSL_EVP_CHACHA20 */ - }, + }, #ifdef WITH_INSECURE_NONE - { - .name = "none", - .blocksize = 8, - .keysize = 0, - .encrypt = none_crypt, - .decrypt = none_crypt, - }, + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, + }, #endif /* WITH_INSECURE_NONE */ - { - .name = NULL - } + { + .name = NULL, + }, }; struct ssh_cipher_struct *ssh_get_ciphertab(void) { - return ssh_ciphertab; + return ssh_ciphertab; } /** @@ -1378,19 +1396,19 @@ int ssh_crypto_init(void) if (libcrypto_initialized) { return SSH_OK; } - if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER){ - SSH_LOG(SSH_LOG_DEBUG, "libssh compiled with %s " - "headers, currently running with %s.", - OPENSSL_VERSION_TEXT, - OpenSSL_version(OpenSSL_version_num()) - ); + if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER) { + SSH_LOG(SSH_LOG_DEBUG, + "libssh compiled with %s " + "headers, currently running with %s.", + OPENSSL_VERSION_TEXT, + OpenSSL_version(OpenSSL_version_num())); } #ifdef CAN_DISABLE_AESNI /* * disable AES-NI when running within Valgrind, because they generate * too many "uninitialized memory access" false positives */ - if (RUNNING_ON_VALGRIND){ + if (RUNNING_ON_VALGRIND) { SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, disabling AES-NI"); /* Bit #57 denotes AES-NI instruction set extension */ OPENSSL_ia32cap &= ~(1LL << 57); @@ -1436,6 +1454,14 @@ void ssh_crypto_finalize(void) engine = NULL; } #endif +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) + if (provider != NULL) { + OSSL_PROVIDER_unload(provider); + provider = NULL; + } +#endif /* WITH_PKCS11_PROVIDER */ +#endif /* WITH_PKCS11_URI */ libcrypto_initialized = 0; } @@ -1445,7 +1471,8 @@ void ssh_crypto_finalize(void) * @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] 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 * @@ -1455,8 +1482,10 @@ void ssh_crypto_finalize(void) * * @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 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); @@ -1481,6 +1510,9 @@ int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, 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; @@ -1570,17 +1602,22 @@ evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key) int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote) { - return evp_dup_pkey("RSA", key, demote, new_key); + return evp_dup_pkey(SN_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); } + +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey(SN_ED25519, 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 pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) { ssh_string s = NULL; size_t len; @@ -1616,15 +1653,14 @@ pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) int pki_key_ecgroup_name_to_nid(const char *group) { - if (strcmp(group, NISTP256) == 0 || - strcmp(group, "secp256r1") == 0 || - strcmp(group, "prime256v1") == 0) { + if (strcmp(group, NISTP256) == 0 || strcmp(group, "secp256r1") == 0 || + strcmp(group, SN_X9_62_prime256v1) == 0) { return NID_X9_62_prime256v1; } else if (strcmp(group, NISTP384) == 0 || - strcmp(group, "secp384r1") == 0) { + strcmp(group, SN_secp384r1) == 0) { return NID_secp384r1; } else if (strcmp(group, NISTP521) == 0 || - strcmp(group, "secp521r1") == 0) { + strcmp(group, SN_secp521r1) == 0) { return NID_secp521r1; } return -1; diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 58f51095..8cce1a9f 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -69,59 +69,6 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { void ssh_reseed(void){ } -#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, size_t 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, size_t 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 - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, uint8_t key_type, unsigned char *output, @@ -169,7 +116,7 @@ int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { return 1; } -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH /* the wrapper functions for blowfish */ static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ if (cipher->key == NULL) { @@ -206,7 +153,7 @@ static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { int mode=GCRY_CIPHER_MODE_CBC; @@ -655,7 +602,7 @@ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, } out: - explicit_bzero(poly_key, sizeof(poly_key)); + ssh_burn(poly_key, sizeof(poly_key)); } static int chacha20_poly1305_aead_decrypt_length( @@ -767,7 +714,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, ret = SSH_OK; out: - explicit_bzero(poly_key, sizeof(poly_key)); + ssh_burn(poly_key, sizeof(poly_key)); return ret; } #endif /* HAVE_GCRYPT_CHACHA_POLY */ @@ -785,7 +732,7 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), /* the table of supported ciphers */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH { .name = "blowfish-cbc", .blocksize = 8, @@ -797,7 +744,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .encrypt = blowfish_encrypt, .decrypt = blowfish_decrypt }, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ { .name = "aes128-ctr", .blocksize = 16, @@ -1019,7 +966,8 @@ int ssh_crypto_init(void) gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P, 0)) { - gcry_control(GCRYCTL_INIT_SECMEM, 4096); + gcry_control(GCRYCTL_USE_SECURE_RNDPOOL); + gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index bc12a820..d52ec3df 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -51,80 +51,6 @@ void ssh_reseed(void) mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); } -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, size_t 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, size_t len) -{ - mbedtls_md_update(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - *mdlen = mbedtls_md_get_size(ctx->MBEDTLS_PRIVATE(md_info)); - mbedtls_md_finish(ctx, md); - mbedtls_md_free(ctx); - SAFE_FREE(ctx); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, uint8_t key_type, unsigned char *output, @@ -193,7 +119,7 @@ int hmac_update(HMACCTX c, const void *data, size_t len) int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { int rc; - *len = (unsigned int)mbedtls_md_get_size(c->md_info); + *len = (unsigned int)mbedtls_md_get_size(c->MBEDTLS_PRIVATE(md_info)); rc = !mbedtls_md_hmac_finish(c, hashmacbuf); mbedtls_md_free(c); SAFE_FREE(c); @@ -207,7 +133,7 @@ cipher_init(struct ssh_cipher_struct *cipher, void *IV) { const mbedtls_cipher_info_t *cipher_info = NULL; - mbedtls_cipher_context_t *ctx; + mbedtls_cipher_context_t *ctx = NULL; size_t key_bitlen = 0; size_t iv_size = 0; int rc; @@ -287,7 +213,7 @@ cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, 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); @@ -785,7 +711,7 @@ chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, ret = SSH_OK; out: - explicit_bzero(poly_key, sizeof(poly_key)); + ssh_burn(poly_key, sizeof(poly_key)); return ret; } @@ -972,7 +898,7 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), #endif /* WITH_INSECURE_NONE */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH { .name = "blowfish-cbc", .blocksize = 8, @@ -984,7 +910,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = cipher_decrypt_cbc, .cleanup = cipher_cleanup }, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ { .name = "aes128-ctr", .blocksize = 16, diff --git a/src/libssh.map b/src/libssh.map index f81d8abe..3f16299e 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -1,4 +1,4 @@ -# This map file was updated with abimap-0.3.2 +# This map file was updated with abimap-0.4.0 LIBSSH_4_5_0 # Released { @@ -459,10 +459,51 @@ LIBSSH_4_9_0 # Released ssh_vlog; } LIBSSH_4_8_1; -LIBSSH_AFTER_4_9_0 +LIBSSH_4_10_0 # Released { global: + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; sftp_channel_default_data_callback; sftp_channel_default_subsystem_request; + sftp_expand_path; + sftp_hardlink; + sftp_home_directory; + sftp_limits; + sftp_limits_free; + sftp_lsetstat; + ssh_channel_get_exit_state; + ssh_channel_request_pty_size_modes; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; + ssh_request_no_more_sessions; } LIBSSH_4_9_0; +LIBSSH_4_11_0 # Released +{ + global: + sftp_get_users_groups_by_id; + sftp_name_id_map_free; + sftp_name_id_map_new; + ssh_get_supported_methods; + ssh_key_get_sk_application; + ssh_key_get_sk_flags; + ssh_key_get_sk_user_id; + ssh_pki_ctx_free; + ssh_pki_ctx_get_sk_attestation_buffer; + ssh_pki_ctx_new; + ssh_pki_ctx_options_set; + ssh_pki_ctx_set_sk_pin_callback; + ssh_pki_ctx_sk_callbacks_option_set; + ssh_pki_ctx_sk_callbacks_options_clear; + ssh_pki_generate_key; + ssh_sk_resident_keys_load; + ssh_string_cmp; + ssh_string_from_data; + sshsig_sign; + sshsig_verify; +} LIBSSH_4_10_0; + diff --git a/src/log.c b/src/log.c index 5bae18b8..8bc8ccab 100644 --- a/src/log.c +++ b/src/log.c @@ -44,7 +44,7 @@ static LIBSSH_THREAD int ssh_log_level; static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; -static LIBSSH_THREAD void *ssh_log_userdata; +static LIBSSH_THREAD void *ssh_log_userdata = NULL; /** * @defgroup libssh_log The SSH logging functions @@ -59,22 +59,22 @@ static int current_timestring(int hires, char *buf, size_t len) { char tbuf[64]; struct timeval tv; - struct tm *tm; + struct tm tm, *tm_ptr = NULL; time_t t; gettimeofday(&tv, NULL); t = (time_t) tv.tv_sec; - tm = localtime(&t); - if (tm == NULL) { + tm_ptr = localtime_r(&t, &tm); + if (tm_ptr == NULL) { return -1; } if (hires) { - strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); snprintf(buf, len, "%s.%06ld", tbuf, (long)tv.tv_usec); } else { - strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); snprintf(buf, len, "%s", tbuf); } @@ -221,6 +221,12 @@ int ssh_set_log_callback(ssh_logging_callback cb) { return SSH_OK; } +void +_ssh_reset_log_cb(void) +{ + ssh_log_cb = NULL; +} + ssh_logging_callback ssh_get_log_callback(void) { return ssh_log_cb; } diff --git a/src/match.c b/src/match.c index 3e58f733..40b9c0f8 100644 --- a/src/match.c +++ b/src/match.c @@ -40,88 +40,83 @@ #include #include #include +#ifndef _WIN32 +#include +#include +#include +#endif -#include "libssh/priv.h" +/* for systems without IPv6 support matching should still work */ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif -#define MAX_MATCH_RECURSION 16 +#include "libssh/priv.h" -/* - * Returns true if the given string matches the pattern (which may contain ? - * and * as wildcards), and zero if it does not match. +/** + * @brief Compare a string with a pattern containing wildcards `*` and `?` + * + * This function is an iterative replacement for the previously recursive + * implementation to avoid exponential complexity (DoS) with specific patterns. + * + * @param[in] s The string to match. + * @param[in] pattern The pattern to match against. + * + * @return 1 if the pattern matches, 0 otherwise. */ -static int match_pattern(const char *s, const char *pattern, size_t limit) +static int match_pattern(const char *s, const char *pattern) { - bool had_asterisk = false; + const char *s_star = NULL; /* Position in s when last `*` was met */ + const char *p_star = NULL; /* Position in pattern after last `*` */ - if (s == NULL || pattern == NULL || limit <= 0) { + if (s == NULL || pattern == NULL) { return 0; } - for (;;) { - /* If at end of pattern, accept if also at end of string. */ - if (*pattern == '\0') { - return (*s == '\0'); - } - - /* Skip all the asterisks and adjacent question marks */ - while (*pattern == '*' || (had_asterisk && *pattern == '?')) { - if (*pattern == '*') { - had_asterisk = true; - } + while (*s) { + /* Case 1: Exact match or '?' wildcard */ + if (*pattern == *s || *pattern == '?') { + s++; pattern++; + continue; } - 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. + /* Case 2: '*' wildcard */ + if (*pattern == '*') { + /* Record the position of the star and the current string position. + * We optimistically assume * matches 0 characters first. */ - for (; *s; s++) { - if (match_pattern(s, pattern, limit - 1)) { - return 1; - } - } - /* Failed. */ - return 0; - } - /* - * There must be at least one more character in the string. - * If we are at the end, fail. - */ - if (!*s) { - return 0; + p_star = ++pattern; + s_star = s; + continue; } - /* Check if the next character of the string is acceptable. */ - if (*pattern != '?' && *pattern != *s) { - return 0; + /* Case 3: Mismatch */ + if (p_star) { + /* If we have seen a star previously, backtrack. + * We restore the pattern to just after the star, + * but advance the string position (consume one more char for the + * star). + * No need to backtrack to previous stars as any match of the last + * star could be eaten the same way by the previous star. + */ + pattern = p_star; + s = ++s_star; + continue; } - /* Move to the next character, both in string and in pattern. */ - s++; + /* Case 4: Mismatch and no star to backtrack to */ + return 0; + } + + /* Handle trailing stars in the pattern + * (e.g., pattern "abc*" matching "abc") */ + while (*pattern == '*') { pattern++; } - /* NOTREACHED */ - return 0; + /* If we reached the end of the pattern, it's a match */ + return (*pattern == '\0'); } /* @@ -172,7 +167,7 @@ int match_pattern_list(const char *string, const char *pattern, sub[subi] = '\0'; /* Try to match the subpattern against the string. */ - if (match_pattern(string, sub, MAX_MATCH_RECURSION)) { + if (match_pattern(string, sub)) { if (negated) { return -1; /* Negative */ } else { @@ -195,6 +190,426 @@ int match_pattern_list(const char *string, const char *pattern, * Returns -1 if negation matches, 1 if there is a positive match, 0 if there * is no match at all. */ -int match_hostname(const char *host, const char *pattern, unsigned int len) { - return match_pattern_list(host, pattern, len, 1); +int +match_hostname(const char *host, const char *pattern, size_t len) +{ + return match_pattern_list(host, pattern, len, 1); +} + +#ifndef _WIN32 +/** + * @brief Tries to match the host IPv6 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_6(struct in6_addr *host_addr, + struct in6_addr *net_addr, + unsigned int bits) +{ + const uint8_t *a = host_addr->s6_addr; + const uint8_t *b = net_addr->s6_addr; + + unsigned int byte_whole, bits_left; + + /* The number of a complete byte covered by the prefix */ + byte_whole = bits / 8; + + /* + * The number of bits remaining in the incomplete (last) byte + * covered by the prefix + */ + bits_left = bits % 8; + + if (byte_whole) { + if (memcmp(a, b, byte_whole) != 0) { + return 0; + } + } + + if (bits_left) { + if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) { + return 0; + } + } + + return 1; +} + +/** + * @brief Tries to match the host IPv4 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_4(struct in_addr *host_addr, + struct in_addr *net_addr, + unsigned int bits) +{ + if (bits == 0) { + /* C99 6.5.7 (3): u32 << 32 is undefined behaviour */ + return 1; + } + + return !((host_addr->s_addr ^ net_addr->s_addr) & + htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu)); +} + +/** + * @brief Checks if the mask length is valid according to the address family + * (IPv4 or IPv6). + * + * @param[in] family The address family (e.g. AF_INET or AF_INET6) + * + * @param[in] mask The subnet mask (prefix) + * + * @return true if the mask length does not exceed the maximum valid length + * according to the address family (IPv4 or IPv6). + * @return false if the mask length exceeds the maximum valid length + * or there is no match with IPv4 or IPv6 address family. + */ +static bool +masklen_valid(int family, unsigned int mask) +{ + switch (family) { + case AF_INET: + return mask <= 32; + case AF_INET6: + return mask <= 128; + default: + return false; + } +} + +/** + * @brief Extracts address family given a network address. + * + * @param[in] address The network address. + * + * @return The value of the address family if no errors. + * @return -1 in case of errors. + */ +static int +get_address_family(const char *address) +{ + struct addrinfo hints, *ai = NULL; + int rc = -1, rv; + + ZERO_STRUCT(hints); + if (address == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad arguments"); + goto out; + } + + hints.ai_flags = AI_NUMERICHOST; + rv = getaddrinfo(address, NULL, &hints, &ai); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address information - getaddrinfo() failed: %s", + gai_strerror(rv)); + goto out; + } + + rc = ai->ai_family; + freeaddrinfo(ai); + +out: + return rc; +} + +/** + * @brief Tries to match the host address against a CIDR list provided + * by the user. If the host address family is unknown, it can be derived by + * passing -1 as sa_family argument. + * + * It can be also used to validate a CIDR list when the passed address is NULL + * and sa_family is -1. + * + * @param[in] address The host address to verify (NULL to validate CIDR list). + * + * @param[in] addrlist The CIDR list against which the match is being verified. + * The CIDR list can contain both IPv4 and IPv6 addresses + * and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param[in] sa_family The socket address family (e.g. AF_INET or AF_INET6, + * -1 to validate CIDR list or unknown address family). + * + * @usage To validate CIDR list: match_cidr_address_list(NULL, addrlist, -1). + * @usage To verify a match with unknown address family: + * match_cidr_address_list(address, addrlist, -1). + * @return 1 only on positive match. + * @return 0 on negative match or valid CIDR list. + * @return -1 on errors or invalid CIDR list. + */ +int +match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family) +{ + char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL; + char addr_buffer[64], addr[NI_MAXHOST]; + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + unsigned long mask_len; + size_t addr_len, tmp_len; + int rc = 0, r, ai_family; + + ZERO_STRUCT(try_addr); + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr); + ZERO_STRUCT(match_addr6); + + if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid argument: sa_family %d is not valid", + sa_family); + return -1; + } + + if (address != NULL) { + strncpy(addr, address, NI_MAXHOST - 1); + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(addr, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* + * If sa_family is set to -1 and address is not NULL then + * the socket address family should be derived + */ + if (sa_family == -1) { + r = get_address_family(addr); + if (r == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive address family for address " + "\"%.100s\"", + addr); + return -1; + } + sa_family = r; + } + + /* + * Translate host address from dot notation to binary network format + * according to family type, + * i.e. IPv4 (store in in_addr) or IPv6 (store in in6_addr) + */ + if (sa_family == AF_INET) { + if (inet_pton(AF_INET, addr, &try_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr); + return -1; + } + } else if (sa_family == AF_INET6) { + if (inet_pton(AF_INET6, addr, &try_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + sa_family, + addr); + return -1; + } + } + + b = list = strdup(addrlist); + if (b == NULL) { + return -1; + } + + while ((cp = strsep(&list, ",")) != NULL) { + if (*cp == '\0') { + SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b); + rc = -1; + break; + } + + /* + * Stop junk from reaching address translation. +3 for the "/prefix". + * INET6_ADDRSTRLEN is 46 and includes space for '\0' terminator. The + * maximum IPv6 address printable is the one that carries IPv4 too. + * E.g. ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255 is 46 chars + * long ('\0' included) and the maximum prefix length possible is 96. + * This explains why +3. All the other IPv6 addresses with maximum /127 + * prefix length (39 + 4) are covered just by INET6_ADDRSTRLEN itself + */ + addr_len = strlen(cp); + if (addr_len > INET6_ADDRSTRLEN + 3) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)", + cp, + addr_len, + INET6_ADDRSTRLEN + 3); + rc = -1; + break; + } + +#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/" + tmp_len = strspn(cp, VALID_CIDR_CHARS); + if (tmp_len != addr_len) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" contains invalid characters " + "-> \"%c\" is an invalid character", + cp, + cp[tmp_len]); + rc = -1; + break; + } +#undef VALID_CIDR_CHARS + + strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1); + sp = strchr(addr_buffer, '/'); + if (sp != NULL) { + *sp = '\0'; + sp++; + mask_len = strtoul(sp, &cp, 10); + if (*sp < '0' || *sp > '9' || *cp != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp); + rc = -1; + break; + } + if (mask_len > 128) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid prefix: %lu exceeds the maximum allowed " + "(>128)", + mask_len); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Missing prefix length for list entry \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + ai_family = get_address_family(addr_buffer); + if (ai_family == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address family for \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + if (ai_family == AF_INET) { + if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else if (ai_family == AF_INET6) { + if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + ai_family, + addr_buffer); + rc = -1; + break; + } + + if (masklen_valid(ai_family, mask_len) != true) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid mask length %lu for list entry \"%.100s\"", + mask_len, + addr_buffer); + rc = -1; + break; + } + + /* Verify match between host address and network address*/ + if (((ai_family == AF_INET && sa_family == AF_INET) && + cidr_match_4(&try_addr, &match_addr, mask_len)) || + ((ai_family == AF_INET6 && sa_family == AF_INET6) && + cidr_match_6(&try_addr6, &match_addr6, mask_len))) { + rc = 1; + break; + } + } + SAFE_FREE(b); + + return rc; +} +#endif /* _WIN32 */ + +/** + * @brief Tries to match an object against a comma separated group of objects + * + * The characters '*' and '?' are NOT considered wildcards and an object in the + * group preceded by a ! does NOT indicate negation. The characters '*', '?' + * and '!' are treated normally like other characters, only ',' (comma) is + * treated specially and is considered as a delimiter that separates objects in + * the group. + * + * @param[in] group Group of objects (comma separated) to match against. + * + * @param[in] object Object to match. + * + * @returns 1 if there is a match, 0 if there is no match at all. + */ +int match_group(const char *group, const char *object) +{ + const char *a = NULL; + const char *z = NULL; + + if (group == NULL || object == NULL) { + return 0; + } + + z = group; + do { + a = strchr(z, ','); + if (a == NULL) { + if (strcmp(z, object) == 0) { + return 1; + } + return 0; + } else { + if (strncmp(z, object, a - z) == 0) { + return 1; + } + } + z = a + 1; + } while (1); + + /* not reached */ + return 0; } diff --git a/src/mbedcrypto-compat.h b/src/mbedcrypto-compat.h index 705294a7..f028854f 100644 --- a/src/mbedcrypto-compat.h +++ b/src/mbedcrypto-compat.h @@ -5,13 +5,17 @@ * v3 defines the version inside build_info.h so if it isn't defined * in version.h we should have v3 */ -#include #include -#ifdef MBEDTLS_VERSION_MAJOR +#include + +#ifndef MBEDTLS_VERSION_MAJOR +#include +#endif /* MBEDTLS_VERSION_MAJOR */ + #if MBEDTLS_VERSION_MAJOR < 3 -static inline size_t mbedtls_cipher_info_get_key_bitlen( - const mbedtls_cipher_info_t *info) +static inline size_t +mbedtls_cipher_info_get_key_bitlen(const mbedtls_cipher_info_t *info) { if (info == NULL) { return 0; @@ -19,8 +23,8 @@ static inline size_t mbedtls_cipher_info_get_key_bitlen( return info->key_bitlen; } -static inline size_t mbedtls_cipher_info_get_iv_size( - const mbedtls_cipher_info_t *info) +static inline size_t +mbedtls_cipher_info_get_iv_size(const mbedtls_cipher_info_t *info) { if (info == NULL) { return 0; @@ -29,11 +33,24 @@ static inline size_t mbedtls_cipher_info_get_iv_size( } #define MBEDTLS_PRIVATE(X) X + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) X +#define MBEDTLS_ECDH_PARAMS(X) X +typedef mbedtls_ecdh_context mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + +#else /* MBEDTLS_VERSION_MAJOR < 3 */ + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) MBEDTLS_PRIVATE(X) +#define MBEDTLS_ECDH_PARAMS(X) X.MBEDTLS_PRIVATE(ctx).MBEDTLS_PRIVATE(mbed_ecdh) +typedef mbedtls_ecdh_context_mbed mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + #endif /* MBEDTLS_VERSION_MAJOR < 3 */ -#else /* MBEDTLS_VERSION_MAJOR */ -#include -#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/md_crypto.c b/src/md_crypto.c index f5104f04..e7b1cf7a 100644 --- a/src/md_crypto.c +++ b/src/md_crypto.c @@ -25,6 +25,7 @@ #include "libssh/crypto.h" #include "libssh/wrapper.h" +#include #include #include #include @@ -46,28 +47,49 @@ sha1_init(void) } void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha1_update(SHACTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1_final(unsigned char *md, SHACTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1(const unsigned char *digest, size_t len, unsigned char *hash) { SHACTX c = sha1_init(); - if (c != NULL) { - sha1_update(c, digest, len); - sha1_final(hash, c); + 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 @@ -87,28 +109,49 @@ sha256_init(void) } void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha256_update(SHA256CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256_final(unsigned char *md, SHA256CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256(const unsigned char *digest, size_t len, unsigned char *hash) { SHA256CTX c = sha256_init(); - if (c != NULL) { - sha256_update(c, digest, len); - sha256_final(hash, c); + 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 @@ -128,28 +171,49 @@ sha384_init(void) } void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha384_update(SHA384CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384_final(unsigned char *md, SHA384CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384(const unsigned char *digest, size_t len, unsigned char *hash) { SHA384CTX c = sha384_init(); - if (c != NULL) { - sha384_update(c, digest, len); - sha384_final(hash, c); + 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 @@ -169,28 +233,49 @@ sha512_init(void) } void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha512_update(SHA512CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512_final(unsigned char *md, SHA512CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512(const unsigned char *digest, size_t len, unsigned char *hash) { SHA512CTX c = sha512_init(); - if (c != NULL) { - sha512_update(c, digest, len); - sha512_final(hash, c); + 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 @@ -210,16 +295,79 @@ md5_init(void) } void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int md5_update(MD5CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +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; +} + +/** +* @ brief One-shot MD5. Not intended for use in security-relevant contexts. +*/ +int +md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc, ret = SSH_ERROR; + unsigned int mdlen = 0; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md5 = NULL; +#endif + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + goto out; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + md5 = EVP_MD_fetch(NULL, "MD5", "provider=default,-fips"); + if (md5 == NULL) { + goto out; + } + rc = EVP_DigestInit(c, md5); +#else + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); +#endif + if (rc == 0) { + goto out; + } + + rc = EVP_DigestUpdate(c, digest, len); + if (rc != 1) { + goto out; + } + + rc = EVP_DigestFinal(c, hash, &mdlen); + if (rc != 1) { + goto out; + } + + ret = SSH_OK; - EVP_DigestFinal(c, md, &mdlen); +out: EVP_MD_CTX_free(c); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(md5); +#endif + return ret; } diff --git a/src/md_gcrypt.c b/src/md_gcrypt.c index 1f0a71f3..c8830773 100644 --- a/src/md_gcrypt.c +++ b/src/md_gcrypt.c @@ -36,24 +36,40 @@ sha1_init(void) return ctx; } -void +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); - memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); + 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; } -void +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 @@ -66,23 +82,39 @@ sha256_init(void) } 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; } -void +int sha256_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); + 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; } -void +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 @@ -95,23 +127,39 @@ sha384_init(void) } 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; } -void +int sha384_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); + 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; } -void +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 @@ -124,23 +172,39 @@ sha512_init(void) } 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; } -void +int sha512_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); + 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; } -void +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 @@ -153,15 +217,36 @@ md5_init(void) } 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; } -void +int md5_final(unsigned char *md, MD5CTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); + 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; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_MD5, hash, digest, len); + return SSH_OK; } diff --git a/src/md_mbedcrypto.c b/src/md_mbedcrypto.c index 227e20ab..445f644d 100644 --- a/src/md_mbedcrypto.c +++ b/src/md_mbedcrypto.c @@ -64,27 +64,48 @@ sha1_init(void) } void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha1_update(SHACTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1_final(unsigned char *md, SHACTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +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); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + 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 @@ -122,27 +143,48 @@ sha256_init(void) } void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha256_update(SHA256CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256_final(unsigned char *md, SHA256CTX c) { - mbedtls_md_finish(c, md); + int rc = mbedtls_md_finish(c, md); mbedtls_md_free(c); SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +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) { - mbedtls_md(md_info, digest, len, hash); + 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 @@ -180,27 +222,48 @@ sha384_init(void) } void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha384_update(SHA384CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384_final(unsigned char *md, SHA384CTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +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); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + 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 @@ -237,27 +300,48 @@ sha512_init(void) } void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha512_update(SHA512CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512_final(unsigned char *md, SHA512CTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +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); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + 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 @@ -294,15 +378,78 @@ md5_init(void) } void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int md5_update(MD5CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int md5_final(unsigned char *md, MD5CTX c) { - mbedtls_md_finish(c, md); + int rc = mbedtls_md_finish(c, md); mbedtls_md_free(c); SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + 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 SSH_ERROR; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return SSH_ERROR; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_update(ctx, digest, len); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_finish(ctx, hash); + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; } diff --git a/src/messages.c b/src/messages.c index be9462ac..824b2772 100644 --- a/src/messages.c +++ b/src/messages.c @@ -40,8 +40,8 @@ #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/pki.h" -#include "libssh/dh.h" #include "libssh/messages.h" +#include "libssh/socket.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/gssapi.h" @@ -97,6 +97,41 @@ static int ssh_message_reply_default(ssh_message msg) { #endif +static int ssh_send_disconnect(ssh_session session) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye"); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + ssh_session_socket_close(session); + } + + return rc; +} + #ifdef WITH_SERVER static int ssh_execute_server_request(ssh_session session, ssh_message msg) @@ -149,6 +184,19 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) ssh_message_reply_default(msg); } + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_INTERACTIVE && + ssh_callbacks_exists(session->server_callbacks, auth_kbdint_function)) { + rc = session->server_callbacks->auth_kbdint_function(msg, + session, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else if (rc == SSH_AUTH_INFO) { + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } return SSH_OK; } break; @@ -169,8 +217,35 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) ssh_message_reply_default(msg); } + return SSH_OK; +#define CB channel_open_request_direct_tcpip_function + } else if (msg->channel_request_open.type == SSH_CHANNEL_DIRECT_TCPIP && + ssh_callbacks_exists(session->server_callbacks, CB)) { + struct ssh_channel_request_open *rq = &msg->channel_request_open; + channel = session->server_callbacks->CB(session, + rq->destination, + rq->destination_port, + rq->originator, + rq->originator_port, + session->server_callbacks->userdata); +#undef CB + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel( + msg, + channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; } + break; case SSH_REQUEST_CHANNEL: channel = msg->channel_request.channel; @@ -303,7 +378,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) if (rc == 0) { ssh_message_reply_default(msg); } else { - ssh_disconnect(session); + ssh_send_disconnect(session); } return SSH_OK; @@ -484,30 +559,36 @@ static void ssh_message_queue(ssh_session session, ssh_message message) * * @returns The head message or NULL if it doesn't exist. */ -ssh_message ssh_message_pop_head(ssh_session session){ - ssh_message msg=NULL; - struct ssh_iterator *i; - if(session->ssh_message_list == NULL) - return NULL; - i=ssh_list_get_iterator(session->ssh_message_list); - if(i != NULL){ - msg=ssh_iterator_value(ssh_message,i); - ssh_list_remove(session->ssh_message_list,i); - } - return msg; +ssh_message ssh_message_pop_head(ssh_session session) +{ + ssh_message msg = NULL; + struct ssh_iterator *i = NULL; + + if (session->ssh_message_list == NULL) + return NULL; + + i = ssh_list_get_iterator(session->ssh_message_list); + if (i != NULL) { + msg = ssh_iterator_value(ssh_message, i); + ssh_list_remove(session->ssh_message_list, i); + } + return msg; } /* Returns 1 if there is a message available */ -static int ssh_message_termination(void *s){ - ssh_session session = s; - struct ssh_iterator *it; - if(session->session_state == SSH_SESSION_STATE_ERROR) - return 1; - it = ssh_list_get_iterator(session->ssh_message_list); - if(!it) - return 0; - else - return 1; +static int ssh_message_termination(void *s) +{ + ssh_session session = s; + struct ssh_iterator *it = NULL; + + if (session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + + it = ssh_list_get_iterator(session->ssh_message_list); + if (!it) + return 0; + else + return 1; } /** * @brief Retrieve a SSH message from a SSH session. @@ -602,11 +683,12 @@ void ssh_message_free(ssh_message msg){ 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)); - SAFE_FREE(msg->auth_request.password); + ssh_burn(msg->auth_request.password, + strlen(msg->auth_request.password)); + SAFE_FREE(msg->auth_request.password); } ssh_key_free(msg->auth_request.pubkey); + ssh_key_free(msg->auth_request.server_pubkey); break; case SSH_REQUEST_CHANNEL_OPEN: SAFE_FREE(msg->channel_request_open.originator); @@ -698,11 +780,12 @@ SSH_PACKET_CALLBACK(ssh_packet_service_request) static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, ssh_message msg, const char *service, - ssh_string algo) + ssh_string algo, + const char *method) { struct ssh_crypto_struct *crypto = NULL; - ssh_buffer buffer; - ssh_string str=NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL; int rc; crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); @@ -722,15 +805,16 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, rc = ssh_buffer_pack(buffer, "dPbsssbsS", - crypto->session_id_len, /* session ID string */ - crypto->session_id_len, crypto->session_id, + (uint32_t)crypto->session_id_len, /* session ID string */ + crypto->session_id_len, + crypto->session_id, SSH2_MSG_USERAUTH_REQUEST, /* type */ msg->auth_request.username, service, - "publickey", /* method */ + method, 1, /* has to be signed (true) */ ssh_string_get_char(algo), /* pubkey algorithm */ - str); /* public key as a blob */ + str); /* public key as a blob */ SSH_STRING_FREE(str); if (rc != SSH_OK) { @@ -739,6 +823,25 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, return NULL; } + /* Add server public key for hostbound extension */ + if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0 && + msg->auth_request.server_pubkey != NULL) { + + rc = ssh_pki_export_pubkey_blob(msg->auth_request.server_pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, str); + SSH_STRING_FREE(str); + if (rc < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + } + return buffer; } @@ -748,263 +851,403 @@ static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a * SSH Message */ -SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ - ssh_message msg = NULL; - ssh_signature sig = NULL; - char *service = NULL; - char *method = NULL; - int cmp; - int rc; +SSH_PACKET_CALLBACK(ssh_packet_userauth_request) +{ + ssh_message msg = NULL; + ssh_signature sig = NULL; + char *service = NULL; + char *method = NULL; + int cmp; + int rc; - (void)user; - (void)type; + (void)user; + (void)type; - msg = ssh_message_new(session); - if (msg == NULL) { - ssh_set_error_oom(session); - goto error; - } - msg->type = SSH_REQUEST_AUTH; - rc = ssh_buffer_unpack(packet, - "sss", - &msg->auth_request.username, - &service, - &method); + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_AUTH; + rc = ssh_buffer_unpack(packet, + "sss", + &msg->auth_request.username, + &service, + &method); - if (rc != SSH_OK) { - goto error; - } + if (rc != SSH_OK) { + goto error; + } - SSH_LOG(SSH_LOG_PACKET, - "Auth request for service %s, method %s for user '%s'", - service, method, - msg->auth_request.username); - - cmp = strcmp(service, "ssh-connection"); - if (cmp != 0) { - SSH_LOG(SSH_LOG_TRACE, - "Invalid service request: %s", - service); - goto end; - } + SSH_LOG(SSH_LOG_PACKET, + "Auth request for service %s, method %s for user '%s'", + service, + method, + msg->auth_request.username); + + cmp = strcmp(service, "ssh-connection"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid service request: %s", service); + goto end; + } - if (strcmp(method, "none") == 0) { - msg->auth_request.method = SSH_AUTH_METHOD_NONE; - goto end; - } + if (strcmp(method, "none") == 0) { + msg->auth_request.method = SSH_AUTH_METHOD_NONE; + goto end; + } - if (strcmp(method, "password") == 0) { - uint8_t tmp; + if (strcmp(method, "password") == 0) { + uint8_t tmp; - msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; - rc = ssh_buffer_unpack(packet, "bs", &tmp, &msg->auth_request.password); - if (rc != SSH_OK) { - goto error; + msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; + rc = ssh_buffer_unpack(packet, "bs", &tmp, &msg->auth_request.password); + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(method, "keyboard-interactive") == 0) { - ssh_string lang = NULL; - ssh_string submethods = NULL; + if (strcmp(method, "keyboard-interactive") == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; - msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; - lang = ssh_buffer_get_ssh_string(packet); - if (lang == NULL) { - goto error; - } - /* from the RFC 4256 - * 3.1. Initial Exchange - * "The language tag is deprecated and SHOULD be the empty string." - */ - SSH_STRING_FREE(lang); + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + lang = ssh_buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + SSH_STRING_FREE(lang); - submethods = ssh_buffer_get_ssh_string(packet); - if (submethods == NULL) { - goto error; + submethods = ssh_buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + SSH_STRING_FREE(submethods); + + goto end; } - /* from the RFC 4256 - * 3.1. Initial Exchange - * "One possible implementation strategy of the submethods field on the - * server is that, unless the user may use multiple different - * submethods, the server ignores this field." - */ - SSH_STRING_FREE(submethods); - goto end; - } + if (strcmp(method, "publickey") == 0 || + strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) { + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + ssh_string server_pubkey_blob = NULL; + uint8_t has_sign; - if (strcmp(method, "publickey") == 0) { - ssh_string algo = NULL; - ssh_string pubkey_blob = NULL; - uint8_t has_sign; + msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; - msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; - SAFE_FREE(method); - rc = ssh_buffer_unpack(packet, "bSS", - &has_sign, - &algo, - &pubkey_blob - ); + rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob); - if (rc != SSH_OK) { - goto error; - } + if (rc != SSH_OK) { + goto error; + } - rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); - SSH_STRING_FREE(pubkey_blob); - pubkey_blob = NULL; - if (rc < 0) { - SSH_STRING_FREE(algo); - algo = NULL; - goto error; - } - msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; - 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; - } + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + server_pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (server_pubkey_blob == NULL) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } - // has a valid signature ? - if(has_sign) { - ssh_string sig_blob = NULL; - ssh_buffer digest = NULL; + rc = ssh_pki_import_pubkey_blob(server_pubkey_blob, + &msg->auth_request.server_pubkey); + SSH_STRING_FREE(server_pubkey_blob); - sig_blob = ssh_buffer_get_ssh_string(packet); - if(sig_blob == NULL) { - SSH_LOG(SSH_LOG_PACKET, "Invalid signature packet from peer"); - msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + if (rc < 0) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); + SSH_STRING_FREE(pubkey_blob); + pubkey_blob = NULL; + if (rc < 0) { SSH_STRING_FREE(algo); algo = NULL; goto error; } - - digest = ssh_msg_userauth_build_digest(session, msg, service, algo); - SSH_STRING_FREE(algo); - algo = NULL; - if (digest == NULL) { - SSH_STRING_FREE(sig_blob); - SSH_LOG(SSH_LOG_PACKET, "Failed to get digest"); - msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + 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; } - rc = ssh_pki_import_signature_blob(sig_blob, - msg->auth_request.pubkey, - &sig); - if (rc == SSH_OK) { - /* Check if the signature from client matches server preferences */ - if (session->opts.pubkey_accepted_types) { - if (!ssh_match_group(session->opts.pubkey_accepted_types, - sig->type_c)) - { - ssh_set_error(session, + // has a valid signature ? + if (has_sign) { + ssh_string sig_blob = NULL; + ssh_buffer digest = NULL; + + sig_blob = ssh_buffer_get_ssh_string(packet); + if (sig_blob == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid signature packet from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + digest = ssh_msg_userauth_build_digest(session, + msg, + service, + algo, + method); + SSH_STRING_FREE(algo); + algo = NULL; + if (digest == NULL) { + SSH_STRING_FREE(sig_blob); + SSH_LOG(SSH_LOG_PACKET, "Failed to get digest"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, + msg->auth_request.pubkey, + &sig); + if (rc == SSH_OK) { + /* Check if the signature from client matches server preferences + */ + if (session->opts.pubkey_accepted_types) { + cmp = match_group(session->opts.pubkey_accepted_types, + sig->type_c); + if (cmp != 1) { + ssh_set_error( + session, SSH_FATAL, "Public key from client (%s) doesn't match server " "preference (%s)", sig->type_c, session->opts.pubkey_accepted_types); - rc = SSH_ERROR; + rc = SSH_ERROR; + } + } + + if (rc == SSH_OK) { + rc = ssh_pki_signature_verify(session, + sig, + msg->auth_request.pubkey, + ssh_buffer_get(digest), + ssh_buffer_get_len(digest)); } } + SSH_STRING_FREE(sig_blob); + SSH_BUFFER_FREE(digest); + ssh_signature_free(sig); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, + "Received an invalid signature from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } - if (rc == SSH_OK) { - rc = ssh_pki_signature_verify(session, - sig, - msg->auth_request.pubkey, - ssh_buffer_get(digest), - ssh_buffer_get_len(digest)); + SSH_LOG(SSH_LOG_PACKET, "Valid signature received"); + + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + ssh_key server_key = NULL; + + if (msg->auth_request.server_pubkey == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key not provided by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_get_server_publickey(session, &server_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to get server public key for hostbound " + "verification"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_ERROR; + ssh_key_free(server_key); + goto error; + } + + if (ssh_key_cmp(server_key, + msg->auth_request.server_pubkey, + SSH_KEY_CMP_PUBLIC) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key doesn't match the one provided " + "by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + ssh_key_free(server_key); + goto error; + } + ssh_key_free(server_key); } + + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; } - SSH_STRING_FREE(sig_blob); - SSH_BUFFER_FREE(digest); - ssh_signature_free(sig); - if (rc < 0) { - SSH_LOG( - SSH_LOG_PACKET, - "Received an invalid signature from peer"); - msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + + SAFE_FREE(method); + SSH_STRING_FREE(algo); + goto end; + } +#ifdef WITH_GSSAPI + if (strcmp(method, "gssapi-with-mic") == 0) { + uint32_t n_oid; + ssh_string *oids = NULL; + ssh_string oid = NULL; + char *hexa = NULL; + int i; + ssh_buffer_get_u32(packet, &n_oid); + n_oid = ntohl(n_oid); + if (n_oid > 100) { + ssh_set_error( + session, + SSH_FATAL, + "USERAUTH_REQUEST: gssapi-with-mic OID count too big (%d)", + n_oid); goto error; } + SSH_LOG(SSH_LOG_PACKET, "gssapi: %d OIDs", n_oid); + oids = calloc(n_oid, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + goto error; + } + for (i = 0; i < (int)n_oid; ++i) { + oid = ssh_buffer_get_ssh_string(packet); + if (oid == NULL) { + for (i = i - 1; i >= 0; --i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + ssh_set_error(session, + SSH_LOG_PACKET, + "USERAUTH_REQUEST: gssapi-with-mic missing OID"); + goto error; + } + oids[i] = oid; + if (session->common.log_verbosity >= SSH_LOG_PACKET) { + hexa = ssh_get_hexa(ssh_string_data(oid), ssh_string_len(oid)); + SSH_LOG(SSH_LOG_PACKET, "gssapi: OID %d: %s", i, hexa); + SAFE_FREE(hexa); + } + } + ssh_gssapi_handle_userauth(session, + msg->auth_request.username, + n_oid, + oids); - SSH_LOG(SSH_LOG_PACKET, "Valid signature received"); + for (i = 0; i < (int)n_oid; ++i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_MESSAGE_FREE(msg); - msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; + return SSH_PACKET_USED; + } + if (strcmp(method, "gssapi-keyex") == 0) { + gss_buffer_desc received_mic = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + ssh_string mic_token_string = NULL; + OM_uint32 maj_stat, min_stat; + ssh_buffer buf = NULL; + + if (!ssh_kex_is_gss(session->current_crypto)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key Exchange."); + ssh_auth_reply_default(session, 0); + goto error; + } + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + ssh_auth_reply_default(session, 0); + goto error; + } + + rc = ssh_buffer_unpack(packet, "S", &mic_token_string); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); + goto error; + } + received_mic.length = ssh_string_len(mic_token_string); + received_mic.value = ssh_string_data(mic_token_string); + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(msg->auth_request.username); + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &received_mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify MIC for gssapi-keyex auth"); + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + ssh_auth_reply_success(session, 0); + + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_BUFFER_FREE(buf); + SSH_MESSAGE_FREE(msg); + SSH_STRING_FREE(mic_token_string); + + return SSH_PACKET_USED; } - SSH_STRING_FREE(algo); - goto end; - } -#ifdef WITH_GSSAPI - if (strcmp(method, "gssapi-with-mic") == 0) { - uint32_t n_oid; - ssh_string *oids; - ssh_string oid; - char *hexa; - int i; - ssh_buffer_get_u32(packet, &n_oid); - n_oid=ntohl(n_oid); - if(n_oid > 100){ - ssh_set_error(session, SSH_FATAL, "USERAUTH_REQUEST: gssapi-with-mic OID count too big (%d)",n_oid); - goto error; - } - SSH_LOG(SSH_LOG_PACKET, "gssapi: %d OIDs", n_oid); - oids = calloc(n_oid, sizeof(ssh_string)); - if (oids == NULL){ - ssh_set_error_oom(session); - goto error; - } - for (i=0;i<(int) n_oid;++i){ - oid=ssh_buffer_get_ssh_string(packet); - if(oid == NULL){ - for(i=i-1;i>=0;--i){ - SAFE_FREE(oids[i]); - } - SAFE_FREE(oids); - ssh_set_error(session, SSH_LOG_PACKET, "USERAUTH_REQUEST: gssapi-with-mic missing OID"); - goto error; - } - oids[i] = oid; - if(session->common.log_verbosity >= SSH_LOG_PACKET){ - hexa = ssh_get_hexa(ssh_string_data(oid), ssh_string_len(oid)); - SSH_LOG(SSH_LOG_PACKET,"gssapi: OID %d: %s",i, hexa); - SAFE_FREE(hexa); - } - } - ssh_gssapi_handle_userauth(session, msg->auth_request.username, n_oid, oids); - - for(i=0;i<(int)n_oid;++i){ - SAFE_FREE(oids[i]); - } - SAFE_FREE(oids); - /* bypass the message queue thing */ - SAFE_FREE(service); - SAFE_FREE(method); - SSH_MESSAGE_FREE(msg); - - return SSH_PACKET_USED; - } #endif - msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; - SAFE_FREE(method); - goto end; + msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(method); + goto end; error: - SAFE_FREE(service); - SAFE_FREE(method); + SAFE_FREE(service); + SAFE_FREE(method); - SSH_MESSAGE_FREE(msg); + SSH_MESSAGE_FREE(msg); - return SSH_PACKET_USED; + return SSH_PACKET_USED; end: - SAFE_FREE(service); - SAFE_FREE(method); + SAFE_FREE(service); + SAFE_FREE(method); - ssh_message_queue(session,msg); + ssh_message_queue(session, msg); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } #endif /* WITH_SERVER */ @@ -1026,7 +1269,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ uint32_t nanswers; uint32_t i; - ssh_string tmp; + ssh_string tmp = NULL; int rc; ssh_message msg = NULL; @@ -1075,9 +1318,9 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ uint32_t n; for (n = 0; n < session->kbdint->nanswers; n++) { - explicit_bzero(session->kbdint->answers[n], - strlen(session->kbdint->answers[n])); - SAFE_FREE(session->kbdint->answers[n]); + ssh_burn(session->kbdint->answers[n], + strlen(session->kbdint->answers[n])); + SAFE_FREE(session->kbdint->answers[n]); } SAFE_FREE(session->kbdint->answers); session->kbdint->nanswers = 0; @@ -1182,7 +1425,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ 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); + ssh_send_disconnect(session); goto error; } @@ -1265,8 +1508,10 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ * * @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; +int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, + ssh_channel chan) +{ + ssh_session session = NULL; int rc; if (msg == NULL) { @@ -1316,25 +1561,25 @@ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_c * * @returns NULL in case of error */ -ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { - ssh_channel chan; - int rc; - - if (msg == NULL) { - return NULL; - } - - chan = ssh_channel_new(msg->session); - if (chan == NULL) { - return NULL; - } - rc = ssh_message_channel_request_open_reply_accept_channel(msg, chan); - if (rc < 0) { - ssh_channel_free(chan); - chan = NULL; - } - return chan; +ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) +{ + ssh_channel chan = NULL; + int rc; + + if (msg == NULL) { + return NULL; + } + chan = ssh_channel_new(msg->session); + if (chan == NULL) { + return NULL; + } + rc = ssh_message_channel_request_open_reply_accept_channel(msg, chan); + if (rc < 0) { + ssh_channel_free(chan); + chan = NULL; + } + return chan; } /** @@ -1356,116 +1601,126 @@ ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { * * @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) { - ssh_message msg = NULL; - int rc; - - msg = ssh_message_new(session); - if (msg == NULL) { - ssh_set_error_oom(session); - goto error; - } - - SSH_LOG(SSH_LOG_PACKET, - "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 ") (want_reply=%hhd)", - request, channel->local_channel, channel->remote_channel, want_reply); - - msg->type = SSH_REQUEST_CHANNEL; - msg->channel_request.channel = channel; - msg->channel_request.want_reply = want_reply; - - if (strcmp(request, "pty-req") == 0) { - rc = ssh_buffer_unpack(packet, "sddddS", - &msg->channel_request.TERM, - &msg->channel_request.width, - &msg->channel_request.height, - &msg->channel_request.pxwidth, - &msg->channel_request.pxheight, - &msg->channel_request.modes - ); +int +ssh_message_handle_channel_request(ssh_session session, + ssh_channel channel, + ssh_buffer packet, + const char *request, + uint8_t want_reply) +{ + ssh_message msg = NULL; + int rc; - msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } - if (rc != SSH_OK) { - goto error; + SSH_LOG(SSH_LOG_PACKET, + "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 + ") (want_reply=%hhu)", + request, + channel->local_channel, + channel->remote_channel, + want_reply); + + msg->type = SSH_REQUEST_CHANNEL; + msg->channel_request.channel = channel; + msg->channel_request.want_reply = want_reply; + + if (strcmp(request, "pty-req") == 0) { + rc = ssh_buffer_unpack(packet, + "sddddS", + &msg->channel_request.TERM, + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight, + &msg->channel_request.modes); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(request, "window-change") == 0) { - msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; - rc = ssh_buffer_unpack(packet, "dddd", - &msg->channel_request.width, - &msg->channel_request.height, - &msg->channel_request.pxwidth, - &msg->channel_request.pxheight); - if (rc != SSH_OK){ - goto error; + if (strcmp(request, "window-change") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; + rc = ssh_buffer_unpack(packet, + "dddd", + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight); + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(request, "subsystem") == 0) { - rc = ssh_buffer_unpack(packet, "s", - &msg->channel_request.subsystem); - msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; - if (rc != SSH_OK){ - goto error; + if (strcmp(request, "subsystem") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.subsystem); + msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(request, "shell") == 0) { - msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; - goto end; - } + if (strcmp(request, "shell") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; + goto end; + } - if (strcmp(request, "exec") == 0) { - rc = ssh_buffer_unpack(packet, "s", - &msg->channel_request.command); - msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; - if (rc != SSH_OK) { - goto error; + if (strcmp(request, "exec") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.command); + msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(request, "env") == 0) { - rc = ssh_buffer_unpack(packet, "ss", - &msg->channel_request.var_name, - &msg->channel_request.var_value); - msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; - if (rc != SSH_OK) { - goto error; + if (strcmp(request, "env") == 0) { + rc = ssh_buffer_unpack(packet, + "ss", + &msg->channel_request.var_name, + &msg->channel_request.var_value); + msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; + if (rc != SSH_OK) { + goto error; + } + goto end; } - goto end; - } - if (strcmp(request, "x11-req") == 0) { - rc = ssh_buffer_unpack(packet, "bssd", - &msg->channel_request.x11_single_connection, - &msg->channel_request.x11_auth_protocol, - &msg->channel_request.x11_auth_cookie, - &msg->channel_request.x11_screen_number); + if (strcmp(request, "x11-req") == 0) { + rc = ssh_buffer_unpack(packet, + "bssd", + &msg->channel_request.x11_single_connection, + &msg->channel_request.x11_auth_protocol, + &msg->channel_request.x11_auth_cookie, + &msg->channel_request.x11_screen_number); - msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; - if (rc != SSH_OK) { - goto error; - } + msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; + if (rc != SSH_OK) { + goto error; + } - goto end; - } + goto end; + } - msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; + msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; end: - ssh_message_queue(session,msg); + ssh_message_queue(session, msg); - return SSH_OK; + return SSH_OK; error: - SSH_MESSAGE_FREE(msg); + SSH_MESSAGE_FREE(msg); - return SSH_ERROR; + return SSH_ERROR; } /** @internal @@ -1476,53 +1731,55 @@ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, * * @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; +int ssh_message_channel_request_reply_success(ssh_message msg) +{ + uint32_t channel; + int rc; - if (msg == NULL) { - return SSH_ERROR; - } + if (msg == NULL) { + return SSH_ERROR; + } - if (msg->channel_request.want_reply) { - channel = msg->channel_request.channel->remote_channel; + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; - SSH_LOG(SSH_LOG_PACKET, - "Sending a channel_request success to channel %" PRIu32, channel); + SSH_LOG(SSH_LOG_PACKET, + "Sending a channel_request success to channel %" PRIu32, + channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_SUCCESS, + channel); + if (rc != SSH_OK) { + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } - rc = ssh_buffer_pack(msg->session->out_buffer, - "bd", - SSH2_MSG_CHANNEL_SUCCESS, - channel); - if (rc != SSH_OK){ - ssh_set_error_oom(msg->session); - return SSH_ERROR; + return ssh_packet_send(msg->session); } - return ssh_packet_send(msg->session); - } - - SSH_LOG(SSH_LOG_PACKET, - "The client doesn't want to know the request succeeded"); + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request succeeded"); - return SSH_OK; + return SSH_OK; } #ifdef WITH_SERVER -SSH_PACKET_CALLBACK(ssh_packet_global_request){ +SSH_PACKET_CALLBACK(ssh_packet_global_request) +{ ssh_message msg = NULL; - char *request=NULL; + char *request = NULL; uint8_t want_reply; int rc = SSH_PACKET_USED; int r; + (void)user; (void)type; (void)packet; SSH_LOG(SSH_LOG_DEBUG,"Received SSH_MSG_GLOBAL_REQUEST packet"); - r = ssh_buffer_unpack(packet, "sb", - &request, - &want_reply); + r = ssh_buffer_unpack(packet, "sb", &request, &want_reply); if (r != SSH_OK){ goto error; } @@ -1541,25 +1798,35 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ goto reply_with_failure; } - r = ssh_buffer_unpack(packet, "sd", - &msg->global_request.bind_address, - &msg->global_request.bind_port - ); + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); if (r != SSH_OK){ goto reply_with_failure; } msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %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_DEBUG, "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, - want_reply, msg->global_request.bind_address, + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu %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); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); } else { SAFE_FREE(request); ssh_message_queue(session, msg); @@ -1572,21 +1839,29 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ goto reply_with_failure; } - r = ssh_buffer_unpack(packet, "sd", - &msg->global_request.bind_address, - &msg->global_request.bind_port); + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); if (r != SSH_OK){ goto reply_with_failure; } msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; msg->global_request.want_reply = want_reply; - SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_GLOBAL_REQUEST %s %d %s:%d", request, want_reply, + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, msg->global_request.bind_address, msg->global_request.bind_port); - if(ssh_callbacks_exists(session->common.callbacks, global_request_function)) { - session->common.callbacks->global_request_function(session, msg, session->common.callbacks->userdata); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); } else { SAFE_FREE(request); ssh_message_queue(session, msg); @@ -1595,24 +1870,49 @@ 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_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_LOG(SSH_LOG_DEBUG, + "Received keepalive@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { 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); + SSH_LOG(SSH_LOG_PROTOCOL, + "Received no-more-sessions@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { + ssh_message_global_request_reply_success(msg, 0); + } session->flags |= SSH_SESSION_FLAG_NO_MORE_SESSIONS; } else { - SSH_LOG(SSH_LOG_DEBUG, "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, " - "want_reply = %d", request, want_reply); + SSH_LOG(SSH_LOG_DEBUG, + "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, want_reply = %hhu", + request, + want_reply); goto reply_with_failure; } @@ -1623,8 +1923,7 @@ SSH_PACKET_CALLBACK(ssh_packet_global_request){ reply_with_failure: /* Only report the failure if requested */ if (want_reply) { - r = ssh_buffer_add_u8(session->out_buffer, - SSH2_MSG_REQUEST_FAILURE); + r = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_REQUEST_FAILURE); if (r < 0) { ssh_set_error_oom(session); goto error; diff --git a/src/misc.c b/src/misc.c index 8bdd568e..0d702f7b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -27,15 +27,17 @@ #ifndef _WIN32 /* This is needed for a standard getpwuid_r on opensolaris */ #define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include #include -#include #include -#include -#include +#include #endif /* _WIN32 */ #include +#include #include #include #include @@ -59,6 +61,7 @@ #include #include #include +#include #ifdef HAVE_IO_H #include @@ -71,7 +74,7 @@ #include "libssh/session.h" #ifdef HAVE_LIBGCRYPT -#define GCRYPT_STRING "/gnutls" +#define GCRYPT_STRING "/gcrypt" #else #define GCRYPT_STRING "" #endif @@ -94,6 +97,8 @@ #define ZLIB_STRING "" #endif +#define ARPA_DOMAIN_MAX_LEN 63 + /** * @defgroup libssh_misc The SSH helper functions * @ingroup libssh @@ -104,22 +109,22 @@ */ #ifdef _WIN32 -char *ssh_get_user_home_dir(void) +static char *ssh_get_user_home_dir_internal(void) { - char tmp[PATH_MAX] = {0}; - char *szPath = NULL; + char tmp[PATH_MAX] = {0}; + char *szPath = NULL; - if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { - szPath = malloc(strlen(tmp) + 1); - if (szPath == NULL) { - return NULL; - } + if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { + szPath = malloc(strlen(tmp) + 1); + if (szPath == NULL) { + return NULL; + } - strcpy(szPath, tmp); - return szPath; - } + strcpy(szPath, tmp); + return szPath; + } - return NULL; + return NULL; } /* we have read access on file */ @@ -174,10 +179,40 @@ int ssh_gettimeofday(struct timeval *__p, void *__t) return (0); } +/** + * @internal + * + * @brief Convert time in seconds since the Epoch to broken-down local time + * + * This is a helper used to provide localtime_r() like function interface + * on Windows. + * + * @param timer Pointer to a location storing the time_t which + * represents the time in seconds since the Epoch. + * + * @param result Pointer to a location where the broken-down time + * (expressed as local time) should be stored. + * + * @returns A pointer to the structure pointed to by the parameter + * result on success, NULL on error with the errno + * set to indicate the error. + */ +struct tm *ssh_localtime(const time_t *timer, struct tm *result) +{ + errno_t rc; + rc = localtime_s(result, timer); + if (rc != 0) { + return NULL; + } + + return result; +} + char *ssh_get_local_username(void) { DWORD size = 0; - char *user; + char *user = NULL; + int rc; /* get the size */ GetUserName(NULL, &size); @@ -188,9 +223,14 @@ char *ssh_get_local_username(void) } if (GetUserName(user, &size)) { - return user; + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } } + free(user); + return NULL; } @@ -220,22 +260,37 @@ int ssh_is_ipaddr_v4(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 */ @@ -244,7 +299,7 @@ int ssh_is_ipaddr(const char *str) #define NSS_BUFLEN_PASSWD 4096 #endif /* NSS_BUFLEN_PASSWD */ -char *ssh_get_user_home_dir(void) +static char *ssh_get_user_home_dir_internal(void) { char *szPath = NULL; struct passwd pwd; @@ -259,7 +314,6 @@ char *ssh_get_user_home_dir(void) return NULL; } snprintf(buf, sizeof(buf), "%s", szPath); - return strdup(buf); } @@ -308,7 +362,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); @@ -317,8 +371,10 @@ 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; } @@ -341,40 +397,78 @@ int ssh_is_ipaddr_v4(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_get_user_home_dir(ssh_session session) +{ + char *szPath = NULL; + + /* If used previously, reuse cached value */ + if (session != NULL && session->opts.homedir != NULL) { + return strdup(session->opts.homedir); + } + + szPath = ssh_get_user_home_dir_internal(); + if (szPath == NULL) { + return NULL; + } + + if (session != NULL) { + /* cache it: + * failure is not fatal -- at worst we will just not cache it */ + session->opts.homedir = strdup(szPath); + } + + return szPath; +} + char *ssh_lowercase(const char* str) { - char *new, *p; + char *new = NULL, *p = NULL; - if (str == NULL) { - return NULL; - } + if (str == NULL) { + return NULL; + } - new = strdup(str); - if (new == NULL) { - return NULL; - } + new = strdup(str); + if (new == NULL) { + return NULL; + } - for (p = new; *p; p++) { - *p = tolower(*p); - } + for (p = new; *p; p++) { + *p = tolower(*p); + } - return new; + return new; } char *ssh_hostport(const char *host, int port) @@ -397,45 +491,56 @@ char *ssh_hostport(const char *host, int port) return dest; } -/** - * @brief Convert a buffer into a colon separated hex string. - * The caller has to free the memory. - * - * @param[in] what What should be converted to a hex string. - * - * @param[in] len Length of the buffer to convert. - * - * @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) +static char * +ssh_get_hexa_internal(const unsigned char *what, size_t len, bool colons) { const char h[] = "0123456789abcdef"; - char *hexa; + char *hexa = NULL; size_t i; - size_t hlen = len * 3; + size_t bytes_per_byte = 2 + (colons ? 1 : 0); + size_t hlen = len * bytes_per_byte; - if (len > (UINT_MAX - 1) / 3) { + if (what == NULL || len < 1 || len > (UINT_MAX - 1) / bytes_per_byte) { return NULL; } - hexa = malloc(hlen + 1); + hexa = calloc(hlen + 1, sizeof(char)); if (hexa == NULL) { return NULL; } for (i = 0; i < len; i++) { - hexa[i * 3] = h[(what[i] >> 4) & 0xF]; - hexa[i * 3 + 1] = h[what[i] & 0xF]; - hexa[i * 3 + 2] = ':'; + hexa[i * bytes_per_byte] = h[(what[i] >> 4) & 0xF]; + hexa[i * bytes_per_byte + 1] = h[what[i] & 0xF]; + if (colons) { + hexa[i * bytes_per_byte + 2] = ':'; + } + } + if (colons) { + hexa[hlen - 1] = '\0'; } - hexa[hlen - 1] = '\0'; return hexa; } +/** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. + * + * @param[in] what What should be converted to a hex string. + * + * @param[in] len Length of the buffer to convert. + * + * @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) +{ + return ssh_get_hexa_internal(what, len, true); +} + /** * @deprecated Please use ssh_print_hash() instead */ @@ -683,7 +788,7 @@ struct ssh_list *ssh_list_new(void) void ssh_list_free(struct ssh_list *list) { - struct ssh_iterator *ptr, *next; + struct ssh_iterator *ptr = NULL, *next = NULL; if (!list) return; ptr = list->root; @@ -704,7 +809,7 @@ struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) { - struct ssh_iterator *it; + struct ssh_iterator *it = NULL; for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) if (it->data == value) @@ -743,7 +848,17 @@ static struct ssh_iterator *ssh_iterator_new(const void *data) return iterator; } -int ssh_list_append(struct ssh_list *list,const void *data) +/** + * @internal + * + * @brief Appends an element to the end of the list. + * + * @param[in] list The list to append the element + * @param[in] data The element to append + * + * @return `SSH_OK` on success, `SSH_ERROR` on error + */ +int ssh_list_append(struct ssh_list *list, const void *data) { struct ssh_iterator *iterator = NULL; @@ -794,32 +909,32 @@ int ssh_list_prepend(struct ssh_list *list, const void *data) void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) { - struct ssh_iterator *ptr, *prev; + struct ssh_iterator *ptr = NULL, *prev = NULL; - if (list == NULL) { - return; - } + if (list == NULL) { + return; + } - prev=NULL; - ptr=list->root; - while(ptr && ptr != iterator){ - prev=ptr; - ptr=ptr->next; - } - if(!ptr){ - /* we did not find the element */ - return; - } - /* unlink it */ - if(prev) - prev->next=ptr->next; - /* if iterator was the head */ - if(list->root == iterator) - list->root=iterator->next; - /* if iterator was the tail */ - if(list->end == iterator) - list->end = prev; - SAFE_FREE(iterator); + prev = NULL; + ptr = list->root; + while (ptr && ptr != iterator) { + prev = ptr; + ptr = ptr->next; + } + if (!ptr) { + /* we did not find the element */ + return; + } + /* unlink it */ + if (prev) + prev->next = ptr->next; + /* if iterator was the head */ + if (list->root == iterator) + list->root = iterator->next; + /* if iterator was the tail */ + if (list->end == iterator) + list->end = prev; + SAFE_FREE(iterator); } /** @@ -935,7 +1050,7 @@ char *ssh_dirname (const char *path) char *ssh_basename (const char *path) { char *new = NULL; - const char *s; + const char *s = NULL; size_t len; if (path == NULL || *path == '\0') { @@ -1073,8 +1188,8 @@ int ssh_mkdirs(const char *pathname, mode_t mode) */ char *ssh_path_expand_tilde(const char *d) { - char *h = NULL, *r; - const char *p; + char *h = NULL, *r = NULL; + const char *p = NULL; size_t ld; size_t lh = 0; @@ -1089,7 +1204,7 @@ char *ssh_path_expand_tilde(const char *d) #ifdef _WIN32 return strdup(d); #else - struct passwd *pw; + struct passwd *pw = NULL; size_t s = p - d; char u[128]; @@ -1108,7 +1223,7 @@ char *ssh_path_expand_tilde(const char *d) } else { ld = strlen(d); p = (char *) d; - h = ssh_get_user_home_dir(); + h = ssh_get_user_home_dir(NULL); } if (h == NULL) { return NULL; @@ -1130,15 +1245,118 @@ char *ssh_path_expand_tilde(const char *d) return r; } +char *ssh_get_local_hostname(void) +{ + char host[NI_MAXHOST] = {0}; + int rc; + + rc = gethostname(host, sizeof(host)); + if (rc != 0) { + return NULL; + } + return strdup(host); +} + +static char *get_connection_hash(ssh_session session) +{ + unsigned char conn_hash[SHA_DIGEST_LENGTH]; + char *local_hostname = NULL; + SHACTX ctx = sha1_init(); + char strport[10] = {0}; + unsigned int port; + char *username = NULL; + int rc; + + if (session == NULL) { + return NULL; + } + + if (ctx == NULL) { + goto err; + } + + /* Local hostname %l */ + local_hostname = ssh_get_local_hostname(); + if (local_hostname == NULL) { + goto err; + } + rc = sha1_update(ctx, local_hostname, strlen(local_hostname)); + if (rc != SSH_OK) { + goto err; + } + SAFE_FREE(local_hostname); + + /* Remote hostname %h */ + if (session->opts.host == NULL) { + goto err; + } + rc = sha1_update(ctx, session->opts.host, strlen(session->opts.host)); + if (rc != SSH_OK) { + goto err; + } + + /* Remote port %p */ + ssh_options_get_port(session, &port); + snprintf(strport, sizeof(strport), "%d", port); + rc = sha1_update(ctx, strport, strlen(strport)); + if (rc != SSH_OK) { + goto err; + } + + /* The remote username %r */ + username = session->opts.username; + if (username == NULL) { + /* fallback to local username: it will be used if not explicitly set */ + username = ssh_get_local_username(); + if (username == NULL) { + goto err; + } + } + rc = sha1_update(ctx, username, strlen(username)); + if (username != session->opts.username) { + free(username); + } + if (rc != SSH_OK) { + goto err; + } + + /* ProxyJump */ + if (session->opts.proxy_jumps_str != NULL) { + rc = sha1_update(ctx, + session->opts.proxy_jumps_str, + strlen(session->opts.proxy_jumps_str)); + } + if (rc != SSH_OK) { + goto err; + } + + /* Frees context */ + rc = sha1_final(conn_hash, ctx); + if (rc != SSH_OK) { + goto err; + } + + return ssh_get_hexa_internal(conn_hash, SHA_DIGEST_LENGTH, false); + +err: + free(local_hostname); + sha1_ctx_free(ctx); + return NULL; +} + /** @internal * @brief expands a string in function of session options + * * @param[in] s Format string to expand. Known parameters: - * %d SSH configuration directory (~/.ssh) - * %h target host name - * %u local username - * %l local hostname - * %r remote username - * %p remote port + * - %d user home directory (~) + * - %h target host name + * - %u local username + * - %l local hostname + * - %r remote username + * - %p remote port + * - %j proxyjump string + * - %C Hash of %l%h%p%r%j + * * @returns Expanded string. The caller needs to free the memory using * ssh_string_free_char(). * @@ -1146,11 +1364,10 @@ char *ssh_path_expand_tilde(const char *d) */ char *ssh_path_expand_escape(ssh_session session, const char *s) { - char host[NI_MAXHOST] = {0}; char *buf = NULL; char *r = NULL; char *x = NULL; - const char *p; + const char *p = NULL; size_t i, l; r = ssh_path_expand_tilde(s); @@ -1195,65 +1412,67 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) } switch (*p) { - case '%': - goto escape; - case 'd': - 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(); - break; - case 'l': - if (gethostname(host, sizeof(host) == 0)) { - x = strdup(host); - } - break; - case 'h': - 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': - 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': - { - char tmp[6]; - - snprintf(tmp, sizeof(tmp), "%hu", - (uint16_t)(session->opts.port > 0 ? session->opts.port - : 22)); - x = strdup(tmp); - } - break; - default: - ssh_set_error(session, SSH_FATAL, - "Wrong escape sequence detected"); + case '%': + goto escape; + case 'd': + x = ssh_get_user_home_dir(session); + if (x == NULL) { + ssh_set_error(session, SSH_FATAL, "Cannot expand homedir"); + free(buf); + free(r); + return NULL; + } + break; + case 'u': + x = ssh_get_local_username(); + break; + case 'l': + x = ssh_get_local_hostname(); + break; + case 'h': + 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': + 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': { + char tmp[6]; + unsigned int port; + + ssh_options_get_port(session, &port); + snprintf(tmp, sizeof(tmp), "%u", port); + x = strdup(tmp); + break; + } + case 'j': + if (session->opts.proxy_jumps_str != NULL) { + x = strdup(session->opts.proxy_jumps_str); + } else { + x = strdup(""); + } + break; + case 'C': + x = get_connection_hash(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); + free(r); + return NULL; } if (x == NULL) { @@ -1265,8 +1484,7 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) i += strlen(x); if (i >= MAX_BUF_SIZE) { - ssh_set_error(session, SSH_FATAL, - "String too long"); + ssh_set_error(session, SSH_FATAL, "String too long"); free(buf); free(x); free(r); @@ -1303,8 +1521,9 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) */ int ssh_analyze_banner(ssh_session session, int server) { - const char *banner; - const char *openssh; + const char *banner = NULL; + const char *openssh = NULL; + const char *ios = NULL; if (server) { banner = session->clientbanner; @@ -1355,6 +1574,7 @@ int ssh_analyze_banner(ssh_session session, int server) char *tmp = NULL; unsigned long int major = 0UL; unsigned long int minor = 0UL; + int off = 0; /* * The banner is typical: @@ -1374,8 +1594,9 @@ int ssh_analyze_banner(ssh_session session, int server) } errno = 0; - minor = strtoul(openssh + 10, &tmp, 10); - if ((tmp == (openssh + 10)) || + off = major >= 10 ? 11 : 10; + minor = strtoul(openssh + off, &tmp, 10); + if ((tmp == (openssh + off)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || (minor > 100)) { @@ -1392,6 +1613,11 @@ int ssh_analyze_banner(ssh_session session, int server) major, minor, session->openssh); } } + /* Cisco devices have odd scp implementation which breaks */ + ios = strstr(banner, "Cisco"); + if (ios != NULL) { + session->flags |= SSH_SESSION_FLAG_SCP_QUOTING_BROKEN; + } done: return 0; @@ -1529,48 +1755,26 @@ int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) return ret >= 0 ? ret: 0; } - -int ssh_match_group(const char *group, const char *object) +/** + * @brief Securely free memory by overwriting it before deallocation + * + * Overwrites the memory region with zeros before calling free() to prevent + * sensitive data from remaining in memory after deallocation. + * + * @param[in] ptr Pointer to the memory region to securely free. + * Can be NULL (no operation performed). + * @param[in] len Length of the memory region in bytes. + * + */ +void burn_free(void *ptr, size_t len) { - const char *a; - const char *z; - - z = group; - do { - a = strchr(z, ','); - if (a == NULL) { - if (strcmp(z, object) == 0) { - return 1; - } - return 0; - } else { - if (strncmp(z, object, a - z) == 0) { - return 1; - } - } - z = a + 1; - } while(1); - - /* not reached */ - return 0; -} + if (ptr == NULL || len == 0) { + return; + } -#if !defined(HAVE_EXPLICIT_BZERO) -void explicit_bzero(void *s, size_t n) -{ -#if defined(HAVE_MEMSET_S) - memset_s(s, n, '\0', n); -#elif defined(HAVE_SECURE_ZERO_MEMORY) - SecureZeroMemory(s, n); -#else - memset(s, '\0', n); -#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) - /* See http://llvm.org/bugs/show_bug.cgi?id=15495 */ - __asm__ volatile("" : : "g"(s) : "memory"); -#endif /* HAVE_GCC_VOLATILE_MEMORY_PROTECTION */ -#endif + ssh_burn(ptr, len); + free(ptr); } -#endif /* !HAVE_EXPLICIT_BZERO */ #if !defined(HAVE_STRNDUP) char *strndup(const char *s, size_t n) @@ -1784,7 +1988,7 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) /* Put the string terminator */ *dst = '\0'; - return dst - buf; + return (int)(dst - buf); error: return SSH_ERROR; @@ -1830,7 +2034,7 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) } *out = '\0'; - return out - buf; + return (int)(out - buf); } /** @@ -1955,7 +2159,7 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) */ char *ssh_strerror(int err_num, char *buf, size_t buflen) { -#if defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) +#if ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) /* GNU extension on Linux */ return strerror_r(err_num, buf, buflen); #else @@ -1973,7 +2177,7 @@ char *ssh_strerror(int err_num, char *buf, size_t buflen) buf[0] = '\0'; } return buf; -#endif /* defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) */ +#endif /* ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) */ } /** @@ -2073,7 +2277,7 @@ ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) ((const char *)buf) + total_bytes_written, nbytes - total_bytes_written); if (bytes_written == -1) { - if(errno == EINTR) { + if (errno == EINTR) { /* Ignoring errors due to signal interrupts */ continue; } @@ -2087,4 +2291,216 @@ ssize_t ssh_writen(int fd, const void *buf, size_t 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; +} + +/** + * @brief Free proxy jump list + * + * Frees everything in a proxy jump list, but doesn't free the ssh_list + * + * @param proxy_jump_list + * + */ +void +ssh_proxyjumps_free(struct ssh_list *proxy_jump_list) +{ + struct ssh_jump_info_struct *jump = NULL; + + for (jump = + ssh_list_pop_head(struct ssh_jump_info_struct *, proxy_jump_list); + jump != NULL; + jump = ssh_list_pop_head(struct ssh_jump_info_struct *, + proxy_jump_list)) { + SAFE_FREE(jump->hostname); + SAFE_FREE(jump->username); + SAFE_FREE(jump); + } +} + +/** + * @brief Check if libssh proxy jumps is enabled + * + * If env variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * through the OpenSSH binary. + * + * @return false if OPENSSH_PROXYJUMP=1 + * true otherwise + */ +bool +ssh_libssh_proxy_jumps(void) +{ + const char *t = getenv("OPENSSH_PROXYJUMP"); + + return !(t != NULL && t[0] == '1'); +} + +/** + * @internal + * + * @brief Safely open a file containing some configuration. + * + * Runs checks if the file can be used as some configuration file (is regular + * file and is not too large). If so, returns the opened file (for reading). + * Otherwise logs error and returns `NULL`. + * + * @param filename The path to the file to open. + * @param max_file_size Maximum file size that is accepted. + * + * @returns the opened file or `NULL` on error. + */ +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) +{ + FILE *f = NULL; + struct stat sb; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + int r, fd; + + /* open first to avoid TOCTOU */ + fd = open(filename, O_RDONLY); + if (fd == -1) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return NULL; + } + + /* Check the file is sensible for a configuration file */ + r = fstat(fd, &sb); + if (r != 0) { + SSH_LOG(SSH_LOG_RARE, + "Failed to stat %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + if ((sb.st_mode & S_IFMT) != S_IFREG) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is not a regular file: skipping", + filename); + close(fd); + return NULL; + } + + if ((size_t)sb.st_size > max_file_size) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is too large (%jd MB > %zu MB): skipping", + filename, + (intmax_t)sb.st_size / 1024 / 1024, + max_file_size / 1024 / 1024); + close(fd); + return NULL; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + + /* the flcose() will close also the underlying fd */ + return f; +} + /** @} */ diff --git a/src/mlkem.c b/src/mlkem.c new file mode 100644 index 00000000..967c4f11 --- /dev/null +++ b/src/mlkem.c @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/mlkem.h" + +const struct mlkem_type_info *kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + return &MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return &MLKEM1024_INFO; +#endif + default: + return NULL; + } +} diff --git a/src/mlkem_crypto.c b/src/mlkem_crypto.c new file mode 100644 index 00000000..8a784658 --- /dev/null +++ b/src/mlkem_crypto.c @@ -0,0 +1,238 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include +#include +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_768_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = OSSL_ML_KEM_1024_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_1024_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_1024, +}; + +int ssh_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + size_t pubkey_size; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, mlkem_info->name, NULL); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen_init(ctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen(ctx, &pkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + EVP_PKEY_free(crypto->mlkem_privkey); + crypto->mlkem_privkey = pkey; + + pubkey_size = mlkem_info->pubkey_size; + pubkey = ssh_string_new(pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, + ssh_string_data(pubkey), + &pubkey_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to extract ML-KEM public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey = ssh_string_data(crypto->mlkem_client_pubkey); + const size_t pubkey_len = ssh_string_len(crypto->mlkem_client_pubkey); + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + ssh_string ciphertext = NULL; + size_t ciphertext_size; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + pkey = EVP_PKEY_new_raw_public_key_ex(NULL, + mlkem_info->name, + NULL, + pubkey, + pubkey_len); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM public key from raw data: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ciphertext_size = mlkem_info->ciphertext_size; + ciphertext = ssh_string_new(ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate(ctx, + ssh_string_data(ciphertext), + &ciphertext_size, + shared_secret, + &shared_secret_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + struct ssh_crypto_struct *crypto = session->next_crypto; + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, crypto->mlkem_privkey, NULL); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate(ctx, + shared_secret, + &shared_secret_size, + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + EVP_PKEY_CTX_free(ctx); + return ret; +} diff --git a/src/mlkem_gcrypt.c b/src/mlkem_gcrypt.c new file mode 100644 index 00000000..1898becb --- /dev/null +++ b/src/mlkem_gcrypt.c @@ -0,0 +1,207 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = GCRY_KEM_MLKEM768_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM768_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM768_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = GCRY_KEM_MLKEM1024_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM1024_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM1024_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM1024, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + unsigned char *privkey = NULL, *pubkey_data = NULL; + gcry_error_t err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + privkey = malloc(mlkem_info->privkey_size); + if (privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey_data = ssh_string_data(pubkey); + err = gcry_kem_keypair(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + privkey, + mlkem_info->privkey_size); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ML-KEM key: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + free(crypto->mlkem_privkey); + crypto->mlkem_privkey = privkey; + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + privkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + if (privkey != NULL) { + ssh_burn(privkey, mlkem_info->privkey_size); + free(privkey); + } + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + unsigned char *ciphertext_data = NULL; + ssh_string ciphertext = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + gcry_error_t err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_encap(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + gcry_error_t err; + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + if (crypto->mlkem_privkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ML-KEM private key in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_decap(mlkem_info->alg, + crypto->mlkem_privkey, + mlkem_info->privkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/mlkem_native.c b/src/mlkem_native.c new file mode 100644 index 00000000..97b22a36 --- /dev/null +++ b/src/mlkem_native.c @@ -0,0 +1,202 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/mlkem_native.h" +#include "libssh/session.h" + +#define crypto_kem_mlkem768_PUBLICKEYBYTES 1184 +#define crypto_kem_mlkem768_SECRETKEYBYTES 2400 +#define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088 + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, + .privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES, + .ciphertext_size = crypto_kem_mlkem768_CIPHERTEXTBYTES, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + unsigned char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN]; + struct libcrux_mlkem768_keypair keypair; + int err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd); + + if (ssh_string_len(crypto->mlkem_client_pubkey) < mlkem_info->pubkey_size) { + SSH_STRING_FREE(crypto->mlkem_client_pubkey); + } + if (crypto->mlkem_client_pubkey == NULL) { + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_client_pubkey, + keypair.pk.value, + mlkem_info->pubkey_size); + if (err) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to fill the string with client pubkey"); + goto cleanup; + } + + if (crypto->mlkem_privkey == NULL) { + crypto->mlkem_privkey = malloc(mlkem_info->privkey_size); + if (crypto->mlkem_privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + memcpy(crypto->mlkem_privkey, keypair.sk.value, mlkem_info->privkey_size); + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + + ret = SSH_OK; + +cleanup: + ssh_burn(&keypair, sizeof(keypair)); + ssh_burn(rnd, sizeof(rnd)); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + struct libcrux_mlkem768_enc_result enc; + struct libcrux_mlkem768_pk mlkem_pub = {0}; + unsigned char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN]; + int err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + memcpy(mlkem_pub.value, pubkey_data, mlkem_info->pubkey_size); + err = libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub); + if (err == 0) { + SSH_LOG(SSH_LOG_WARNING, "Invalid public key"); + return SSH_ERROR; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd); + + if (ssh_string_len(crypto->mlkem_ciphertext) < mlkem_info->ciphertext_size) { + SSH_STRING_FREE(crypto->mlkem_ciphertext); + } + if (crypto->mlkem_ciphertext == NULL) { + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_ciphertext, + enc.fst.value, + sizeof(enc.fst.value)); + if (err != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to fill the string with ciphertext"); + goto cleanup; + } + memcpy(shared_secret, enc.snd, sizeof(enc.snd)); + + ret = SSH_OK; + +cleanup: + ssh_burn(rnd, sizeof(rnd)); + ssh_burn(&enc, sizeof(enc)); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + struct libcrux_mlkem768_sk mlkem_priv = {0}; + struct libcrux_mlkem768_ciphertext mlkem_ciphertext = {0}; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + memcpy(mlkem_ciphertext.value, + ciphertext_data, + sizeof(mlkem_ciphertext.value)); + + memcpy(mlkem_priv.value, crypto->mlkem_privkey, crypto->mlkem_privkey_len); + + libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv, + &mlkem_ciphertext, + shared_secret); + return SSH_OK; +} diff --git a/src/options.c b/src/options.c index f0bb476a..23ce086e 100644 --- a/src/options.c +++ b/src/options.c @@ -31,12 +31,21 @@ #else #include #endif -#include +#include "libssh/config.h" +#include "libssh/config_parser.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" #include "libssh/pki_priv.h" #include "libssh/priv.h" #include "libssh/session.h" +#include #include "libssh/misc.h" #include "libssh/options.h" +#include "libssh/config_parser.h" +#include "libssh/gssapi.h" +#include "libssh/token.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/bind.h" @@ -66,7 +75,7 @@ */ int ssh_options_copy(ssh_session src, ssh_session *dest) { - ssh_session new; + ssh_session new = NULL; struct ssh_iterator *it = NULL; struct ssh_list *list = NULL; char *id = NULL; @@ -118,7 +127,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) while (it) { int rc; - id = strdup((char *) it->data); + id = strdup((char *)it->data); if (id == NULL) { ssh_free(new); return -1; @@ -138,6 +147,32 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) 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); + 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 certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); + } + if (src->opts.sshdir != NULL) { new->opts.sshdir = strdup(src->opts.sshdir); if (new->opts.sshdir == NULL) { @@ -226,9 +261,19 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) new->opts.nodelay = src->opts.nodelay; new->opts.config_processed = src->opts.config_processed; new->opts.control_master = src->opts.control_master; + new->opts.address_family = src->opts.address_family; new->common.log_verbosity = src->common.log_verbosity; new->common.callbacks = src->common.callbacks; + SSH_PKI_CTX_FREE(new->pki_context); + if (src->pki_context != NULL) { + new->pki_context = ssh_pki_ctx_dup(src->pki_context); + if (new->pki_context == NULL) { + ssh_free(new); + return -1; + } + } + *dest = new; return 0; @@ -284,10 +329,17 @@ int ssh_options_set_algo(ssh_session session, * following: * * - SSH_OPTIONS_HOST: - * The hostname or ip address to connect to (const char *). + * The hostname or ip address to connect to. It can be also in + * the format of URI, containing also username, such as + * [username@]hostname. The IPv6 addresses can be enclosed + * within square braces, for example [::1]. The IPv4 address + * supports any format supported by OS. The hostname needs to be + * encoded to match RFC1035, so for IDN it needs to be encoded + * in punycode. + * (const char *). * * - SSH_OPTIONS_PORT: - * The port to connect to (unsigned int *). + * The port to connect to (unsigned int). * * - SSH_OPTIONS_PORT_STR: * The port to connect to (const char *). @@ -347,12 +399,29 @@ int ssh_options_set_algo(ssh_session session, * the identity list.\n * \n * By default id_rsa, id_ecdsa and id_ed25519 files are used.\n + * If libssh is built with FIDO2/U2F support, id_ecdsa_sk and\n + * id_ed25519_sk files are also used by default.\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). * @@ -470,6 +539,20 @@ int ssh_options_set_algo(ssh_session session, * Set the command to be executed in order to connect to * server (const char *). * + * - SSH_OPTIONS_PROXYJUMP: + * Set the comma separated jump hosts in order to connect to + * server (const char *). Set to "none" to disable. + * Example: + * "alice@127.0.0.1:5555,bob@127.0.0.2" + * + * If environment variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * handled by the OpenSSH binary. + * + * - SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + * Append the callbacks struct for a jump in order of + * SSH_OPTIONS_PROXYJUMP. Append as many times + * as the number of jumps (struct ssh_jump_callbacks_struct *). + * * - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY * Set it to specify the GSSAPI server identity that libssh * should expect when connecting to the server (const char *). @@ -482,6 +565,16 @@ int ssh_options_set_algo(ssh_session session, * Set it to specify that GSSAPI should delegate credentials * to the server (int, 0 = false). * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set to true to allow GSSAPI key exchange (bool). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used (const char *, + * comma-separated list). ex: + * "gss-curve25519-sha256-,gss-nistp256-sha256-" + * These will prefix the default algorithms if + * SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true. + * * - SSH_OPTIONS_PASSWORD_AUTH * Set it if password authentication should be used * in ssh_userauth_auto_pubkey(). (int, 0=false). @@ -529,17 +622,17 @@ int ssh_options_set_algo(ssh_session session, * - 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 + * The values under 1024 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 *) - + * Default is 3072 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. @@ -547,7 +640,7 @@ int ssh_options_set_algo(ssh_session session, * * - SSH_OPTIONS_CONTROL_MASTER * Set the option to enable the sharing of multiple sessions over a - * single network connection using connection multiplexing. + * single network connection using connection multiplexing (int). * * The possible options are among the following: * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible @@ -564,22 +657,54 @@ int ssh_options_set_algo(ssh_session session, * Set to "none" to disable connection sharing. * (const char *) * + * - SSH_OPTIONS_PKI_CONTEXT + * Attach a previously created generic PKI context to the + * session. This allows supplying per-session PKI + * configuration options for PKI operations. + * All fields from the user's context are copied to the session's + * own context. The user retains ownership of the original + * context and can free it after this call. + * (ssh_pki_ctx) + * + * - SSH_OPTIONS_ADDRESS_FAMILY + * Specify which address family to use when connecting. + * + * Possible options: + * - SSH_ADDRESS_FAMILY_ANY: use any address family + * - SSH_ADDRESS_FAMILY_INET: IPv4 only + * - SSH_ADDRESS_FAMILY_INET6: IPv6 only * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. * * @return 0 on success, < 0 on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char * in case of strings, ssh_key in case of a + * libssh key), the value parameter should be that pointer. + * Do NOT pass a pointer to a pointer (const char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int *, + * unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its not a + * pointer when it should have been a pointer, or if its a pointer + * to a pointer when it should have just been a pointer), then the + * behaviour is undefined. */ int ssh_options_set(ssh_session session, enum ssh_options_e type, const void *value) { - const char *v; - char *p, *q; + const char *v = NULL; + char *p = NULL, *q = NULL; long int i; unsigned int u; int rc; char **wanted_methods = session->opts.wanted_methods; + struct ssh_jump_callbacks_struct *j = NULL; if (session == NULL) { return -1; @@ -592,33 +717,19 @@ 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) { + ssh_set_error_invalid(session); return -1; } - p = strrchr(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; @@ -711,6 +822,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: @@ -753,6 +869,22 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, 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; + } + break; case SSH_OPTIONS_KNOWNHOSTS: v = value; SAFE_FREE(session->opts.knownhosts); @@ -775,7 +907,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, SAFE_FREE(session->opts.global_knownhosts); if (v == NULL) { session->opts.global_knownhosts = - strdup("/etc/ssh/ssh_known_hosts"); + strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); if (session->opts.global_knownhosts == NULL) { ssh_set_error_oom(session); return -1; @@ -1059,6 +1191,31 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } } break; + case SSH_OPTIONS_PROXYJUMP: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_config_parse_proxy_jump(session, v, true); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + break; + case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + j = (struct ssh_jump_callbacks_struct *)value; + if (j == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_list_prepend(session->opts.proxy_jumps_user_cb, j); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + break; case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: v = value; if (v == NULL || v[0] == '\0') { @@ -1097,6 +1254,37 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.gss_delegate_creds = (x & 0xff); } break; +#ifdef WITH_GSSAPI + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.gssapi_key_exchange = *x; + } + break; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + /* Check if algorithms are supported */ + char *ret = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v); + if (ret == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI key exchange algorithms not " + "supported or invalid"); + return -1; + } + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = ret; + } + break; +#endif case SSH_OPTIONS_PASSWORD_AUTH: case SSH_OPTIONS_PUBKEY_AUTH: case SSH_OPTIONS_KBDINT_AUTH: @@ -1168,10 +1356,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, 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); + + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); return -1; } session->opts.rsa_min_size = *x; @@ -1233,6 +1432,34 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, } } break; + case SSH_OPTIONS_PKI_CONTEXT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_PKI_CTX_FREE(session->pki_context); + + session->pki_context = ssh_pki_ctx_dup((const ssh_pki_ctx)value); + if (session->pki_context == NULL) { + ssh_set_error_oom(session); + return -1; + } + break; + case SSH_OPTIONS_ADDRESS_FAMILY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < SSH_ADDRESS_FAMILY_ANY || + *x > SSH_ADDRESS_FAMILY_INET6) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.address_family = *x; + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -1242,6 +1469,46 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return 0; } +/** + * @brief This function returns the current algorithms used for algorithm + * negotiation. It is either libssh default, option manually set or option + * read from configuration file. + * + * This function will return NULL on error + * + * @param session An allocated SSH session structure. + * @param algo One of the ssh_kex_types_e values. + */ +char *ssh_options_get_algo(ssh_session session, + enum ssh_kex_types_e algo) +{ + char *value = NULL; + + /* Check session and algo values are valid */ + + if (session == NULL) { + return NULL; + } + + if (algo >= SSH_LANG_C_S) { + ssh_set_error_invalid(session); + return NULL; + } + + /* Get the option the user has set, if there is one */ + value = session->opts.wanted_methods[algo]; + if (value == NULL) { + /* The user has not set a value, return the appropriate default */ + if (ssh_fips_mode()) + value = (char *)ssh_kex_get_fips_methods(algo); + else + value = (char *)ssh_kex_get_default_methods(algo); + } + + return value; +} + + /** * @brief This function can get ssh the ssh port. It must only be used on * a valid ssh session. This function is useful when the session @@ -1294,13 +1561,32 @@ 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 id_rsa, id_ecdsa and id_ed25519 files are used. + * By default `id_rsa`, `id_ecdsa`, `id_ed25519`, `id_ecdsa_sk` + * and `id_ed25519_sk` (when SK support is built in) files are + * used. + * + * - SSH_OPTIONS_NEXT_IDENTITY: + * Get the next identity file name (const char *).\n + * \n + * Repeat calls to get all key paths. SSH_EOF is returned when + * the end of list is reached. Another call will start another + * iteration over the same list. * * - SSH_OPTIONS_PROXYCOMMAND: * Get the proxycommand necessary to log into the * remote host. When not explicitly set, it will be read * from the ~/.ssh/config file. * + * - SSH_OPTIONS_IDENTITY_AGENT: + * Get the socket of the authentication agent to use, as it is + * configured by `IdentityAgent`. When not explicitly set, it + * will be read from the ~/.ssh/config file. + * + * - SSH_OPTIONS_IDENTITIES_ONLY: + * Get whether only the configured identities may be used for + * authentication, as "yes" or "no". When not explicitly set, + * it will be read from the ~/.ssh/config file. + * * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: * Get the path to the global known_hosts file being used. * @@ -1308,7 +1594,44 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * 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. + * Get the path to the control socket being used for connection + * multiplexing. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Get the key exchange methods to be used. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HOSTKEYS: + * Get the preferred server host key types. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Get the preferred public key algorithms to be used for + * authentication. + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Get the symmetric cipher client to server. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Get the symmetric cipher server to client. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_C_S: + * Get the Message Authentication Code algorithm client to server + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_S_C: + * Get the Message Authentication Code algorithm server to client + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Get the compression to use for client to server communication + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Get the compression to use for server to client communication + * If the option has not been set, returns the defaults. * * @param value The value to get into. As a char**, space will be * allocated by the function for the value, it is @@ -1319,7 +1642,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { */ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) { - char* src = NULL; + char *src = NULL; if (session == NULL) { return SSH_ERROR; @@ -1332,16 +1655,16 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) switch(type) { - case SSH_OPTIONS_HOST: { + case SSH_OPTIONS_HOST: src = session->opts.host; break; - } - case SSH_OPTIONS_USER: { + + case SSH_OPTIONS_USER: src = session->opts.username; break; - } + case SSH_OPTIONS_IDENTITY: { - struct ssh_iterator *it; + struct ssh_iterator *it = NULL; it = ssh_list_get_iterator(session->opts.identity); if (it == NULL) { it = ssh_list_get_iterator(session->opts.identity_non_exp); @@ -1352,22 +1675,91 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) src = ssh_iterator_value(char *, it); break; } - case SSH_OPTIONS_PROXYCOMMAND: { - src = session->opts.ProxyCommand; + + case SSH_OPTIONS_NEXT_IDENTITY: { + if (session->opts.identity_it != NULL) { + /* Move to the next item */ + session->opts.identity_it = session->opts.identity_it->next; + if (session->opts.identity_it == NULL) { + *value = NULL; + return SSH_EOF; + } + } else { + /* Get iterator from opts */ + struct ssh_iterator *it = NULL; + 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; + } + session->opts.identity_it = it; + } + src = ssh_iterator_value(char *, session->opts.identity_it); break; } - case SSH_OPTIONS_KNOWNHOSTS: { + + case SSH_OPTIONS_PROXYCOMMAND: + src = session->opts.ProxyCommand; + break; + + case SSH_OPTIONS_IDENTITY_AGENT: + src = session->opts.agent_socket; + break; + + case SSH_OPTIONS_IDENTITIES_ONLY: + /* This option is a boolean, it is reported the same way it is spelled in the configuration file. */ + src = (char *)(session->opts.identities_only ? "yes" : "no"); + break; + + case SSH_OPTIONS_KNOWNHOSTS: src = session->opts.knownhosts; break; - } - case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: { + + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: src = session->opts.global_knownhosts; break; - } - case SSH_OPTIONS_CONTROL_PATH: { + case SSH_OPTIONS_CONTROL_PATH: src = session->opts.control_path; break; - } + + case SSH_OPTIONS_CIPHERS_C_S: + src = ssh_options_get_algo(session, SSH_CRYPT_C_S); + break; + + case SSH_OPTIONS_CIPHERS_S_C: + src = ssh_options_get_algo(session, SSH_CRYPT_S_C); + break; + + case SSH_OPTIONS_KEY_EXCHANGE: + src = ssh_options_get_algo(session, SSH_KEX); + break; + + case SSH_OPTIONS_HOSTKEYS: + src = ssh_options_get_algo(session, SSH_HOSTKEYS); + break; + + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + src = session->opts.pubkey_accepted_types; + break; + + case SSH_OPTIONS_HMAC_C_S: + src = ssh_options_get_algo(session, SSH_MAC_C_S); + break; + + case SSH_OPTIONS_HMAC_S_C: + src = ssh_options_get_algo(session, SSH_MAC_S_C); + break; + + case SSH_OPTIONS_COMPRESSION_C_S: + src = ssh_options_get_algo(session, SSH_COMP_C_S); + break; + + case SSH_OPTIONS_COMPRESSION_S_C: + src = ssh_options_get_algo(session, SSH_COMP_S_C); + break; + default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return SSH_ERROR; @@ -1426,6 +1818,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) int compress = 0; int cont = 1; size_t current = 0; + int opt_rc = 0; int saveoptind = optind; /* need to save 'em */ int saveopterr = opterr; int opt; @@ -1436,7 +1829,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:r12")) != -1) { + while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) { switch(opt) { case 'l': user = optarg; @@ -1446,6 +1839,7 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) break; case 'v': debuglevel++; + ssh_set_log_level(debuglevel); break; case 'r': break; @@ -1458,6 +1852,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) case 'C': compress++; break; + case 'o': + opt_rc = ssh_config_parse_line_cli(session, optarg); + break; case '2': break; case '1': @@ -1489,6 +1886,9 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) } } } /* switch */ + if (opt_rc == SSH_ERROR) { + break; + } } /* while */ opterr = saveopterr; tmp = realloc(save, (current + (argc - optind)) * sizeof(char*)); @@ -1511,10 +1911,13 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) optind++; } - ssh_set_log_level(debuglevel); - optind = saveoptind; + if (opt_rc == SSH_ERROR) { + SAFE_FREE(save); + return SSH_ERROR; + } + if(!cont) { SAFE_FREE(save); return -1; @@ -1580,6 +1983,8 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) * * @param filename The options file to use, if NULL the default * ~/.ssh/config and /etc/ssh/ssh_config will be used. + * If complied with support for hermetic-usr, + * /usr/etc/ssh/ssh_config will be used last. * * @return 0 on success, < 0 on error. * @@ -1587,53 +1992,73 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) */ int ssh_options_parse_config(ssh_session session, const char *filename) { - char *expanded_filename; - int r; + char *expanded_filename = NULL; + int r; + FILE *fp = NULL; - if (session == NULL) { - return -1; - } - if (session->opts.host == NULL) { - ssh_set_error_invalid(session); - return -1; - } - - if (session->opts.sshdir == NULL) { - r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); - if (r < 0) { - ssh_set_error_oom(session); - return -1; - } - } - - /* set default filename */ - if (filename == NULL) { - expanded_filename = ssh_path_expand_escape(session, "%d/config"); - } else { - expanded_filename = ssh_path_expand_escape(session, filename); - } - if (expanded_filename == NULL) { - return -1; - } - - r = ssh_config_parse_file(session, expanded_filename); - if (r < 0) { - goto out; - } - if (filename == NULL) { - r = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); - } - - /* Do not process the default configuration as part of connection again */ - session->opts.config_processed = true; + if (session == NULL) { + return -1; + } + if (session->opts.host == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->opts.sshdir == NULL) { + r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (r < 0) { + ssh_set_error_oom(session); + return -1; + } + } + + /* set default filename */ + if (filename == NULL) { + expanded_filename = ssh_path_expand_escape(session, "%d/.ssh/config"); + } else { + expanded_filename = ssh_path_expand_escape(session, filename); + } + if (expanded_filename == NULL) { + return -1; + } + + r = ssh_config_parse_file(session, expanded_filename); + if (r < 0) { + goto out; + } + if (filename == NULL) { + fp = ssh_strict_fopen(GLOBAL_CLIENT_CONFIG, SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = GLOBAL_CLIENT_CONFIG; +#ifdef USR_GLOBAL_CLIENT_CONFIG + } else { + fp = ssh_strict_fopen(USR_GLOBAL_CLIENT_CONFIG, + SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = USR_GLOBAL_CLIENT_CONFIG; + } +#endif + } + + if (fp) { + SSH_LOG(SSH_LOG_PACKET, + "Reading configuration data from %s", + filename); + r = ssh_config_parse(session, fp, true); + fclose(fp); + } + } + + /* Do not process the default configuration as part of connection again */ + session->opts.config_processed = true; out: - free(expanded_filename); - return r; + free(expanded_filename); + return r; } int ssh_options_apply(ssh_session session) { - char *tmp; + char *tmp = NULL; int rc; if (session->opts.sshdir == NULL) { @@ -1652,7 +2077,7 @@ int ssh_options_apply(ssh_session session) 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"); + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); } else { tmp = ssh_path_expand_escape(session, session->opts.knownhosts); } @@ -1666,7 +2091,7 @@ int ssh_options_apply(ssh_session session) 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"); + tmp = strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); } else { tmp = ssh_path_expand_escape(session, session->opts.global_knownhosts); @@ -1734,10 +2159,10 @@ int ssh_options_apply(ssh_session session) * it with ssh expansion of ssh escape characters. */ tmp = ssh_path_expand_escape(session, id); + free(id); if (tmp == NULL) { return -1; } - free(id); } /* use append to keep the order at first call and use prepend @@ -1748,11 +2173,40 @@ int ssh_options_apply(ssh_session session) rc = ssh_list_append(session->opts.identity, tmp); } if (rc != SSH_OK) { + free(tmp); 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); + free(id); + if (tmp == NULL) { + return -1; + } + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + free(tmp); + return -1; + } + } + +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + rc = ssh_gssapi_check_client_config(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange"); + session->opts.gssapi_key_exchange = false; + } + } +#endif + return 0; } @@ -1824,13 +2278,13 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * Set the IP address to bind (const char *). * * - SSH_BIND_OPTIONS_BINDPORT: - * Set the port to bind (unsigned int *). + * Set the port to bind (unsigned int). * * - SSH_BIND_OPTIONS_BINDPORT_STR: * Set the port to bind (const char *). * * - SSH_BIND_OPTIONS_LOG_VERBOSITY: - * Set the session logging verbosity (int *). + * Set the session logging verbosity (int). * The logging verbosity should have one of the * following values, which are listed in order * of increasing verbosity. Every log message @@ -1839,7 +2293,8 @@ 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 information, packet level + * - SSH_LOG_PACKET: Lower level protocol information, + * packet level * - SSH_LOG_FUNCTIONS: Every function path * The default is SSH_LOG_NOLOG. * @@ -1848,8 +2303,8 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * string that will be converted to a numerical * value (e.g. "3") and interpreted according * to the values of - * SSH_BIND_OPTIONS_LOG_VERBOSITY above (const - * char *). + * SSH_BIND_OPTIONS_LOG_VERBOSITY above + * (const char *). * * - SSH_BIND_OPTIONS_RSAKEY: * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY @@ -1866,19 +2321,24 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * This is DEPRECATED, please do not use. * * - SSH_BIND_OPTIONS_IMPORT_KEY: - * Set the Private Key for the server directly (ssh_key) + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). + * + * - SSH_BIND_OPTIONS_IMPORT_KEY_STR: + * Set the Private key for the server from a + * base64 encoded buffer (const char *). * * - SSH_BIND_OPTIONS_CIPHERS_C_S: - * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * Set the symmetric cipher client to server + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_CIPHERS_S_C: - * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * Set the symmetric cipher server to client + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_KEY_EXCHANGE: - * Set the key exchange method to be used (const char *, - * comma-separated list). ex: + * Set the key exchange method to be used + * (const char *, comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1" * * - SSH_BIND_OPTIONS_HMAC_C_S: @@ -1922,239 +2382,249 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - 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 + * operations. The values under 1024 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. + * Default is 3072 bits or 2048 bits in FIPS mode. * (int) * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set true to enable GSSAPI key exchange, + * false to disable GSSAPI key exchange. (bool) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used + * (const char *, comma-separated list). + * ex: "gss-group14-sha256-,gss-group16-sha512-" * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the * corresponding value of type above. * - * @return 0 on success, < 0 on error, invalid option, or parameter. + * @return 0 on success, < 0 on error, invalid option, or + * parameter. + * + * @warning When the option value to set is represented via a + * pointer (e.g const char * in case of strings, ssh_key + * in case of a libssh key), the value parameter should be + * that pointer. Do NOT pass a pointer to a pointer (const + * char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int + * *, unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its + * not a pointer when it should have been a pointer, or if + * its a pointer to a pointer when it should have just been + * a pointer), then the behaviour is undefined. */ -int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, - const void *value) +int +ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, + const void *value) { - bool allowed; - char *p, *q; - const char *v; - int i, rc; - char **wanted_methods = sshbind->wanted_methods; + bool allowed; + char *p = NULL, *q = NULL; + const char *v = NULL; + int i, rc; + char **wanted_methods = sshbind->wanted_methods; - if (sshbind == NULL) { - return -1; - } + if (sshbind == NULL) { + return -1; + } - switch (type) { + switch (type) { case SSH_BIND_OPTIONS_RSAKEY: case SSH_BIND_OPTIONS_ECDSAKEY: /* deprecated */ case SSH_BIND_OPTIONS_HOSTKEY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int key_type; - ssh_key key; - ssh_key *bind_key_loc = NULL; - char **bind_key_path_loc; - - rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); - if (rc != SSH_OK) { - return -1; - } - 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_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: -#ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; - bind_key_path_loc = &sshbind->ecdsakey; -#else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); -#endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - bind_key_path_loc = &sshbind->rsakey; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - bind_key_path_loc = &sshbind->ed25519key; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); - } - - if (bind_key_loc == NULL) { - ssh_key_free(key); - return -1; - } - - /* Set the location of the key on disk even though we don't - need it in case some other function wants it */ - rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); - if (rc < 0) { - ssh_key_free(key); - return -1; - } - ssh_key_free(*bind_key_loc); - *bind_key_loc = key; - } - break; case SSH_BIND_OPTIONS_IMPORT_KEY: + case SSH_BIND_OPTIONS_IMPORT_KEY_STR: if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; } else { int key_type; ssh_key *bind_key_loc = NULL; - ssh_key key = (ssh_key)value; - + ssh_key key = NULL; + char **bind_key_path_loc = NULL; + + if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + const char *key_str = (const char *)value; + rc = ssh_pki_import_privkey_base64(key_str, + NULL, + NULL, + NULL, + &key); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, + SSH_FATAL, + "Failed to import key from buffer"); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) { + key = (ssh_key)value; + } else { + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } + } allowed = ssh_bind_key_size_allowed(sshbind, key); if (!allowed) { ssh_set_error(sshbind, SSH_FATAL, "The host key size %d is too small.", ssh_key_size(key)); + if (type != SSH_BIND_OPTIONS_IMPORT_KEY) { + SSH_KEY_FREE(key); + } return -1; } - key_type = ssh_key_type(key); switch (key_type) { - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; + bind_key_loc = &sshbind->ecdsa; + bind_key_path_loc = &sshbind->ecdsakey; #else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); #endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + bind_key_path_loc = &sshbind->rsakey; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); + } + if (type == SSH_BIND_OPTIONS_RSAKEY || + type == SSH_BIND_OPTIONS_ECDSAKEY || + type == SSH_BIND_OPTIONS_HOSTKEY) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + } else { + if (bind_key_loc == NULL) { + return -1; + } } - if (bind_key_loc == NULL) - return -1; ssh_key_free(*bind_key_loc); *bind_key_loc = key; } break; case SSH_BIND_OPTIONS_BINDADDR: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->bindaddr); - sshbind->bindaddr = strdup(value); - if (sshbind->bindaddr == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - } - break; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; case SSH_BIND_OPTIONS_BINDPORT: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - sshbind->bindport = *x & 0xffffU; - } - break; - case SSH_BIND_OPTIONS_BINDPORT_STR: - if (value == NULL) { - sshbind->bindport = 22 & 0xffffU; - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - i = strtol(q, &p, 10); - if (q == p) { - SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); - SAFE_FREE(q); + if (value == NULL) { + ssh_set_error_invalid(sshbind); return -1; + } else { + int *x = (int *)value; + sshbind->bindport = *x & 0xffffU; } - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffffU; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); - sshbind->bindport = i & 0xffffU; - } - break; + sshbind->bindport = i & 0xffffU; + } + break; case SSH_BIND_OPTIONS_LOG_VERBOSITY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - ssh_set_log_level(*x & 0xffffU); - } - break; - case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: - if (value == NULL) { - ssh_set_log_level(0); - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - i = strtol(q, &p, 10); - if (q == p) { - SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); - SAFE_FREE(q); + if (value == NULL) { + ssh_set_error_invalid(sshbind); return -1; + } else { + int *x = (int *)value; + ssh_set_log_level(*x & 0xffffU); } - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + ssh_set_log_level(0); + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + 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; + ssh_set_log_level(i & 0xffffU); + } + break; case SSH_BIND_OPTIONS_BANNER: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->banner); - sshbind->banner = strdup(value); - if (sshbind->banner == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - } - break; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; case SSH_BIND_OPTIONS_CIPHERS_C_S: v = value; if (v == NULL || v[0] == '\0') { @@ -2215,7 +2685,7 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } } break; - case SSH_BIND_OPTIONS_HMAC_S_C: + case SSH_BIND_OPTIONS_HMAC_S_C: v = value; if (v == NULL || v[0] == '\0') { ssh_set_error_invalid(sshbind); @@ -2304,22 +2774,65 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, 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); + + if (*x < 0) { + ssh_set_error_invalid(sshbind); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); return -1; } sshbind->rsa_min_size = *x; } break; +#ifdef WITH_GSSAPI + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->gssapi_key_exchange = *x; + } + break; + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + char *ret = NULL; + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value); + if (ret == NULL) { + ssh_set_error( + sshbind, + SSH_REQUEST_DENIED, + "GSSAPI key exchange algorithms not supported or invalid"); + return -1; + } + sshbind->gssapi_key_exchange_algs = ret; + } + break; +#endif /* WITH_GSSAPI */ default: - ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); - return -1; - break; - } + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "Unknown ssh option %d", + type); + return -1; + break; + } - return 0; + return 0; } static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) @@ -2327,7 +2840,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) char *buf = NULL; char *r = NULL; char *x = NULL; - const char *p; + const char *p = NULL; size_t i, l; r = ssh_path_expand_tilde(s); @@ -2433,7 +2946,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename) { int rc = 0; - char *expanded_filename; + char *expanded_filename = NULL; if (sshbind == NULL) { return -1; @@ -2442,7 +2955,13 @@ int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename) /* If the global default configuration hasn't been processed yet, process it * before the provided configuration. */ if (!(sshbind->config_processed)) { - rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG); + if (ssh_file_readaccess_ok(GLOBAL_BIND_CONFIG)) { + rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG); +#ifdef USR_GLOBAL_BIND_CONFIG + } else { + rc = ssh_bind_config_parse_file(sshbind, USR_GLOBAL_BIND_CONFIG); +#endif + } if (rc != 0) { return rc; } diff --git a/src/packet.c b/src/packet.c index 5c811292..0c78a468 100644 --- a/src/packet.c +++ b/src/packet.c @@ -52,9 +52,9 @@ static ssh_packet_callback default_packet_handlers[]= { ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1 - ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 + ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3 - ssh_packet_ignore_callback, // SSH2_MSG_DEBUG 4 + ssh_packet_debug_callback, // SSH2_MSG_DEBUG 4 #if WITH_SERVER ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5 #else @@ -294,6 +294,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or session_state == SSH_SESSION_STATE_INITIAL_KEX * - dh_handshake_state == DH_STATE_INIT * or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange) + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) * * Transitions: @@ -313,6 +314,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se if ((session->dh_handshake_state != DH_STATE_INIT) && (session->dh_handshake_state != DH_STATE_INIT_SENT) && + (session->dh_handshake_state != DH_STATE_REQUEST_SENT) && (session->dh_handshake_state != DH_STATE_FINISHED)) { rc = SSH_PACKET_DENIED; @@ -350,7 +352,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se break; case SSH2_MSG_KEXDH_INIT: // 30 // SSH2_MSG_KEX_ECDH_INIT: // 30 - // SSH2_MSG_ECMQV_INIT: // 30 + // SSH2_MSG_KEX_HYBRID_INIT: // 30 // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: // 30 /* Server only */ @@ -366,7 +368,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ - if (!session->server) { + if (session->client) { rc = SSH_PACKET_DENIED; break; } @@ -386,9 +388,11 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se break; case SSH2_MSG_KEXDH_REPLY: // 31 // SSH2_MSG_KEX_ECDH_REPLY: // 31 - // SSH2_MSG_ECMQV_REPLY: // 31 + // SSH2_MSG_KEX_HYBRID_REPLY: // 31 // SSH2_MSG_KEX_DH_GEX_GROUP: // 31 + /* Client only */ + /* * States required: * - session_state == SSH_SESSION_STATE_DH @@ -399,6 +403,11 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * - 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; @@ -413,15 +422,130 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_KEX_DH_GEX_INIT: // 32 - /* TODO Not filtered */ + // SSH2_MSG_KEXGSS_COMPLETE: // 32 + if (ssh_kex_is_gss(session->next_crypto)) { + /* SSH2_MSG_KEXGSS_COMPLETE */ + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_client_gss_kex_reply which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (!session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } else { + /* SSH2_MSG_KEX_DH_GEX_INIT */ + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_GROUP_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * then calls ssh_packet_server_dhgex_init which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_GROUP_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_KEX_DH_GEX_REPLY: // 33 - /* TODO Not filtered */ + + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - 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; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_KEX_DH_GEX_REQUEST: // 34 - /* TODO Not filtered */ + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_server_dhgex_request which triggers: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_USERAUTH_REQUEST: // 50 @@ -502,6 +626,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT * or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT * * Transitions: @@ -531,8 +656,8 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se (session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && (session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && (session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && - (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) - { + (session->auth.state != SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) { rc = SSH_PACKET_DENIED; break; } @@ -624,17 +749,83 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se rc = SSH_PACKET_ALLOWED; break; case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63 - /* TODO Not filtered */ + /* Server only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi == NULL) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + rc = SSH_PACKET_DENIED; + break; + } rc = SSH_PACKET_ALLOWED; break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64 - /* TODO Not filtered */ + /* Client only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65 - /* TODO Not filtered */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66 /* Server only */ @@ -654,7 +845,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * - any other case: * - None * */ - +#ifdef WITH_GSSAPI /* If this is a client, reject the message */ if (session->client) { rc = SSH_PACKET_DENIED; @@ -673,6 +864,10 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se rc = SSH_PACKET_ALLOWED; break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ case SSH2_MSG_GLOBAL_REQUEST: // 80 /* * States required: @@ -1068,7 +1263,7 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) uint32_t lenfield_blocksize = 8; size_t current_macsize = 0; uint8_t *ptr = NULL; - long to_be_read; + ssize_t to_be_read; int rc; uint8_t *cleartext_packet = NULL; uint8_t *packet_second_block = NULL; @@ -1178,7 +1373,7 @@ 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 (%ld)!", + "Given numbers of bytes left to be read < 0 (%zd)!", to_be_read); goto error; } @@ -1196,7 +1391,7 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET, "packet: partial packet (read len) " - "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%ld]", + "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%zd]", packet_len, receivedlen, to_be_read); @@ -1210,7 +1405,7 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) /* remaining encrypted bytes from the packet, MAC not included */ packet_remaining = packet_len - (packet_offset - sizeof(uint32_t)); cleartext_packet = ssh_buffer_allocate(session->in_buffer, - packet_remaining); + (uint32_t)packet_remaining); if (cleartext_packet == NULL) { goto error; } @@ -1303,7 +1498,6 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) ssh_buffer_pass_bytes_end(session->in_buffer, padding); compsize = ssh_buffer_get_len(session->in_buffer); -#ifdef WITH_ZLIB if (crypto && crypto->do_compress_in && ssh_buffer_get_len(session->in_buffer) > 0) { rc = decompress_buffer(session, session->in_buffer, @@ -1312,8 +1506,20 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) 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; @@ -1338,7 +1544,19 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) "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); @@ -1354,12 +1572,16 @@ 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; } session->packet_state = PACKET_STATE_INIT; if (processed < receivedlen) { + size_t num; /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET, "Processing %zu bytes left in socket buffer", @@ -1367,9 +1589,10 @@ 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); - processed += rc; + num = ssh_packet_socket_callback(ptr, + receivedlen - processed, + user); + processed += num; } ok = ssh_packet_need_rekey(session, 0); @@ -1399,12 +1622,37 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) return processed; } +static void ssh_packet_socket_controlflow_callback(int code, void *userdata) +{ + ssh_session session = userdata; + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; + + if (code == SSH_SOCKET_FLOW_WRITEWONTBLOCK) { + SSH_LOG(SSH_LOG_TRACE, "sending channel_write_wontblock callback"); + + /* the out pipe is empty so we can forward this to channels */ + it = ssh_list_get_iterator(session->channels); + while (it != NULL) { + channel = ssh_iterator_value(ssh_channel, it); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + it = it->next; + } + } +} + void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) { 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); } @@ -1504,9 +1752,35 @@ 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 * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet * @param session the SSH session @@ -1625,7 +1899,6 @@ static int packet_send2(ssh_session session) lenfield_blocksize = 0; } -#ifdef WITH_ZLIB if (crypto != NULL && crypto->do_compress_out && ssh_buffer_get_len(session->out_buffer) > 0) { rc = compress_buffer(session,session->out_buffer); @@ -1634,7 +1907,6 @@ static int packet_send2(ssh_session session) } currentlen = ssh_buffer_get_len(session->out_buffer); } -#endif /* WITH_ZLIB */ compsize = currentlen; /* compressed payload + packet len (4) + padding_size len (1) */ /* totallen - lenfield_blocksize - etm_packet_offset must be equal to 0 (mod blocksize) */ @@ -1689,7 +1961,7 @@ static int packet_send2(ssh_session session) if (hmac != NULL) { rc = ssh_buffer_add_data(session->out_buffer, hmac, - hmac_digest_len(hmac_type)); + (uint32_t)hmac_digest_len(hmac_type)); if (rc < 0) { goto error; } @@ -1815,8 +2087,12 @@ int ssh_packet_send(ssh_session session) /* We finished the key exchange so we can try to send our queue now */ if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { - struct ssh_iterator *it; + struct ssh_iterator *it = NULL; + 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)) { diff --git a/src/packet_cb.c b/src/packet_cb.c index c3b36700..6228b44a 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -27,6 +27,10 @@ #ifdef HAVE_ARPA_INET_H #include #endif +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif #include "libssh/priv.h" #include "libssh/buffer.h" @@ -45,146 +49,243 @@ * * @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); - } - - 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_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; - /* correctly handle disconnect during authorization */ - session->auth.state = SSH_AUTH_STATE_FAILED; - /* 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; } /** * @internal * - * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. + * @brief Handle a SSH_IGNORE 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_DEBUG,"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 SSH_MSG_IGNORE packet"); + + 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_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; - } - - 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; - } +/** + * @internal + * + * @brief Handle a SSH_DEBUG packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_debug_callback) +{ + uint8_t always_display = -1; + char *message = NULL; + int rc; - rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "bs", &always_display, &message); if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading debug message"); + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_DEBUG packet with message %s%s", + message, + always_display != 0 ? " (always display)" : ""); + SAFE_FREE(message); + + 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_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) { - ssh_set_error(session, - SSH_FATAL, - "Failed to verify server hostkey signature"); - goto error; - } - SSH_LOG(SSH_LOG_DEBUG,"Signature verified and valid"); + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { +#ifdef WITH_GSSAPI + if (ssh_kex_is_gss(session->next_crypto)) { + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + goto error; + } - /* 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; + if (session->gssapi_key_exchange_mic == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI mic not set"); + goto error; + } + + mic.length = ssh_string_len(session->gssapi_key_exchange_mic); + mic.value = ssh_string_data(session->gssapi_key_exchange_mic); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &msg, + &mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify mic after GSSAPI Key Exchange"); + goto error; + } + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + } else +#endif + { + ssh_key server_key = NULL; + + /* 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 = 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; +#ifdef WITH_GSSAPI + SSH_STRING_FREE(session->gssapi_key_exchange_mic); +#endif + 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; } /** @@ -192,16 +293,16 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){ * @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; } /** @@ -214,6 +315,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) int rc; uint32_t nr_extensions = 0; uint32_t i; + (void)type; (void)user; @@ -236,7 +338,6 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) for (i = 0; i < nr_extensions; i++) { char *name = NULL; char *value = NULL; - int cmp; rc = ssh_buffer_unpack(packet, "ss", &name, &value); if (rc != SSH_OK) { @@ -244,16 +345,22 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) return SSH_PACKET_USED; } - cmp = strcmp(name, "server-sig-algs"); - if (cmp == 0) { + if (strcmp(name, "server-sig-algs") == 0) { /* TODO check for NULL bytes */ SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); - if (ssh_match_group(value, "rsa-sha2-512")) { + + rc = match_group(value, "rsa-sha2-512"); + if (rc == 1) { session->extensions |= SSH_EXT_SIG_RSA_SHA512; } - if (ssh_match_group(value, "rsa-sha2-256")) { + + rc = match_group(value, "rsa-sha2-256"); + if (rc == 1) { session->extensions |= SSH_EXT_SIG_RSA_SHA256; } + } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND; } else { SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); } diff --git a/src/packet_crypt.c b/src/packet_crypt.c index fe3f489e..12846fe5 100644 --- a/src/packet_crypt.c +++ b/src/packet_crypt.c @@ -235,7 +235,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) #endif } } - explicit_bzero(out, len); + ssh_burn(out, len); SAFE_FREE(out); return crypto->hmacbuf; @@ -262,7 +262,7 @@ int ssh_packet_hmac_verify(ssh_session session, { struct ssh_crypto_struct *crypto = NULL; unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; - HMACCTX ctx; + HMACCTX ctx = NULL; size_t hmaclen = DIGEST_MAX_LEN; uint32_t seq; int cmp; diff --git a/src/pcap.c b/src/pcap.c index 3d295427..1a98e1cd 100644 --- a/src/pcap.c +++ b/src/pcap.c @@ -126,11 +126,10 @@ ssh_pcap_file ssh_pcap_file_new(void) { struct ssh_pcap_file_struct *pcap = NULL; - pcap = malloc(sizeof(struct ssh_pcap_file_struct)); + pcap = calloc(1, sizeof(struct ssh_pcap_file_struct)); if (pcap == NULL) { return NULL; } - ZERO_STRUCTP(pcap); return pcap; } @@ -296,12 +295,13 @@ void ssh_pcap_file_free(ssh_pcap_file pcap) */ 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)); + ssh_pcap_context ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pcap_context_struct)); if (ctx == NULL) { ssh_set_error_oom(session); return NULL; } - ZERO_STRUCTP(ctx); ctx->session = session; return ctx; } diff --git a/src/pki.c b/src/pki.c index 6863c90c..ef90df23 100644 --- a/src/pki.c +++ b/src/pki.c @@ -43,15 +43,18 @@ #include #include +#include "libssh/agent.h" +#include "libssh/buffer.h" +#include "libssh/keys.h" #include "libssh/libssh.h" -#include "libssh/session.h" -#include "libssh/priv.h" +#include "libssh/misc.h" #include "libssh/pki.h" +#include "libssh/pki_context.h" #include "libssh/pki_priv.h" -#include "libssh/keys.h" -#include "libssh/buffer.h" -#include "libssh/misc.h" -#include "libssh/agent.h" +#include "libssh/pki_sk.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/sk_common.h" /* For SK_NOT_SUPPORTED_MSG */ #ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 4096 @@ -116,6 +119,85 @@ ssh_key ssh_key_new (void) return ptr; } +/** + * @internal + * + * @brief Initialize a new SSH key by duplicating common fields from an existing + * key. + * + * This function creates a new SSH key and copies the common fields from the + * source key, including the key type, type string, flags, and security key + * fields if applicable. This is a helper function used by key duplication + * routines. + * + * @param[in] key The source ssh_key to copy common fields from. + * @param[in] demote Whether to demote the new key to public only. If non-zero, + * only the public fields will be copied and the flags will + * be set accordingly. + * + * @return A new ssh_key with common fields initialized, or NULL on + * error. + * + * @note The caller is responsible for freeing the returned key with + * ssh_key_free(). + */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote) +{ + ssh_key new = NULL; + + if (key == NULL) { + return NULL; + } + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + /* Copy security key fields if present */ + if (is_sk_key_type(key->type)) { + new->sk_application = ssh_string_copy(key->sk_application); + if (new->sk_application == NULL) { + goto fail; + } + + if (key->sk_user_id != NULL) { + new->sk_user_id = ssh_string_copy(key->sk_user_id); + if (new->sk_user_id == NULL) { + goto fail; + } + } + + if (!demote) { + new->sk_flags = key->sk_flags; + + new->sk_key_handle = ssh_string_copy(key->sk_key_handle); + if (new->sk_key_handle == NULL) { + goto fail; + } + + new->sk_reserved = ssh_string_copy(key->sk_reserved); + if (new->sk_reserved == NULL) { + goto fail; + } + } + } + + return new; + +fail: + SSH_KEY_FREE(new); + return NULL; +} + /** * @brief duplicates the key * @@ -143,27 +225,26 @@ void ssh_key_clean (ssh_key key) pki_key_clean(key); - if (key->ed25519_privkey != NULL){ -#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 /* HAVE_LIBCRYPTO*/ +#ifndef HAVE_LIBCRYPTO + if (key->ed25519_privkey != NULL) { + ssh_burn(key->ed25519_privkey, sizeof(ed25519_privkey)); SAFE_FREE(key->ed25519_privkey); } SAFE_FREE(key->ed25519_pubkey); +#endif /* HAVE_LIBCRYPTO */ if (key->cert != NULL) { SSH_BUFFER_FREE(key->cert); } - if (key->type == SSH_KEYTYPE_SK_ECDSA || - key->type == SSH_KEYTYPE_SK_ED25519 || - key->type == SSH_KEYTYPE_SK_ECDSA_CERT01 || - key->type == SSH_KEYTYPE_SK_ED25519_CERT01) { + if (is_sk_key_type(key->type)) { ssh_string_burn(key->sk_application); ssh_string_free(key->sk_application); + ssh_string_burn(key->sk_key_handle); + ssh_string_free(key->sk_key_handle); + ssh_string_burn(key->sk_reserved); + ssh_string_free(key->sk_reserved); + ssh_string_burn(key->sk_user_id); + ssh_string_free(key->sk_user_id); + key->sk_flags = 0; } key->cert_type = SSH_KEYTYPE_UNKNOWN; key->flags = SSH_KEY_FLAG_EMPTY; @@ -203,6 +284,74 @@ enum ssh_keytypes_e ssh_key_type(const ssh_key key) return key->type; } +/** + * @brief Get security key (FIDO2) flags for a security key backed ssh_key. + * + * The returned value contains a bitmask of SSH_SK_* flags (e.g. + * SSH_SK_USER_PRESENCE_REQD, SSH_SK_USER_VERIFICATION_REQD, etc.). + * If NULL is passed, then 0 is returned. + * + * @param[in] key The ssh_key handle. + * + * @return Bitmask of security key flags, or 0 if not applicable. + */ +uint32_t ssh_key_get_sk_flags(const ssh_key key) +{ + if (key == NULL) { + return 0; + } + return key->sk_flags; +} + +/** + * @brief Get the application (RP ID) associated with a security key. + * + * This function returns a freshly allocated ssh_string containing a copy of the + * application (RP ID). The caller owns the returned ssh_string and must free it + * with SSH_STRING_FREE() when no longer needed. + * + * Returns NULL if the key is NULL, not a security key type or if the field is + * not set. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of the application (RP ID) or NULL if not available. + */ +ssh_string ssh_key_get_sk_application(const ssh_key key) +{ + if (key == NULL || key->sk_application == NULL) { + return NULL; + } + + return ssh_string_copy(key->sk_application); +} + +/** + * @brief Get a copy of the user ID associated with a resident security key + * credential. + * + * For resident (discoverable) credentials, authenticators may provide a user + * id which can be arbitrary binary data to allow for storing multiple keys for + * the same Relying Party. This function returns a freshly allocated ssh_string + * containing a copy of that user id. The caller owns the returned ssh_string + * and must free it with SSH_STRING_FREE() when no longer needed. + * + * @note This function will only return useful information if the ssh_key + * passed represents a resident key loaded using the ssh_sk_resident_keys_load() + * function. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of user id or NULL if not available. + */ +ssh_string ssh_key_get_sk_user_id(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + return ssh_string_copy(key->sk_user_id); +} + /** * @brief Convert a signature type to a string. * @@ -344,7 +493,7 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) */ int ssh_key_algorithm_allowed(ssh_session session, const char *type) { - const char *allowed_list; + const char *allowed_list = NULL; if (session->client) { allowed_list = session->opts.pubkey_accepted_types; @@ -371,18 +520,18 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) } SSH_LOG(SSH_LOG_DEBUG, "Checking %s with list <%s>", type, allowed_list); - return ssh_match_group(allowed_list, type); + return 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 (min_size < RSA_MIN_KEY_SIZE) { if (ssh_fips_mode()) { - min_size = 2048; + min_size = RSA_MIN_FIPS_KEY_SIZE; } else { - min_size = 1024; + min_size = RSA_MIN_KEY_SIZE; } } return (key_size >= min_size); @@ -410,6 +559,61 @@ bool ssh_key_size_allowed(ssh_session session, ssh_key key) } } +/** + * @brief Helper function to convert a key type to a hash type. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + * + * @warning This helper function is available for use without session (for + * example for signing commits) and might cause interoperability issues + * when used within session! It is recommended to use + * ssh_key_type_to_hash() instead of this helper directly when a + * session is available. + * + * @note In order to follow current security best practises for RSA, defaults + * to SHA-2 with SHA-512 digest (RFC8332) instead of the default for + * the SSH protocol (SHA1 with RSA ; RFC 4253). + * + * @see ssh_key_type_to_hash() + */ +static enum ssh_digest_e key_type_to_hash(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA: + return SSH_DIGEST_SHA256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + return SSH_DIGEST_SHA384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_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 " + "is not defined", + type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + /** * @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 @@ -453,26 +657,8 @@ enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, /* Default algorithm for RSA is SHA1 */ return SSH_DIGEST_SHA1; - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P256: - return SSH_DIGEST_SHA256; - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P384: - return SSH_DIGEST_SHA384; - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_ECDSA_P521: - return SSH_DIGEST_SHA512; - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_ED25519: - return SSH_DIGEST_AUTO; - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_DSS: /* deprecated */ - case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ - case SSH_KEYTYPE_ECDSA: - case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_TRACE, "Digest algorithm to be used with key type %u " - "is not defined", type); + return key_type_to_hash(type); } /* We should never reach this */ @@ -586,7 +772,7 @@ 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. * @@ -648,6 +834,10 @@ int ssh_key_is_private(const ssh_key k) { /** * @brief Compare keys if they are equal. * + * Note that comparing private keys is almost never needed. The private key + * is cryptographically bound to the public key and comparing public keys should + * always be preferred. + * * @param[in] k1 The first key to compare. * * @param[in] k2 The second key to compare. @@ -664,7 +854,7 @@ int ssh_key_cmp(const ssh_key k1, return 1; } - if (k1->type != k2->type) { + 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; } @@ -676,32 +866,65 @@ int ssh_key_cmp(const ssh_key k1, } } - if (k1->type == SSH_KEYTYPE_SK_ECDSA || - k1->type == SSH_KEYTYPE_SK_ED25519) { - if (strncmp(ssh_string_get_char(k1->sk_application), - ssh_string_get_char(k2->sk_application), - ssh_string_len(k2->sk_application)) != 0) { + if (is_sk_key_type(k1->type)) { + if (ssh_string_cmp(k1->sk_application, k2->sk_application) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_user_id, k2->sk_user_id) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (k1->sk_flags != k2->sk_flags) { + return 1; + } + + if (ssh_string_cmp(k1->sk_key_handle, k2->sk_key_handle) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_reserved, k2->sk_reserved) != 0) { + return 1; + } + } + } + + 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) { +#ifndef HAVE_LIBCRYPTO + if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); + } else if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_SK_ED25519) { + return pki_ed25519_key_cmp(k1, k2, SSH_KEY_CMP_PUBLIC); } +#endif return pki_key_compare(k1, k2, what); } ssh_signature ssh_signature_new(void) { - struct ssh_signature_struct *sig; + struct ssh_signature_struct *sig = NULL; - sig = malloc(sizeof(struct ssh_signature_struct)); + sig = calloc(1, sizeof(struct ssh_signature_struct)); if (sig == NULL) { return NULL; } - ZERO_STRUCTP(sig); return sig; } @@ -783,7 +1006,7 @@ int ssh_pki_import_privkey_base64(const char *b64_key, void *auth_data, ssh_key *pkey) { - ssh_key key; + ssh_key key = NULL; char *openssh_header = NULL; if (b64_key == NULL || pkey == NULL) { @@ -820,9 +1043,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. * @@ -836,15 +1060,19 @@ int ssh_pki_import_privkey_base64(const char *b64_key, * @param[out] b64_key A pointer to store the allocated base64 encoded key. You * 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; @@ -853,16 +1081,37 @@ 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. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + 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; @@ -879,6 +1128,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. * @@ -906,8 +1191,8 @@ int ssh_pki_import_privkey_file(const char *filename, void *auth_data, ssh_key *pkey) { struct stat sb; - char *key_buf; - FILE *file; + char *key_buf = NULL; + FILE *file = NULL; off_t size; int rc; char err_msg[SSH_ERRNO_MSG_MAX] = {0}; @@ -986,8 +1271,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. * @@ -1000,16 +1284,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)) { @@ -1024,16 +1313,38 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, 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. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + + 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,13 +1363,45 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, return SSH_OK; } -/* 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) { +/** + * @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 = NULL; + ssh_key tmp = NULL; + + if (key == NULL) { return NULL; } @@ -1076,12 +1419,15 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) pub->type = tmp->type; pub->type_c = tmp->type_c; -#ifndef HAVE_LIBCRYPTO - pub->rsa_pub = tmp->rsa; - tmp->rsa = NULL; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + pub->rsa_pub = tmp->pk; + tmp->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) pub->key_pub = tmp->key; tmp->key = NULL; +#else + pub->rsa_pub = tmp->rsa; + tmp->rsa = NULL; #endif /* HAVE_LIBCRYPTO */ ssh_key_free(tmp); @@ -1091,7 +1437,7 @@ 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) { - ssh_private_key privkey; + ssh_private_key privkey = NULL; privkey = calloc(1, sizeof(struct ssh_private_key_struct)); if (privkey == NULL) { @@ -1100,10 +1446,12 @@ ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) } privkey->type = key->type; -#ifndef HAVE_LIBCRYPTO - privkey->rsa_priv = key->rsa; -#else +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; +#elif defined(HAVE_LIBCRYPTO) privkey->key_priv = key->key; +#else + privkey->rsa_priv = key->rsa; #endif /* HAVE_LIBCRYPTO */ return privkey; @@ -1126,122 +1474,178 @@ 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_RSA: - { - ssh_string n = NULL; - ssh_string e = NULL; - ssh_string d = NULL; - ssh_string iqmp = NULL; - ssh_string p = NULL; - ssh_string q = NULL; - - rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, - &iqmp, &p, &q); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_TRACE, "Unpack error"); - goto fail; - } + case SSH_KEYTYPE_RSA: { + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + + rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, &iqmp, &p, &q); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } - rc = pki_privkey_build_rsa(key, n, e, d, iqmp, p, q); + rc = pki_privkey_build_rsa(key, n, e, d, iqmp, p, q); #ifdef DEBUG_CRYPTO - ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); - ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); - ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); - ssh_log_hexdump("iqmp", ssh_string_data(iqmp), - ssh_string_len(iqmp)); - ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); - ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); + ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); + ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); + ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); + ssh_log_hexdump("iqmp", ssh_string_data(iqmp), ssh_string_len(iqmp)); + ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); + ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); #endif /* DEBUG_CRYPTO */ - ssh_string_burn(n); - SSH_STRING_FREE(n); - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(d); - SSH_STRING_FREE(d); - ssh_string_burn(iqmp); - SSH_STRING_FREE(iqmp); - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - if (rc == SSH_ERROR) { - SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); - goto fail; - } - } - break; + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); + goto fail; + } + break; + } #ifdef HAVE_ECC - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - { - ssh_string e = NULL; - ssh_string exp = NULL; - ssh_string i = NULL; - int nid; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string e = NULL; + ssh_string exp = NULL; + ssh_string i = NULL; + int nid; - rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_TRACE, "Unpack error"); - goto fail; - } + rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } - nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); - SSH_STRING_FREE(i); - if (nid == -1) { - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(exp); - SSH_STRING_FREE(exp); - goto fail; - } + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + goto fail; + } - rc = pki_privkey_build_ecdsa(key, nid, e, exp); - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(exp); - SSH_STRING_FREE(exp); - if (rc < 0) { - SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); - goto fail; - } - } - break; + rc = pki_privkey_build_ecdsa(key, nid, e, exp); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ECDSA: { + ssh_string type_str = NULL; + ssh_string pubkey = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &type_str, &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(type_str); + SSH_STRING_FREE(pubkey); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(type_str)); + SSH_STRING_FREE(type_str); + + if (nid == -1) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } #endif /* HAVE_ECC */ - case SSH_KEYTYPE_ED25519: - { - ssh_string pubkey = NULL, privkey = NULL; + 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_TRACE, "Unpack error"); - goto fail; - } + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } - rc = pki_privkey_build_ed25519(key, pubkey, privkey); - ssh_string_burn(privkey); - SSH_STRING_FREE(privkey); - SSH_STRING_FREE(pubkey); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); - goto fail; - } - } - break; - 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_SK_ECDSA: - case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_SK_ED25519: - case SSH_KEYTYPE_SK_ED25519_CERT01: - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); + rc = ssh_buffer_unpack(buffer, "SS", &pubkey, &privkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_ed25519(key, pubkey, privkey); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ED25519: { + ssh_string pubkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); goto fail; + } + + rc = ssh_buffer_unpack(buffer, "S", &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } + 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_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); + goto fail; } *pkey = key; @@ -1328,11 +1732,6 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, goto fail; } - /* Update key type */ - if (type == SSH_KEYTYPE_ECDSA) { - key->type_c = ssh_pki_key_ecdsa_name(key); - } - /* Unpack SK specific parameters */ if (type == SSH_KEYTYPE_SK_ECDSA) { ssh_string application = ssh_buffer_get_ssh_string(buffer); @@ -1358,16 +1757,13 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, goto fail; } - key->ed25519_pubkey = malloc(ED25519_KEY_LEN); - if (key->ed25519_pubkey == NULL) { - ssh_string_burn(pubkey); - SSH_STRING_FREE(pubkey); - goto fail; - } - - memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + rc = pki_pubkey_build_ed25519(key, pubkey); ssh_string_burn(pubkey); SSH_STRING_FREE(pubkey); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ED25519 public key"); + goto fail; + } if (type == SSH_KEYTYPE_SK_ED25519) { ssh_string application = ssh_buffer_get_ssh_string(buffer); @@ -1389,7 +1785,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_TRACE, "Unknown public key protocol %d", type); + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type %d", type); goto fail; } @@ -1405,9 +1801,9 @@ static int pki_import_cert_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, ssh_key *pkey) { - ssh_buffer cert; - ssh_string tmp_s; - const char *type_c; + ssh_buffer cert = NULL; + ssh_string tmp_s = NULL; + const char *type_c = NULL; ssh_key key = NULL; int rc; @@ -1476,7 +1872,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; @@ -1490,14 +1886,16 @@ static int pki_import_cert_buffer(ssh_buffer buffer, /** * @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. + * Note that the public key is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH public + * key file or known_hosts file). * + * @param[in] b64_key The base64 key to import. + * @param[in] type The type of the key to import. * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_key_free() */ @@ -1568,8 +1966,9 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, return SSH_ERROR; } - rc = ssh_buffer_add_data(buffer, ssh_string_data(key_blob), - ssh_string_len(key_blob)); + rc = ssh_buffer_add_data(buffer, + ssh_string_data(key_blob), + (uint32_t)ssh_string_len(key_blob)); if (rc < 0) { SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); goto fail; @@ -1668,10 +2067,10 @@ 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}; @@ -1777,6 +2176,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])) { @@ -1794,14 +2197,16 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) /** * @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. + * Note that the certificate is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH certificate + * file). * - * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory using ssh_key_free(). + * @param[in] b64_cert The base64 cert to import. + * @param[in] type The type of the cert to import. + * @param[out] pkey A pointer where the allocated certificate can be stored. + * You need to free the memory using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_key_free() */ @@ -1848,31 +2253,47 @@ 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 key pair. + * @internal + * + * @brief Internal function to generate a key pair. * * @param[in] type Type of key to create * * @param[in] parameter Parameter to the creation of key: * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. * @param[out] pkey A pointer to store the allocated private key. You need * to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. - * - * @warning Generating a key pair may take some time. - * - * @see ssh_key_free() */ -int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, - ssh_key *pkey) +static int pki_generate_key_internal(enum ssh_keytypes_e type, + int parameter, + ssh_key *pkey) { int rc; - ssh_key key = ssh_key_new(); + ssh_key key = NULL; + + if (pkey == NULL) { + return SSH_ERROR; + } + key = ssh_key_new(); if (key == NULL) { return SSH_ERROR; } @@ -1883,6 +2304,15 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, switch(type){ case SSH_KEYTYPE_RSA: + if (parameter != 0 && parameter < RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size parameter (%d) is below minimum allowed (%d)", + parameter, + RSA_MIN_KEY_SIZE); + goto error; + } + rc = pki_key_generate_rsa(key, parameter); if(rc == SSH_ERROR) goto error; @@ -1945,37 +2375,194 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, } /** - * @brief Create a public key from a private key. + * @brief Generates a key pair. * - * @param[in] privkey The private key to get the public key from. + * @param[in] type Type of key to create * - * @param[out] pkey A pointer to store the newly allocated public key. You - * NEED to free the key using ssh_key_free(). + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * + * @warning Generating a key pair may take some time. + * * @see ssh_key_free() */ -int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, - ssh_key *pkey) +int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey) { - ssh_key pubkey; - - if (privkey == NULL || !ssh_key_is_private(privkey)) { - return SSH_ERROR; - } - - pubkey = pki_key_dup(privkey, 1); - if (pubkey == NULL) { - return SSH_ERROR; - } - - *pkey = pubkey; - return SSH_OK; + return pki_generate_key_internal(type, parameter, pkey); } /** - * @internal + * @brief Generates a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] pki_context PKI context containing various configuration + * parameters and sub-contexts. Can be NULL for + * standard SSH key types (RSA, ECDSA, ED25519) where + * defaults will be used. Can also be NULL for security + * key types (SK_*), in which case default callbacks and + * settings will be used automatically. + * + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_pki_ctx_new() + * @see ssh_key_free() + */ +int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey) +{ + + /* Handle Security Key types with the specialized function */ + if (is_sk_key_type(type)) { +#ifdef WITH_FIDO2 + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = pki_context; + int rc; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing SK callbacks in PKI context"); + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + return SSH_ERROR; + } + + rc = pki_sk_enroll_key(ctx_to_use, type, pkey); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +#else /* WITH_FIDO2 */ + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + } else { + int parameter = 0; + + if (type == SSH_KEYTYPE_RSA && pki_context != NULL) { + parameter = pki_context->rsa_key_size; + } + + return pki_generate_key_internal(type, parameter, pkey); + } +} + +/** + * @brief Create a public key from a private key. + * + * @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 using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey) +{ + ssh_key pubkey = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + pubkey = pki_key_dup(privkey, 1); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *pkey = pubkey; + return SSH_OK; +} + +/** + * @internal + * + * @brief Pack security key private data into a buffer. + * + * This function packs the common security key fields (application, flags, + * key handle, and reserved data) into a buffer. + * This is used for both ECDSA and Ed25519 security keys when exporting + * private key data. + * + * @param[in] buffer The buffer to pack the security key data into. + * + * @param[in] key The security key containing the data to pack. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_pack() + */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_pack(buffer, + "SbSS", + key->sk_application, + key->sk_flags, + key->sk_key_handle, + key->sk_reserved); +} + +/** + * @internal + * + * @brief Unpack security key private data from a buffer. + * + * This function unpacks the common security key fields (application, flags, + * key handle, and reserved data) from a buffer. + * This is used for both ECDSA and Ed25519 security keys when importing + * private key data. + * + * @param[in] buffer The buffer to unpack the security key data from. + * + * @param[in] key The security key to store the unpacked data into. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_unpack() + */ +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_unpack(buffer, + "SbSS", + &key->sk_application, + &key->sk_flags, + &key->sk_key_handle, + &key->sk_reserved); +} + +/** + * @internal * * @brief Create a key_blob from a public key. * @@ -1997,13 +2584,48 @@ int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, int ssh_pki_export_pubkey_blob(const ssh_key key, ssh_string *pblob) { - ssh_string blob; + ssh_string blob = NULL; + + if (key == NULL) { + return SSH_OK; + } + + 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 = NULL; if (key == NULL) { return SSH_OK; } - blob = pki_publickey_to_blob(key); + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); if (blob == NULL) { return SSH_ERROR; } @@ -2027,14 +2649,14 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, int ssh_pki_export_pubkey_base64(const ssh_key key, char **b64_key) { - ssh_string key_blob; - unsigned char *b64; + ssh_string key_blob = NULL; + unsigned char *b64 = NULL; if (key == NULL || b64_key == NULL) { 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; } @@ -2066,10 +2688,10 @@ int ssh_pki_export_pubkey_file(const ssh_key key, const char *filename) { char key_buf[MAX_LINE_SIZE]; - char host[256]; - char *b64_key; - char *user; - FILE *fp; + char *host = NULL; + char *b64_key = NULL; + char *user = NULL; + FILE *fp = NULL; int rc; if (key == NULL || filename == NULL || *filename == '\0') { @@ -2081,8 +2703,8 @@ int ssh_pki_export_pubkey_file(const ssh_key key, return SSH_ERROR; } - rc = gethostname(host, sizeof(host)); - if (rc < 0) { + host = ssh_get_local_hostname(); + if (host == NULL) { free(user); return SSH_ERROR; } @@ -2090,6 +2712,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key, rc = ssh_pki_export_pubkey_base64(key, &b64_key); if (rc < 0) { free(user); + free(host); return SSH_ERROR; } @@ -2100,6 +2723,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key, user, host); free(user); + free(host); free(b64_key); if (rc < 0) { return SSH_ERROR; @@ -2129,43 +2753,50 @@ int ssh_pki_export_pubkey_file(const ssh_key key, * * @returns SSH_OK on success, SSH_ERROR otherwise. **/ -int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { - ssh_buffer cert_buffer; - int rc; +int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) +{ + ssh_buffer cert_buffer = NULL; + int rc, cmp; - if (certkey == NULL || privkey == NULL) { - return SSH_ERROR; - } + if (certkey == NULL || privkey == NULL) { + return SSH_ERROR; + } - if (privkey->cert != NULL) { - return SSH_ERROR; - } + if (privkey->cert != NULL) { + return SSH_ERROR; + } - if (certkey->cert == NULL) { - return SSH_ERROR; - } + if (certkey->cert == NULL) { + return SSH_ERROR; + } - cert_buffer = ssh_buffer_new(); - if (cert_buffer == NULL) { - 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; + } - rc = ssh_buffer_add_buffer(cert_buffer, certkey->cert); - if (rc != 0) { - SSH_BUFFER_FREE(cert_buffer); - return SSH_ERROR; - } + cert_buffer = ssh_buffer_new(); + if (cert_buffer == NULL) { + return SSH_ERROR; + } - privkey->cert = cert_buffer; - privkey->cert_type = certkey->type; - return SSH_OK; + rc = ssh_buffer_add_buffer(cert_buffer, certkey->cert); + if (rc != 0) { + SSH_BUFFER_FREE(cert_buffer); + return SSH_ERROR; + } + + privkey->cert = cert_buffer; + privkey->cert_type = certkey->type; + return SSH_OK; } int ssh_pki_export_signature_blob(const ssh_signature sig, ssh_string *sig_blob) { ssh_buffer buf = NULL; - ssh_string str; + ssh_string str = NULL; int rc; if (sig == NULL || sig_blob == NULL) { @@ -2203,6 +2834,15 @@ int ssh_pki_export_signature_blob(const ssh_signature sig, return SSH_ERROR; } + if (is_sk_key_type(sig->type)) { + /* Add flags and counter for SK keys */ + rc = ssh_buffer_pack(buf, "bd", sig->sk_flags, sig->sk_counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + } + str = ssh_string_new(ssh_buffer_get_len(buf)); if (str == NULL) { SSH_BUFFER_FREE(buf); @@ -2229,7 +2869,7 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob, enum ssh_keytypes_e type; enum ssh_digest_e hash_type; ssh_string algorithm = NULL, blob = NULL; - ssh_buffer buf; + ssh_buffer buf = NULL; const char *alg = NULL; uint8_t flags = 0; uint32_t counter = 0; @@ -2246,7 +2886,7 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob, rc = ssh_buffer_add_data(buf, ssh_string_data(sig_blob), - ssh_string_len(sig_blob)); + (uint32_t)ssh_string_len(sig_blob)); if (rc < 0) { SSH_BUFFER_FREE(buf); return SSH_ERROR; @@ -2375,6 +3015,89 @@ int pki_key_check_hash_compatible(ssh_key key, return SSH_ERROR; } +/** + * @brief Prepare buffer for FIDO2/U2F security key signature verification + * + * This function creates a buffer containing the application hash, flags, + * counter, and input hash for FIDO/U2F key signature verification. + * + * @param key The SSH key containing sk_application + * @param sig The signature containing sk_flags and sk_counter + * @param input The input data to hash + * @param input_len Length of the input data + * @param sk_buffer_out Pointer to store the created buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out) +{ + ssh_buffer sk_buffer = NULL; + SHA256CTX ctx = NULL; + unsigned char application_hash[SHA256_DIGEST_LEN] = {0}; + unsigned char input_hash[SHA256_DIGEST_LEN] = {0}; + int rc, ret = SSH_ERROR; + + if (key == NULL || sig == NULL || input == NULL || sk_buffer_out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); + return SSH_ERROR; + } + + *sk_buffer_out = NULL; + + /* Calculate application hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for application hash"); + return SSH_ERROR; + } + sha256_update(ctx, + ssh_string_data(key->sk_application), + ssh_string_len(key->sk_application)); + sha256_final(application_hash, ctx); + + /* Calculate input hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for input hash"); + goto out; + } + sha256_update(ctx, input, input_len); + sha256_final(input_hash, ctx); + + /* Create and pack the sk_buffer */ + sk_buffer = ssh_buffer_new(); + if (sk_buffer == NULL) { + goto out; + } + + rc = ssh_buffer_pack(sk_buffer, + "PbdP", + (size_t)SHA256_DIGEST_LEN, + application_hash, + sig->sk_flags, + sig->sk_counter, + (size_t)SHA256_DIGEST_LEN, + input_hash); + if (rc != SSH_OK) { + goto out; + } + + *sk_buffer_out = sk_buffer; + sk_buffer = NULL; + ret = SSH_OK; + +out: + SSH_BUFFER_FREE(sk_buffer); + ssh_burn(application_hash, SHA256_DIGEST_LEN); + ssh_burn(input_hash, SHA256_DIGEST_LEN); + + return ret; +} + int ssh_pki_signature_verify(ssh_session session, ssh_signature sig, const ssh_key key, @@ -2386,8 +3109,7 @@ int ssh_pki_signature_verify(ssh_session session, enum ssh_keytypes_e key_type; if (session == NULL || sig == NULL || key == NULL || input == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " - "ssh_pki_signature_verify()"); + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); return SSH_ERROR; } key_type = ssh_key_type_plain(key->type); @@ -2405,8 +3127,11 @@ int ssh_pki_signature_verify(ssh_session session, 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)); + 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; } @@ -2416,58 +3141,23 @@ int ssh_pki_signature_verify(ssh_session session, return SSH_ERROR; } - if (key->type == SSH_KEYTYPE_SK_ECDSA || - key->type == SSH_KEYTYPE_SK_ECDSA_CERT01 || - key->type == SSH_KEYTYPE_SK_ED25519 || - key->type == SSH_KEYTYPE_SK_ED25519_CERT01) { - + if (is_sk_key_type(key->type)) { ssh_buffer sk_buffer = NULL; - SHA256CTX ctx = NULL; - unsigned char application_hash[SHA256_DIGEST_LEN] = {0}; - unsigned char input_hash[SHA256_DIGEST_LEN] = {0}; - - ctx = sha256_init(); - if (ctx == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Can not create SHA256CTX for application hash"); - return SSH_ERROR; - } - sha256_update(ctx, ssh_string_data(key->sk_application), - ssh_string_len(key->sk_application)); - sha256_final(application_hash, ctx); - - ctx = sha256_init(); - if (ctx == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Can not create SHA256CTX for input hash"); - return SSH_ERROR; - } - sha256_update(ctx, input, input_len); - sha256_final(input_hash, ctx); - - sk_buffer = ssh_buffer_new(); - if (sk_buffer == NULL) { - return SSH_ERROR; - } - rc = ssh_buffer_pack(sk_buffer, "PbdP", - SHA256_DIGEST_LEN, application_hash, - sig->sk_flags, sig->sk_counter, - SHA256_DIGEST_LEN, input_hash); + rc = pki_sk_signature_buffer_prepare(key, + sig, + input, + input_len, + &sk_buffer); if (rc != SSH_OK) { - SSH_BUFFER_FREE(sk_buffer); - explicit_bzero(input_hash, SHA256_DIGEST_LEN); - explicit_bzero(application_hash, SHA256_DIGEST_LEN); return SSH_ERROR; } - rc = pki_verify_data_signature(sig, key, ssh_buffer_get(sk_buffer), + rc = pki_verify_data_signature(sig, + key, + ssh_buffer_get(sk_buffer), ssh_buffer_get_len(sk_buffer)); - SSH_BUFFER_FREE(sk_buffer); - explicit_bzero(input_hash, SHA256_DIGEST_LEN); - explicit_bzero(application_hash, SHA256_DIGEST_LEN); - return rc; } @@ -2496,6 +3186,594 @@ ssh_signature pki_do_sign(const ssh_key privkey, return pki_sign_data(privkey, hash_type, input, input_len); } +/** + * @brief Encodes a binary signature blob as an sshsig armored signature + * + * @param blob The binary signature blob to encode + * @param out_str Pointer to store the allocated base64 encoded string + * Must be freed with ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_armor(ssh_buffer blob, char **out_str) +{ + char *b64_data = NULL; + char *armored = NULL; + const unsigned char *data = NULL; + size_t len, b64_len, armored_len, num_lines; + size_t i, j; + + if (blob == NULL || out_str == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out_str = NULL; + + data = ssh_buffer_get(blob); + len = ssh_buffer_get_len(blob); + + b64_data = (char *)bin_to_base64(data, len); + if (b64_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to base64 encode signature blob"); + return SSH_ERROR; + } + + b64_len = strlen(b64_data); + + /* Calculate space needed: header + data with line breaks + footer */ + num_lines = (b64_len + SSHSIG_LINE_LENGTH - 1) / + SSHSIG_LINE_LENGTH; /* Round up division */ + armored_len = strlen(SSHSIG_BEGIN_SIGNATURE) + 1 + /* header + \n */ + b64_len + num_lines + /* data + line breaks */ + strlen(SSHSIG_END_SIGNATURE) + 1; /* footer + \0 */ + + armored = calloc(armored_len, 1); + if (armored == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %zu bytes for armored signature", + armored_len); + SAFE_FREE(b64_data); + return SSH_ERROR; + } + + j = snprintf(armored, armored_len, SSHSIG_BEGIN_SIGNATURE "\n"); + for (i = 0; i < b64_len; i++) { + if (i > 0 && i % SSHSIG_LINE_LENGTH == 0) { + armored[j++] = '\n'; + } + armored[j++] = b64_data[i]; + } + armored[j++] = '\n'; + snprintf(armored + j, armored_len - j, SSHSIG_END_SIGNATURE); + + SAFE_FREE(b64_data); + + *out_str = armored; + return SSH_OK; +} + +/** + * @brief Dearmor an sshsig signature from ASCII armored format to binary + * + * @param[in] signature The armored sshsig signature string + * @param[out] out Pointer to store the allocated binary buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_dearmor(const char *signature, ssh_buffer *out) +{ + const char *begin = NULL; + const char *end = NULL; + char *clean_b64 = NULL; + ssh_buffer decoded_buffer = NULL; + int i, j; + int rc = SSH_ERROR; + + if (signature == NULL || out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out = NULL; + + rc = strncmp(signature, + SSHSIG_BEGIN_SIGNATURE, + strlen(SSHSIG_BEGIN_SIGNATURE)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature does not start with expected header"); + return SSH_ERROR; + } + + begin = signature + strlen(SSHSIG_BEGIN_SIGNATURE); + while (isspace(*begin)) { + begin++; + } + + end = strstr(begin, SSHSIG_END_SIGNATURE); + if (end == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Signature end marker not found"); + return SSH_ERROR; + } + + /* Backtrack to find the real end of data */ + while (end > begin && (isspace(*(end - 1)))) { + end--; + } + + clean_b64 = calloc(end - begin + 1, 1); + if (clean_b64 == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %td bytes for clean base64 data", + end - begin + 1); + return SSH_ERROR; + } + + for (i = 0, j = 0; begin + i < end; i++) { + if (!isspace(begin[i])) { + clean_b64[j++] = begin[i]; + } + } + clean_b64[j] = '\0'; + + decoded_buffer = base64_to_bin(clean_b64); + SAFE_FREE(clean_b64); + + if (decoded_buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to decode base64 signature data"); + return SSH_ERROR; + } + + *out = decoded_buffer; + return SSH_OK; +} + +/** + * @internal + * @brief Common helper function to prepare the data in sshsig format + * + * This function handles the common logic to prepare the sshsig format: + * 1. Hash the input data using the specified algorithm + * 2. Build the data buffer to sign + * + * @param data The raw data to process + * @param data_length The length of the data + * @param hash_alg The hash algorithm to use (sha256 or sha512) + * @param sig_namespace The signature namespace + * @param tosign_buf Pointer to store the allocated to-sign buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_prepare_data(const void *data, + size_t data_length, + const char *hash_alg, + const char *sig_namespace, + ssh_buffer *tosign_buf) +{ + ssh_buffer tosign = NULL; + ssh_string hash_string = NULL; + char hash[SHA512_DIGEST_LEN]; + size_t hash_len; + int rc = SSH_ERROR; + + if (data == NULL || hash_alg == NULL || sig_namespace == NULL || + tosign_buf == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *tosign_buf = NULL; + + if (strcmp(hash_alg, "sha256") == 0) { + hash_len = SHA256_DIGEST_LEN; + rc = sha256(data, data_length, (unsigned char *)hash); + } else if (strcmp(hash_alg, "sha512") == 0) { + hash_len = SHA512_DIGEST_LEN; + rc = sha512(data, data_length, (unsigned char *)hash); + } else { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm: %s", hash_alg); + goto cleanup; + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to compute %s hash of data", hash_alg); + goto cleanup; + } + + hash_string = ssh_string_new(hash_len); + if (hash_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate ssh_string for hash"); + goto cleanup; + } + + rc = ssh_string_fill(hash_string, hash, hash_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to fill ssh_string with hash data"); + goto cleanup; + } + + tosign = ssh_buffer_new(); + if (tosign == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate buffer for signing data"); + goto cleanup; + } + + rc = ssh_buffer_pack(tosign, + "tsssS", + SSHSIG_MAGIC_PREAMBLE, + sig_namespace, + "", + hash_alg, + hash_string); + + if (rc == SSH_OK) { + *tosign_buf = tosign; + tosign = NULL; + } else { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signing data into buffer"); + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_STRING_FREE(hash_string); + + return rc; +} + +/** + * @brief Signs data in sshsig compatible format + * + * @param data The data to sign + * @param data_length The length of the data + * @param privkey The private key to sign with + * @param pki_context The PKI context. For non-SK keys, this parameter is + * ignored and can be NULL. For SK keys, can be NULL in + * which case a default context with default callbacks + * will be used. If provided, the context must have + * sk_callbacks set with a valid sign callback + * implementation. See ssh_pki_ctx_set_sk_callbacks(). + * @param sig_namespace The signature namespace (e.g. "file", "email", etc.) + * @param hash_alg The hash algorithm to use (SSHSIG_DIGEST_SHA2_256 or + * SSHSIG_DIGEST_SHA2_512) + * @param signature Pointer to store the allocated signature string in the + * armored format. Must be freed with + * ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature) +{ + ssh_buffer tosign = NULL; + ssh_buffer signature_blob = NULL; + ssh_signature sig = NULL; + ssh_string sig_string = NULL; + ssh_string pub_blob = NULL; + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = NULL; + enum ssh_digest_e digest_type; + const char *hash_alg_str = NULL; + int rc = SSH_ERROR; + + if (privkey == NULL || data == NULL || sig_namespace == NULL || + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_sign"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_sign: empty namespace " + "string"); + return SSH_ERROR; + } + + /* Check if this is an SK key that requires a PKI context */ + if (is_sk_key_type(privkey->type)) { + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Security Key callbacks not configured in PKI context"); + goto cleanup; + } + } + + *signature = NULL; + + if (hash_alg == SSHSIG_DIGEST_SHA2_256) { + hash_alg_str = "sha256"; + } else if (hash_alg == SSHSIG_DIGEST_SHA2_512) { + hash_alg_str = "sha512"; + } else { + SSH_LOG(SSH_LOG_TRACE, "Invalid hash algorithm %d", hash_alg); + return SSH_ERROR; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare data for sshsig signing"); + goto cleanup; + } + + /* Use appropriate signing method based on key type */ + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + sig = pki_sk_do_sign(ctx_to_use, + privkey, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto cleanup; +#endif + } else { + digest_type = key_type_to_hash(ssh_key_type_plain(privkey->type)); + sig = pki_sign_data(privkey, + digest_type, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (sig == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to sign data with private key"); + goto cleanup; + } + + rc = ssh_pki_export_pubkey_blob(privkey, &pub_blob); + if (rc != SSH_OK || pub_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to export public key blob from private key"); + goto cleanup; + } + + rc = ssh_pki_export_signature_blob(sig, &sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export signature blob"); + goto cleanup; + } + + signature_blob = ssh_buffer_new(); + if (signature_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate signature buffer"); + goto cleanup; + } + + rc = ssh_buffer_pack(signature_blob, + "tdSsssS", + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_VERSION, + pub_blob, + sig_namespace, + "", + hash_alg_str, + sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signature blob"); + goto cleanup; + } + + rc = sshsig_armor(signature_blob, signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to armor signature blob"); + goto cleanup; + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(signature_blob); + SSH_SIGNATURE_FREE(sig); + SSH_STRING_FREE(sig_string); + SSH_STRING_FREE(pub_blob); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** + * @brief Verifies an sshsig formatted signature against data + * + * @param data The data to verify + * @param data_length The length of the data + * @param signature The armored sshsig signature + * @param sig_namespace The expected signature namespace + * @param sign_key If not NULL, returns the allocated public key that was + * used for signing this data. Must be freed with + * ssh_key_free(). Note that this is an output parameter + * and is not checked against "allowed signers". The + * caller needs to compare it with expected signer key + * using ssh_key_cmp(). + * + * @return SSH_OK on success, SSH_ERROR on verification failure + */ +int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key) +{ + ssh_buffer sig_buf = NULL; + ssh_buffer tosign = NULL; + ssh_key key = NULL; + char *hash_alg_str = NULL; + ssh_string sig_data = NULL; + ssh_string sig_namespace_str = NULL; + ssh_string reserved_str = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + ssh_signature signature_obj = NULL; + uint32_t sig_version; + + if (sign_key != NULL) { + *sign_key = NULL; + } + + if (signature == NULL || data == NULL || sig_namespace == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_verify"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_verify: empty namespace " + "string"); + return SSH_ERROR; + } + + rc = sshsig_dearmor(signature, &sig_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to dearmor signature"); + return SSH_ERROR; + } + + if (ssh_buffer_get_len(sig_buf) < SSHSIG_MAGIC_PREAMBLE_LEN || + memcmp(ssh_buffer_get(sig_buf), + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_MAGIC_PREAMBLE_LEN) != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid signature magic preamble"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(sig_buf, SSHSIG_MAGIC_PREAMBLE_LEN); + rc = ssh_buffer_unpack(sig_buf, + "dSSSsS", + &sig_version, + &pubkey_blob, + &sig_namespace_str, + &reserved_str, + &hash_alg_str, + &sig_data); + + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to unpack signature buffer"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + if (sig_version != SSHSIG_VERSION) { + SSH_LOG(SSH_LOG_TRACE, + "Unsupported signature version %u, expected %u", + sig_version, + SSHSIG_VERSION); + rc = SSH_ERROR; + goto cleanup; + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from signature"); + goto cleanup; + } + + if (ssh_string_len(sig_namespace_str) != strlen(sig_namespace) || + memcmp(ssh_string_data(sig_namespace_str), + sig_namespace, + strlen(sig_namespace)) != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature namespace mismatch: expected '%s', got '%s'", + sig_namespace, + ssh_string_get_char(sig_namespace_str)); + rc = SSH_ERROR; + goto cleanup; + } + + if (strcmp(hash_alg_str, "sha256") != 0 && + strcmp(hash_alg_str, "sha512") != 0) { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm '%s'", hash_alg_str); + rc = SSH_ERROR; + goto cleanup; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to prepare data for sshsig verification"); + goto cleanup; + } + + rc = ssh_pki_import_signature_blob(sig_data, key, &signature_obj); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import signature blob"); + goto cleanup; + } + + if (is_sk_key_type(key->type)) { + ssh_buffer sk_buffer = NULL; + rc = pki_sk_signature_buffer_prepare(key, + signature_obj, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign), + &sk_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare sk signature buffer"); + goto cleanup; + } + + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(sk_buffer), + ssh_buffer_get_len(sk_buffer)); + SSH_BUFFER_FREE(sk_buffer); + } else { + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature verification failed"); + goto cleanup; + } + + if (sign_key != NULL) { + *sign_key = key; + key = NULL; /* Transferred ownership */ + } + +cleanup: + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(sig_namespace_str); + SSH_STRING_FREE(reserved_str); + SSH_STRING_FREE(sig_data); + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(sig_buf); + SSH_KEY_FREE(key); + SAFE_FREE(hash_alg_str); + SSH_SIGNATURE_FREE(signature_obj); + + return rc; +} + /* * This function signs the session id as a string then * the content of sigbuf */ @@ -2547,16 +3825,45 @@ ssh_string ssh_pki_do_sign(ssh_session session, rc = ssh_buffer_pack(sign_input, "SP", session_id, - ssh_buffer_get_len(sigbuf), ssh_buffer_get(sigbuf)); + (size_t)ssh_buffer_get_len(sigbuf), + ssh_buffer_get(sigbuf)); if (rc != SSH_OK) { goto end; } /* Generate the signature */ - sig = pki_do_sign(privkey, - ssh_buffer_get(sign_input), - ssh_buffer_get_len(sign_input), - hash_type); + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + if (session->pki_context == NULL || + session->pki_context->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing PKI context or SK callbacks"); + goto end; + } + + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible hash type %d for sk key type %d", + hash_type, + privkey->type); + goto end; + } + + sig = pki_sk_do_sign(session->pki_context, + privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto end; +#endif /* WITH_FIDO2 */ + } else { + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + hash_type); + } + if (sig == NULL) { goto end; } @@ -2580,9 +3887,9 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, const ssh_key pubkey) { struct ssh_crypto_struct *crypto = NULL; - ssh_string session_id; - ssh_string sig_blob; - ssh_buffer sig_buf; + ssh_string session_id = NULL; + ssh_string sig_blob = NULL; + ssh_buffer sig_buf = NULL; int rc; crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); diff --git a/src/pki_container_openssh.c b/src/pki_container_openssh.c index 92101c4e..3d782b93 100644 --- a/src/pki_container_openssh.c +++ b/src/pki_container_openssh.c @@ -155,7 +155,7 @@ static int pki_private_key_decrypt(ssh_string blob, } rc = ssh_buffer_add_data(buffer, ssh_string_data(kdfoptions), - ssh_string_len(kdfoptions)); + (uint32_t)ssh_string_len(kdfoptions)); if (rc != SSH_ERROR){ rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); } @@ -208,7 +208,7 @@ static int pki_private_key_decrypt(ssh_string blob, if (rc < 0){ return SSH_ERROR; } - explicit_bzero(passphrase_buffer, sizeof(passphrase_buffer)); + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); cipher.set_decrypt_key(&cipher, key_material, @@ -234,12 +234,12 @@ ssh_pki_openssh_import(const char *text_key, bool private) { const char *ptr = text_key; - const char *end; - char *base64; + const char *end = NULL; + char *base64 = NULL; int cmp; int rc; int i; - ssh_buffer buffer = NULL, privkey_buffer=NULL; + ssh_buffer buffer = NULL, privkey_buffer = NULL; char *magic = NULL, *ciphername = NULL, *kdfname = NULL; uint32_t nkeys = 0, checkint1 = 0, checkint2 = 0xFFFF; ssh_string kdfoptions = NULL; @@ -339,7 +339,7 @@ ssh_pki_openssh_import(const char *text_key, ssh_buffer_set_secure(privkey_buffer); ssh_buffer_add_data(privkey_buffer, ssh_string_data(privkeys), - ssh_string_len(privkeys)); + (uint32_t)ssh_string_len(privkeys)); rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); if (rc == SSH_ERROR || checkint1 != checkint2) { @@ -394,37 +394,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_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, - "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 * @@ -518,7 +487,7 @@ static int pki_private_key_encrypt(ssh_buffer privkey_buffer, ssh_buffer_get(privkey_buffer), ssh_buffer_get_len(privkey_buffer)); ssh_cipher_clear(&cipher); - explicit_bzero(passphrase_buffer, sizeof(passphrase_buffer)); + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); return SSH_OK; } @@ -536,16 +505,16 @@ 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_string pubkey_s=NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; + ssh_string pubkey_s = NULL; ssh_buffer privkey_buffer = NULL; uint32_t rnd; uint32_t rounds = 16; - ssh_string salt=NULL; - ssh_string kdf_options=NULL; + ssh_string salt = NULL; + ssh_string kdf_options = NULL; int to_encrypt=0; - unsigned char *b64; + unsigned char *b64 = NULL; uint32_t str_len, len; uint8_t padding = 1; int ok; @@ -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_TRACE, "Unsupported key type %s", privkey->type_c); - return NULL; - } if (passphrase != NULL || auth_fn != NULL){ 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,18 @@ 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; } @@ -661,15 +622,17 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, rc = ssh_buffer_pack(buffer, "PssSdSdP", - (size_t)strlen(OPENSSH_AUTH_MAGIC) + 1, OPENSSH_AUTH_MAGIC, + strlen(OPENSSH_AUTH_MAGIC) + 1, + OPENSSH_AUTH_MAGIC, to_encrypt ? "aes128-cbc" : "none", /* ciphername */ - to_encrypt ? "bcrypt" : "none", /* kdfname */ - kdf_options, /* kdfoptions */ - (uint32_t) 1, /* nkeys */ + to_encrypt ? "bcrypt" : "none", /* kdfname */ + kdf_options, /* kdfoptions */ + (uint32_t)1, /* nkeys */ pubkey_s, - (uint32_t)ssh_buffer_get_len(privkey_buffer), + ssh_buffer_get_len(privkey_buffer), /* rest of buffer is a string */ - (size_t)ssh_buffer_get_len(privkey_buffer), ssh_buffer_get(privkey_buffer)); + (size_t)ssh_buffer_get_len(privkey_buffer), + ssh_buffer_get(privkey_buffer)); if (rc != SSH_OK) { goto error; } @@ -690,7 +653,7 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, "\n", OPENSSH_HEADER_END, "\n"); - explicit_bzero(b64, strlen((char *)b64)); + ssh_burn(b64, strlen((char *)b64)); SAFE_FREE(b64); if (rc != SSH_OK){ @@ -710,9 +673,11 @@ 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)); + ssh_burn(bufptr, ssh_buffer_get_len(privkey_buffer)); SSH_BUFFER_FREE(privkey_buffer); } SAFE_FREE(pubkey_s); diff --git a/src/pki_context.c b/src/pki_context.c new file mode 100644 index 00000000..6cd78fbf --- /dev/null +++ b/src/pki_context.c @@ -0,0 +1,581 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef WITH_FIDO2 +#include "libssh/buffer.h" +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" +#endif /* WITH_FIDO2 */ + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Allocate a new generic PKI context container. + * + * Allocates and default-initializes a new ssh_pki_ctx instance. + * + * @return Newly allocated context on success, or NULL on allocation failure. + * @see ssh_pki_ctx_free() + */ +ssh_pki_ctx ssh_pki_ctx_new(void) +{ + struct ssh_pki_ctx_struct *ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pki_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + +#ifdef WITH_FIDO2 + /* Initialize SK fields with default, if available. */ + ctx->sk_callbacks = ssh_sk_get_default_callbacks(); + + /* + * Both OpenSSH security key enrollment and server authentication require + * user presence by default, so we replicate that for consistency. + */ + ctx->sk_flags = SSH_SK_USER_PRESENCE_REQD; + + ctx->sk_application = strdup("ssh:"); + if (ctx->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for default application"); + SAFE_FREE(ctx); + return NULL; + } +#endif /* WITH_FIDO2 */ + + return ctx; +} + +/** + * @brief Free a generic PKI context container. + * + * @param[in] context The PKI context to free (may be NULL). + * @see ssh_pki_ctx_new() + */ +void ssh_pki_ctx_free(ssh_pki_ctx context) +{ + if (context == NULL) { + return; + } + +#ifdef WITH_FIDO2 + SAFE_FREE(context->sk_application); + SSH_BUFFER_FREE(context->sk_challenge_buffer); + SSH_BUFFER_FREE(context->sk_attestation_buffer); + SK_OPTIONS_FREE(context->sk_callbacks_options); +#endif /* WITH_FIDO2 */ + + SAFE_FREE(context); +} + +/** + * @brief Set various options for a PKI context. + * + * This function can set all possible PKI context options. + * + * @param[in] context Target PKI context. + * @param option The option type to set. This could be one of the following: + * + * - SSH_PKI_OPTION_RSA_KEY_SIZE (int): + * Set the RSA key size in bits for key generation. + * Typically 2048, 3072, or 4096 bits. Must be greater + * than or equal to 1024, as anything below is considered + * insecure. + * + * - SSH_PKI_OPTION_SK_APPLICATION (const char *): + * The Relying Party identifier (application string) that + * determines which service/domain this security key + * credential will be associated with. This is a required + * field for all security key generation operations. + * The application string typically starts with "ssh:" for + * SSH keys. It is copied internally and can be freed + * after setting. + * + * - SSH_PKI_SK_OPTION_FLAGS (uint8_t): + * Set FIDO2/U2F operation flags that control how the FIDO2/U2F + * authenticator behaves during generation operations. Multiple + * flags can be combined using bitwise OR operations. The + * pointer must not be NULL. + * + * Available flags: + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence + * + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * + * SSH_SK_FORCE_OPERATION: Forces generation even if a + * resident key already exists. + * + * SSH_SK_RESIDENT_KEY: Creates a resident + * key stored on the authenticator. + * + * - SSH_PKI_OPTION_SK_USER_ID (const char *): + * Sets the user identifier to associate with a resident + * credential during enrollment. When a resident key is + * requested (SSH_SK_RESIDENT_KEY), this ID is stored on the + * authenticator and later used to look up or prevent duplicate + * credentials. Maximum length is SK_MAX_USER_ID_LEN bytes; + * longer values will cause the operation to fail. + * + * - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): + * Set custom cryptographic challenge data to be included in + * the generation operation. The challenge is signed by the + * authenticator during key generation. If not provided, + * a random 32-byte challenge will be automatically generated. + * The challenge data is copied internally and the caller + * retains ownership of the provided buffer. + * + * - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): + * Set the security key callback structure to use custom + * callback functions for FIDO2/U2F operations like enrollment, + * signing, and loading resident keys. The structure is not + * copied so it needs to be valid for the whole context + * lifetime or until replaced. + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * option type. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char *, ssh_buffer), the value parameter + * should be that pointer. Do NOT pass a pointer to a + * pointer. + * + * @warning When the option value to set is not a pointer (e.g int, + * uint8_t), the value parameter should be a pointer to the + * location storing the value to set (int *, uint8_t *). + */ +int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value) +{ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid PKI context passed"); + return SSH_ERROR; + } + + switch (option) { + case SSH_PKI_OPTION_RSA_KEY_SIZE: + if (value == NULL) { + SSH_LOG(SSH_LOG_WARN, "RSA key size pointer must not be NULL"); + return SSH_ERROR; + } else if (*(int *)value != 0 && *(int *)value <= RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size must be greater than %d bits or 0 for default", + RSA_MIN_KEY_SIZE); + return SSH_ERROR; + } + context->rsa_key_size = *(int *)value; + break; + +#ifdef WITH_FIDO2 + case SSH_PKI_OPTION_SK_APPLICATION: + SAFE_FREE(context->sk_application); + if (value != NULL) { + context->sk_application = strdup((char *)value); + if (context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for application"); + return SSH_ERROR; + } + } + break; + + case SSH_PKI_OPTION_SK_FLAGS: + if (value == NULL) { + return SSH_ERROR; + } else { + context->sk_flags = *(uint8_t *)value; + } + break; + + case SSH_PKI_OPTION_SK_USER_ID: { + int rc; + + /* + * Set required to false, because only the enrollment callback supports + * the user ID option, and if this context is used for any other + * operation, it would fail unnecessarily. + */ + rc = ssh_pki_ctx_sk_callbacks_option_set(context, + SSH_SK_OPTION_NAME_USER_ID, + value, + false); + if (rc != SSH_OK) { + return SSH_ERROR; + } + break; + } + + case SSH_PKI_OPTION_SK_CHALLENGE: { + SSH_BUFFER_FREE(context->sk_challenge_buffer); + if (value == NULL) { + break; + } + + context->sk_challenge_buffer = ssh_buffer_dup((ssh_buffer)value); + if (context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate challenge buffer"); + return SSH_ERROR; + } + ssh_buffer_set_secure(context->sk_challenge_buffer); + break; + } + + case SSH_PKI_OPTION_SK_CALLBACKS: { + bool is_compatible = sk_callbacks_check_compatibility(value); + if (!is_compatible) { + return SSH_ERROR; + } + context->sk_callbacks = value; + break; + } +#else /* WITH_FIDO2 */ + case SSH_PKI_OPTION_SK_APPLICATION: + case SSH_PKI_OPTION_SK_FLAGS: + case SSH_PKI_OPTION_SK_USER_ID: + case SSH_PKI_OPTION_SK_CHALLENGE: + case SSH_PKI_OPTION_SK_CALLBACKS: + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + + default: + SSH_LOG(SSH_LOG_WARN, "Unknown PKI context option: %d", option); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Set the PIN callback function to get the PIN for security + * key authenticator access. + * + * @param context The PKI context to modify. + * @param pin_callback The callback used when the authenticator requires PIN + * entry for verification. + * @param userdata A generic pointer that is passed as the userdata + * argument to the callback function. Can be NULL. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + * + * @note The callback and userdata are stored internally in the context + * structure and must remain valid until the context is freed or + * replaced. + * + * @see ssh_auth_callback + */ +int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + context->sk_pin_callback = pin_callback; + context->sk_userdata = userdata; + + return SSH_OK; + +#else + (void)context; + (void)pin_callback; + (void)userdata; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Set a security key (FIDO2/U2F) callback option in the + * context. These options are passed to the sk_callbacks during + * enroll/sign/load_resident_keys operations. + * + * Both the name and value strings are duplicated internally so the caller + * retains ownership of the original pointers. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[in] name option name string. Must not be NULL. + * @param[in] value option value string. Must not be NULL. + * @param[in] required Set to true if the option is mandatory. If set and the + * ssh_sk_callbacks do not recognize the option, + * the operation should fail. + * + * @return SSH_OK on success, SSH_ERROR on allocation failure or invalid args. + * + * @note The option objects are freed automatically when the context is freed + * via ssh_pki_sk_ctx_free(). + * + * @see ssh_sk_callbacks_struct + */ +int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required) +{ +#ifdef WITH_FIDO2 + struct sk_option *new_option = NULL; + struct sk_option **temp = NULL; + size_t count = 0; + + if (context == NULL || name == NULL || value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameters passed"); + return SSH_ERROR; + } + + /* Count existing options */ + if (context->sk_callbacks_options != NULL) { + while (context->sk_callbacks_options[count] != NULL) { + count++; + } + } + + /* Allocate new option */ + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for new option"); + return SSH_ERROR; + } + + new_option->name = strdup(name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option name"); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->value = strdup(value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->required = required; + + /* Reallocate array to accommodate new option */ + temp = realloc(context->sk_callbacks_options, + (count + 2) * sizeof(struct sk_option *)); + if (temp == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate options array"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option->value); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + context->sk_callbacks_options = temp; + context->sk_callbacks_options[count] = new_option; + context->sk_callbacks_options[count + 1] = NULL; + + return SSH_OK; +#else + (void)context; + (void)name; + (void)value; + (void)required; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Clear all sk_callbacks options. + * + * Removes and frees all previously set sk_callbacks options from the context. + * + * @param[in] context The PKI context to modify. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + */ +int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + SK_OPTIONS_FREE(context->sk_callbacks_options); + return SSH_OK; +#else + (void)context; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Get a copy of the attestation buffer from a PKI context. + * + * Retrieves a copy of the attestation buffer stored in the context after a key + * enrollment operation. The attestation buffer contains serialized attestation + * information in the "ssh-sk-attest-v01" format. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[out] attestation_buffer Pointer to store a copy of the attestation + * buffer. Will be set to NULL if no attestation + * data is available (e.g., authenticator doesn't + * support attestation, or attestation data + * was invalid/incomplete). + * + * @return SSH_OK on success, SSH_ERROR if context or attestation_buffer is + * NULL, or if buffer duplication fails. + * + * @note The caller is responsible for freeing the returned buffer using + * SSH_BUFFER_FREE(). + */ +int ssh_pki_ctx_get_sk_attestation_buffer( + const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + if (attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "attestation_buffer pointer should not be NULL"); + return SSH_ERROR; + } + + *attestation_buffer = ssh_buffer_dup(context->sk_attestation_buffer); + if (*attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate attestation buffer"); + return SSH_ERROR; + } + + return SSH_OK; +#else + (void)context; + (void)attestation_buffer; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Duplicate an existing PKI context + * + * Creates a new PKI context and copies all fields from the source context. + * This function performs deep copying for all dynamically allocated fields + * to ensure independent ownership between source and destination contexts. + * + * @param[in] context The PKI context to copy from + * + * @return New PKI context with copied data on success, + * NULL on failure or if src_context is NULL + */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context) +{ + ssh_pki_ctx new_context = NULL; + + if (context == NULL) { + return NULL; + } + + new_context = ssh_pki_ctx_new(); + if (new_context == NULL) { + goto error; + } + + new_context->rsa_key_size = context->rsa_key_size; + +#ifdef WITH_FIDO2 + new_context->sk_callbacks = context->sk_callbacks; + + // Free the default application string before copying + SAFE_FREE(new_context->sk_application); + + if (context->sk_application != NULL) { + new_context->sk_application = strdup(context->sk_application); + if (new_context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK application string"); + goto error; + } + } + + new_context->sk_flags = context->sk_flags; + + new_context->sk_pin_callback = context->sk_pin_callback; + new_context->sk_userdata = context->sk_userdata; + + if (context->sk_challenge_buffer != NULL) { + new_context->sk_challenge_buffer = + ssh_buffer_dup(context->sk_challenge_buffer); + if (new_context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK challenge buffer"); + goto error; + } + } + + if (context->sk_callbacks_options != NULL) { + new_context->sk_callbacks_options = sk_options_dup( + (const struct sk_option **)context->sk_callbacks_options); + if (new_context->sk_callbacks_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK callbacks options"); + goto error; + } + } + + if (context->sk_attestation_buffer != NULL) { + new_context->sk_attestation_buffer = + ssh_buffer_dup(context->sk_attestation_buffer); + if (new_context->sk_attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK attestation buffer"); + goto error; + } + } +#endif /* WITH_FIDO2 */ + + return new_context; + +error: + SSH_PKI_CTX_FREE(new_context); + return NULL; +} + +/** @} */ diff --git a/src/pki_crypto.c b/src/pki_crypto.c index c31ef928..7bb5b644 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -33,7 +33,9 @@ #include #include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) #include +#endif #include #if OPENSSL_VERSION_NUMBER < 0x30000000L #include @@ -44,7 +46,6 @@ #include #if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) #include -#include #endif #endif /* OPENSSL_VERSION_NUMBER */ @@ -84,7 +85,7 @@ static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { buf, size, 0, 0, pgp->data); if (rc == 0) { - return strlen(buf); + return (int)strlen(buf); } } @@ -226,7 +227,6 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) } key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); #if OPENSSL_VERSION_NUMBER < 0x30000000L ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); @@ -336,11 +336,10 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) int ok; #else const char *group_name = OSSL_EC_curve_nid2name(nid); - OSSL_PARAM_BLD *param_bld; + OSSL_PARAM_BLD *param_bld = NULL; #endif /* OPENSSL_VERSION_NUMBER */ key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); #if OPENSSL_VERSION_NUMBER < 0x30000000L ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); @@ -413,24 +412,84 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) } #endif /* HAVE_OPENSSL_ECC */ +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(privkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + if (ssh_fips_mode()) { + /* We do not want to fail here as we know the algorithm, but we can not + * use it. Just store the public key here. We won't be able to use it + * for anything though. */ + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate memory for the Ed25519 public key"); + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + return SSH_OK; + } + + pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(pubkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + ssh_key pki_key_dup(const ssh_key key, int demote) { ssh_key new = NULL; int rc; - new = ssh_key_new(); + new = pki_key_dup_common_init(key, demote); if (new == NULL) { return NULL; } - new->type = key->type; - new->type_c = key->type_c; - if (demote) { - new->flags = SSH_KEY_FLAG_PUBLIC; - } else { - new->flags = key->flags; - } - switch (key->type) { case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { @@ -579,6 +638,7 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_OPENSSL_ECC new->ecdsa_nid = key->ecdsa_nid; #ifdef WITH_PKCS11_URI @@ -649,11 +709,77 @@ ssh_key pki_key_dup(const ssh_key key, int demote) break; #endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_ED25519: - rc = pki_ed25519_key_dup(new, key); + case SSH_KEYTYPE_SK_ED25519: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* 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; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + key->type == SSH_KEYTYPE_ED25519) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } else { + unsigned char *ed25519_pubkey = NULL; + size_t key_len = 0; + + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of public key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + free(ed25519_pubkey); + goto fail; + } + + new->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + ed25519_pubkey, + key_len); + free(ed25519_pubkey); + } + +#else + rc = evp_dup_ed25519_pkey(key, new, demote); if (rc != SSH_OK) { goto fail; } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ break; + } case SSH_KEYTYPE_UNKNOWN: default: ssh_key_free(new); @@ -677,6 +803,10 @@ int pki_key_generate_rsa(ssh_key key, int parameter){ unsigned e = 65537; #endif /* OPENSSL_VERSION_NUMBER */ + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + #if OPENSSL_VERSION_NUMBER < 0x30000000L e = BN_new(); key_rsa = RSA_new(); @@ -809,72 +939,89 @@ int pki_key_generate_ecdsa(ssh_key key, int parameter) /* 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) +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - int rc; - (void) what; + int rc, cmp; - switch (k1->type) { - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: + (void)what; + + /* We got here only if the types match */ + 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_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; - } + { + const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key); + const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key); + const EC_POINT *p1 = NULL; + const EC_POINT *p2 = NULL; + const EC_GROUP *g1 = NULL; + const EC_GROUP *g2 = NULL; + + if (ec1 == NULL || ec2 == NULL) { + return 1; + } - p1 = EC_KEY_get0_public_key(ec1); - p2 = EC_KEY_get0_public_key(ec2); - g1 = EC_KEY_get0_group(ec1); - g2 = EC_KEY_get0_group(ec2); + 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; - } + if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) { + return 1; + } - if (EC_GROUP_cmp(g1, g2, NULL) != 0) { - return 1; - } + if (EC_GROUP_cmp(g1, g2, NULL) != 0) { + return 1; + } - if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { - return 1; - } + if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { + return 1; + } - if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(EC_KEY_get0_private_key(ec1), - EC_KEY_get0_private_key(ec2))) { - return 1; - } - } - break; + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (bignum_cmp(EC_KEY_get0_private_key(ec1), + EC_KEY_get0_private_key(ec2))) { + return 1; } + } + break; + } #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) { + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + /* In FIPS mode, we can not use OpenSSL to compare Ed25519 keys. + * The OpenSSL < 3.0 also crashes in EVP_PKEY_eq() when either of + * keys keys is NULL so catch it here. */ + if (ssh_fips_mode() && k1->key == NULL && k2->key == NULL) { + if (what == SSH_KEY_CMP_PRIVATE) { + /* we should never have Ed25519 private key in FIPS mode */ return 1; } - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globally */ - case SSH_KEYTYPE_UNKNOWN: - default: + cmp = memcmp(k1->ed25519_pubkey, + k2->ed25519_pubkey, + ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + /* they match */ + return 0; + } + FALL_THROUGH; + 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_UNKNOWN: + default: + return 1; } return 0; } @@ -896,47 +1043,33 @@ ssh_string pki_private_key_to_pem(const ssh_key key, } switch (key->type) { - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - 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; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto err; + } + pkey = key->key; - break; - case SSH_KEYTYPE_ED25519: - /* In OpenSSL, the input is the private key seed only, which means - * the first half of the SSH private key (the second half is the - * public key) */ - pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, - (const uint8_t *)key->ed25519_privkey, - ED25519_KEY_LEN); - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to create ed25519 EVP_PKEY: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto err; - } + /* Mark the operation as successful as for the other key types */ + rc = 1; - /* Mark the operation as successful as for the other key types */ - rc = 1; - break; - case SSH_KEYTYPE_RSA_CERT01: - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", key->type); - goto err; + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown or invalid private key type %d", + key->type); + goto err; } if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure"); @@ -966,6 +1099,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; } @@ -1001,7 +1137,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, #if OPENSSL_VERSION_NUMBER < 0x30000000L EC_KEY *ecdsa = NULL; #endif /* OPENSSL_VERSION_NUMBER */ - uint8_t *ed25519 = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; EVP_PKEY *pkey = NULL; @@ -1060,41 +1195,8 @@ ssh_key pki_private_key_from_base64(const char *b64_key, break; #endif /* HAVE_OPENSSL_ECC */ case EVP_PKEY_ED25519: - { - size_t key_len; - int evp_rc = 0; - - /* Get the key length */ - evp_rc = EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len); - if (evp_rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get ed25519 raw private key length: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto fail; - } - - if (key_len != ED25519_KEY_LEN) { - goto fail; - } - - ed25519 = malloc(key_len); - if (ed25519 == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Out of memory"); - goto fail; - } - - evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)ed25519, - &key_len); - if (evp_rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get ed25519 raw private key: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto fail; - } type = SSH_KEYTYPE_ED25519; - - } - break; + break; default: SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", EVP_PKEY_base_id(pkey)); @@ -1111,7 +1213,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; key->key = pkey; - key->ed25519_privkey = ed25519; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { #if OPENSSL_VERSION_NUMBER < 0x30000000L @@ -1126,7 +1227,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, fail: EVP_PKEY_free(pkey); ssh_key_free(key); - SAFE_FREE(ed25519); return NULL; } @@ -1134,12 +1234,17 @@ int pki_privkey_build_rsa(ssh_key key, ssh_string n, ssh_string e, ssh_string d, - UNUSED_PARAM(ssh_string iqmp), + ssh_string iqmp, ssh_string p, ssh_string q) { int rc; - BIGNUM *be, *bn, *bd/*, *biqmp*/, *bp, *bq; + 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) { @@ -1155,7 +1260,7 @@ int pki_privkey_build_rsa(ssh_key key, bn = ssh_make_string_bn(n); be = ssh_make_string_bn(e); bd = ssh_make_string_bn(d); - /*biqmp = ssh_make_string_bn(iqmp);*/ + 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 || @@ -1164,6 +1269,33 @@ int pki_privkey_build_rsa(ssh_key key, goto fail; } + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + 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); + + 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; + } + #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); @@ -1180,9 +1312,15 @@ int pki_privkey_build_rsa(ssh_key key, /* 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 */ - /* RSA_set0_crt_params(key->rsa, biqmp, NULL, NULL); - TODO calculate missing crt_params */ + 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) { @@ -1216,24 +1354,45 @@ int pki_privkey_build_rsa(ssh_key key, goto fail; } - rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); - if (rc != SSH_OK) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { rc = SSH_ERROR; goto fail; } - rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); if (rc != 1) { rc = SSH_ERROR; goto fail; } - rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); if (rc != 1) { rc = SSH_ERROR; goto fail; } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + 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; + } + fail: OSSL_PARAM_BLD_free(param_bld); bignum_safe_free(bn); @@ -1241,7 +1400,13 @@ int pki_privkey_build_rsa(ssh_key key, 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 */ } @@ -1264,18 +1429,24 @@ int pki_pubkey_build_rsa(ssh_key key, #endif /* OPENSSL_VERSION_NUMBER */ be = ssh_make_string_bn(e); + if (be == NULL) { + rc = SSH_ERROR; + goto fail; + } bn = ssh_make_string_bn(n); - if (be == NULL || bn == NULL) { + if (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); if (rc == 0) { goto fail; } + /* Memory management of bn and be is transferred to RSA object */ + bn = NULL; + be = NULL; key->key = EVP_PKEY_new(); if (key->key == NULL) { @@ -1288,10 +1459,6 @@ int pki_pubkey_build_rsa(ssh_key key, } return SSH_OK; -fail: - 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) { @@ -1305,37 +1472,52 @@ int pki_pubkey_build_rsa(ssh_key key, } rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); +#endif /* OPENSSL_VERSION_NUMBER */ fail: - OSSL_PARAM_BLD_free(param_bld); bignum_safe_free(bn); bignum_safe_free(be); - +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EVP_PKEY_free(key->key); + RSA_free(key_rsa); + + return SSH_ERROR; +#else + OSSL_PARAM_BLD_free(param_bld); + 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; + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; ssh_string str = NULL; ssh_string e = NULL; ssh_string n = NULL; 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; + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; OSSL_PARAM *params = NULL; #endif /* OPENSSL_VERSION_NUMBER */ + uint8_t *ed25519_pubkey = NULL; + uint8_t *ed25519_privkey = NULL; + size_t key_len = 0; buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); @@ -1360,192 +1542,456 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { + 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; + } + 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; + } + rc = OSSL_PARAM_get_BN(out_param, &be); + if (rc != 1) { + goto fail; + } + 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; + } + rc = OSSL_PARAM_get_BN(out_param, &bn); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + e = ssh_make_bignum_string((BIGNUM *)be); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string((BIGNUM *)bn); + if (n == NULL) { + goto fail; + } + + if (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 *be, *bn; - const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); - RSA_get0_key(key_rsa, &bn, &be, NULL); + 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 - const OSSL_PARAM *out_param = NULL; - rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + OSSL_PARAM_free(params); + 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_E); + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); if (out_param == NULL) { - SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found"); + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); goto fail; } - rc = OSSL_PARAM_get_BN(out_param, &be); + rc = OSSL_PARAM_get_BN(out_param, &bp); if (rc != 1) { goto fail; } - out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N); + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); if (out_param == NULL) { - SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found"); + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); goto fail; } - rc = OSSL_PARAM_get_BN(out_param, &bn); + 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 */ - e = ssh_make_bignum_string((BIGNUM *)be); - if (e == 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; } - n = ssh_make_bignum_string((BIGNUM *)bn); - if (n == NULL) { + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { goto fail; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { + + 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(e); - SSH_STRING_FREE(e); - e = NULL; - ssh_string_burn(n); - SSH_STRING_FREE(n); - n = NULL; + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); #if OPENSSL_VERSION_NUMBER >= 0x30000000L - bignum_safe_free(bn); - bignum_safe_free(be); - OSSL_PARAM_free(params); + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); #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){ + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + (uint8_t *)ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + + if (type == SSH_KEY_PRIVATE && key->type == SSH_KEYTYPE_ED25519) { + key_len = 0; + rc = EVP_PKEY_get_raw_private_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key length: %s", + ERR_error_string(ERR_get_error(), NULL)); goto fail; } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); goto fail; } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: + + ed25519_privkey = malloc(key_len); + if (ed25519_privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_private_key(key->key, + ed25519_privkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dPP", + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, + ed25519_privkey, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + SAFE_FREE(ed25519_privkey); + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ED25519) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ED25519) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; + } + } + + SAFE_FREE(ed25519_pubkey); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_OPENSSL_ECC - { + { #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; + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + OSSL_PARAM *locate_param = NULL; #else - const EC_GROUP *group = NULL; - const EC_POINT *point = NULL; - EC_KEY *ec = NULL; + 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; - } + 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; - } + 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(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; - } + 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); + 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 = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + goto fail; + } - locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); + 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; - } + 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 */ - 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; - } + 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; + } - e = pki_key_make_ecpoint_string(group, point); - EC_GROUP_free(group); - EC_POINT_free(point); + 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; - } + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - OSSL_PARAM_free(params); -#endif /* OPENSSL_VERSION_NUMBER */ + ssh_string_burn(e); + 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 && key->type != SSH_KEYTYPE_SK_ECDSA) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } - break; + 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 (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + 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 != SSH_OK) { + goto fail; + } + } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } #endif /* HAVE_OPENSSL_ECC */ - case SSH_KEYTYPE_UNKNOWN: - default: - goto fail; + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; } makestring: @@ -1575,6 +2021,10 @@ ssh_string pki_publickey_to_blob(const ssh_key key) 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); @@ -1582,8 +2032,15 @@ ssh_string pki_publickey_to_blob(const ssh_key key) 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 */ + free(ed25519_pubkey); + if (ed25519_privkey) { + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + free(ed25519_privkey); + } return NULL; } @@ -1599,9 +2056,9 @@ static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) const BIGNUM *pr = NULL, *ps = NULL; const unsigned char *raw_sig_data = NULL; - size_t raw_sig_len; + long raw_sig_len; - ECDSA_SIG *ecdsa_sig; + ECDSA_SIG *ecdsa_sig = NULL; int rc; @@ -1612,7 +2069,7 @@ static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) if (raw_sig_data == NULL) { return NULL; } - raw_sig_len = ssh_string_len(sig->raw_sig); + raw_sig_len = (long)ssh_string_len(sig->raw_sig); ecdsa_sig = d2i_ECDSA_SIG(NULL, &raw_sig_data, raw_sig_len); if (ecdsa_sig == NULL) { @@ -1694,6 +2151,11 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) sig_blob = pki_ecdsa_signature_to_blob(sig); break; #endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; default: case SSH_KEYTYPE_UNKNOWN: SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); @@ -1707,7 +2169,7 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, const ssh_string sig_blob, ssh_signature sig) { - uint32_t pad_len = 0; + size_t pad_len = 0; char *blob_orig = NULL; char *blob_padded_data = NULL; ssh_string sig_blob_padded = NULL; @@ -1769,7 +2231,7 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, } /* front-pad the buffer with zeroes */ - explicit_bzero(blob_padded_data, pad_len); + ssh_burn(blob_padded_data, pad_len); /* fill the rest with the actual signature blob */ memcpy(blob_padded_data + pad_len, blob_orig, len); @@ -1790,8 +2252,8 @@ static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), ECDSA_SIG *ecdsa_sig = NULL; BIGNUM *pr = NULL, *ps = NULL; - ssh_string r; - ssh_string s; + ssh_string r = NULL; + ssh_string s = NULL; ssh_buffer buf = NULL; uint32_t rlen; @@ -1808,9 +2270,12 @@ static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), return SSH_ERROR; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buf); + rc = ssh_buffer_add_data(buf, ssh_string_data(sig_blob), - ssh_string_len(sig_blob)); + (uint32_t)ssh_string_len(sig_blob)); if (rc < 0) { goto error; } @@ -1895,17 +2360,17 @@ static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), sig->raw_sig = ssh_string_new(raw_sig_len); if (sig->raw_sig == NULL) { - explicit_bzero(raw_sig_data, raw_sig_len); + ssh_burn(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); + ssh_burn(raw_sig_data, raw_sig_len); goto error; } - explicit_bzero(raw_sig_data, raw_sig_len); + ssh_burn(raw_sig_data, raw_sig_len); SAFE_FREE(raw_sig_data); ECDSA_SIG_free(ecdsa_sig); return SSH_OK; @@ -2035,6 +2500,10 @@ 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: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: if (key->key == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); goto error; @@ -2046,37 +2515,6 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) } pkey = key->key; break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ED25519: - case SSH_KEYTYPE_SK_ED25519_CERT01: - if (ssh_key_is_private(key)) { - if (key->ed25519_privkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_privkey"); - goto error; - } - /* In OpenSSL, the input is the private key seed only, which means - * the first half of the SSH private key (the second half is the - * public key). Both keys have the same length (32 bytes) */ - pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, - (const uint8_t *)key->ed25519_privkey, - ED25519_KEY_LEN); - } else { - if (key->ed25519_pubkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, "NULL key->ed25519_pubkey"); - goto error; - } - pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, - (const uint8_t *)key->ed25519_pubkey, - ED25519_KEY_LEN); - } - if (pkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to create ed25519 EVP_PKEY: %s", - ERR_error_string(ERR_get_error(), NULL)); - return NULL; - } - break; case SSH_KEYTYPE_UNKNOWN: default: SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", @@ -2211,7 +2649,7 @@ ssh_signature pki_sign_data(const ssh_key privkey, EVP_MD_CTX_free(ctx); } if (raw_sig_data != NULL) { - explicit_bzero(raw_sig_data, raw_sig_len); + ssh_burn(raw_sig_data, raw_sig_len); } SAFE_FREE(raw_sig_data); EVP_PKEY_free(pkey); @@ -2241,7 +2679,7 @@ int pki_verify_data_signature(ssh_signature signature, EVP_PKEY *pkey = NULL; unsigned char *raw_sig_data = NULL; - unsigned int raw_sig_len; + size_t raw_sig_len; /* Function return code * Do not change this variable throughout the function until the signature @@ -2363,8 +2801,6 @@ int pki_key_generate_ed25519(ssh_key key) int evp_rc; EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; - size_t privkey_len = ED25519_KEY_LEN; - size_t pubkey_len = ED25519_KEY_LEN; if (key == NULL) { return SSH_ERROR; @@ -2393,41 +2829,9 @@ int pki_key_generate_ed25519(ssh_key key) ERR_error_string(ERR_get_error(), NULL)); goto error; } - - key->ed25519_privkey = malloc(ED25519_KEY_LEN); - if (key->ed25519_privkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to allocate memory for ed25519 private key"); - goto error; - } - - key->ed25519_pubkey = malloc(ED25519_KEY_LEN); - if (key->ed25519_pubkey == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to allocate memory for ed25519 public key"); - goto error; - } - - evp_rc = EVP_PKEY_get_raw_private_key(pkey, (uint8_t *)key->ed25519_privkey, - &privkey_len); - if (evp_rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get ed25519 raw private key: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto error; - } - - evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)key->ed25519_pubkey, - &pubkey_len); - if (evp_rc != 1) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to get ed25519 raw public key: %s", - ERR_error_string(ERR_get_error(), NULL)); - goto error; - } + key->key = pkey; EVP_PKEY_CTX_free(pctx); - EVP_PKEY_free(pkey); return SSH_OK; error: @@ -2437,16 +2841,11 @@ int pki_key_generate_ed25519(ssh_key key) if (pkey != NULL) { EVP_PKEY_free(pkey); } - SAFE_FREE(key->ed25519_privkey); - SAFE_FREE(key->ed25519_pubkey); return SSH_ERROR; } #ifdef WITH_PKCS11_URI -#ifdef WITH_PKCS11_PROVIDER -static bool pkcs11_provider_failed = false; -#endif /** * @internal @@ -2512,19 +2911,10 @@ int pki_uri_import(const char *uri_name, /* 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; - } + rv = pki_load_pkcs11_provider(); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to load or initialize pkcs11 provider"); + goto fail; } store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); @@ -2551,16 +2941,18 @@ int pki_uri_import(const char *uri_name, 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_INFO_free(info); + continue; } + OSSL_STORE_INFO_free(info); + break; } OSSL_STORE_close(store); if (pkey == NULL) { @@ -2605,6 +2997,9 @@ int pki_uri_import(const char *uri_name, break; #endif + case EVP_PKEY_ED25519: + type = SSH_KEYTYPE_ED25519; + break; default: SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d", EVP_PKEY_base_id(pkey)); diff --git a/src/pki_ed25519.c b/src/pki_ed25519.c index 6a5a4a8a..f3954064 100644 --- a/src/pki_ed25519.c +++ b/src/pki_ed25519.c @@ -28,6 +28,205 @@ #include "libssh/ed25519.h" #include "libssh/buffer.h" +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; +} + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + /* In the internal implementation, the private key is the concatenation of + * the private seed with the public key. */ + key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + memcpy(key->ed25519_privkey, ssh_string_data(privkey), 2 * ED25519_KEY_LEN); + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; + +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Compare ed25519 keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int +pki_ed25519_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int cmp; + + switch (what) { + case SSH_KEY_CMP_PRIVATE: + if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { + return 1; + } + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + cmp = secure_memcmp(k1->ed25519_privkey, + k2->ed25519_privkey, + 2 * ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + FALL_THROUGH; + case SSH_KEY_CMP_PUBLIC: + if (k1->ed25519_pubkey == NULL || k2->ed25519_pubkey == NULL) { + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, k2->ed25519_pubkey, ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; + } + + return 0; +} + +/** + * @internal + * + * @brief Duplicate an Ed25519 key + * + * @param[out] new Pre-initialized ssh_key structure + * + * @param[in] key Key to copy + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) +{ + if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + if (key->ed25519_privkey != NULL) { + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + new_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (new_key->ed25519_privkey == NULL) { + return SSH_ERROR; + } + memcpy(new_key->ed25519_privkey, + key->ed25519_privkey, + 2 * ED25519_KEY_LEN); + } + + if (key->ed25519_pubkey != NULL) { + 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_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Outputs an Ed25519 public key in a blob buffer. + * + * @param[out] buffer Output buffer + * + * @param[in] key Key to output + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) +{ + int rc; + + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + key->ed25519_pubkey); + + return rc; +} + +/** @internal + * @brief 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; +} + int pki_key_generate_ed25519(ssh_key key) { int rc; @@ -62,7 +261,7 @@ int pki_ed25519_sign(const ssh_key privkey, size_t hlen) { int rc; - uint8_t *buffer; + uint8_t *buffer = NULL; uint64_t dlen = 0; buffer = malloc(hlen + ED25519_SIG_LEN); @@ -104,8 +303,8 @@ int pki_ed25519_verify(const ssh_key pubkey, size_t hlen) { uint64_t mlen = 0; - uint8_t *buffer; - uint8_t *buffer2; + uint8_t *buffer = NULL; + uint8_t *buffer2 = NULL; int rc; if (pubkey == NULL || sig == NULL || @@ -132,8 +331,8 @@ int pki_ed25519_verify(const ssh_key pubkey, hlen + ED25519_SIG_LEN, *pubkey->ed25519_pubkey); - explicit_bzero(buffer, hlen + ED25519_SIG_LEN); - explicit_bzero(buffer2, hlen); + ssh_burn(buffer, hlen + ED25519_SIG_LEN); + ssh_burn(buffer2, hlen); SAFE_FREE(buffer); SAFE_FREE(buffer2); if (rc == 0) { diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c index f9f69649..26e40efb 100644 --- a/src/pki_ed25519_common.c +++ b/src/pki_ed25519_common.c @@ -27,181 +27,6 @@ #include "libssh/pki_priv.h" #include "libssh/buffer.h" -int pki_privkey_build_ed25519(ssh_key key, - ssh_string pubkey, - ssh_string privkey) -{ - if (ssh_string_len(pubkey) != ED25519_KEY_LEN || - ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) - { - SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); - return SSH_ERROR; - } - -#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); -#else - /* In the internal implementation, the private key is the concatenation of - * the private seed with the public key. */ - key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); -#endif - if (key->ed25519_privkey == NULL) { - goto error; - } - - key->ed25519_pubkey = malloc(ED25519_KEY_LEN); - if (key->ed25519_pubkey == NULL) { - goto error; - } - -#ifdef HAVE_LIBCRYPTO - memcpy(key->ed25519_privkey, ssh_string_data(privkey), - ED25519_KEY_LEN); -#else - memcpy(key->ed25519_privkey, ssh_string_data(privkey), - 2 * ED25519_KEY_LEN); -#endif - memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), - ED25519_KEY_LEN); - - return SSH_OK; - -error: - SAFE_FREE(key->ed25519_privkey); - SAFE_FREE(key->ed25519_pubkey); - - return SSH_ERROR; -} - -/** - * @internal - * - * @brief Compare ed25519 keys if they are equal. - * - * @param[in] k1 The first key to compare. - * - * @param[in] k2 The second key to compare. - * - * @param[in] what What part or type of the key do you want to compare. - * - * @return 0 if equal, 1 if not. - */ -int pki_ed25519_key_cmp(const ssh_key k1, - const ssh_key k2, - enum ssh_keycmp_e what) -{ - int cmp; - - switch(what) { - case SSH_KEY_CMP_PRIVATE: - if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { - return 1; - } -#ifdef HAVE_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); -#else - /* In the internal implementation, the private key is the concatenation - * of the private seed with the public key. */ - cmp = memcmp(k1->ed25519_privkey, k2->ed25519_privkey, - 2 * ED25519_KEY_LEN); -#endif - if (cmp != 0) { - return 1; - } - FALL_THROUGH; - case SSH_KEY_CMP_PUBLIC: - if (k1->ed25519_pubkey == NULL || k2->ed25519_pubkey == NULL) { - return 1; - } - cmp = memcmp(k1->ed25519_pubkey, k2->ed25519_pubkey, ED25519_KEY_LEN); - if (cmp != 0) { - return 1; - } - } - - return 0; -} - -/** - * @internal - * - * @brief Duplicate an Ed25519 key - * - * @param[out] new Pre-initialized ssh_key structure - * - * @param[in] key Key to copy - * - * @return SSH_ERROR on error, SSH_OK on success - */ -int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) -{ - if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { - return SSH_ERROR; - } - - if (key->ed25519_privkey != NULL) { -#ifdef HAVE_LIBCRYPTO - /* In OpenSSL implementation, the private key is the original private - * seed, without the public key. */ - 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_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); -#endif - if (new_key->ed25519_privkey == NULL) { - return SSH_ERROR; - } -#ifdef HAVE_LIBCRYPTO - memcpy(new_key->ed25519_privkey, key->ed25519_privkey, ED25519_KEY_LEN); -#else - memcpy(new_key->ed25519_privkey, key->ed25519_privkey, 2 * ED25519_KEY_LEN); -#endif - } - - if (key->ed25519_pubkey != NULL) { - 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_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); - } - - return SSH_OK; -} - -/** - * @internal - * - * @brief Outputs an Ed25519 public key in a blob buffer. - * - * @param[out] buffer Output buffer - * - * @param[in] key Key to output - * - * @return SSH_ERROR on error, SSH_OK on success - */ -int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) -{ - int rc; - - if (key->ed25519_pubkey == NULL){ - return SSH_ERROR; - } - - rc = ssh_buffer_pack(buffer, - "dP", - (uint32_t)ED25519_KEY_LEN, - (size_t)ED25519_KEY_LEN, key->ed25519_pubkey); - - return rc; -} - /** * @internal * @@ -213,7 +38,7 @@ int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) */ ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) { - ssh_string sig_blob; + ssh_string sig_blob = NULL; int rc; #ifdef HAVE_LIBCRYPTO diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index a1674900..802fa7e2 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -28,132 +28,137 @@ #ifdef HAVE_LIBGCRYPT #include -#include -#include -#include #include +#include #include +#include +#include -#include "libssh/priv.h" #include "libssh/buffer.h" -#include "libssh/session.h" -#include "libssh/wrapper.h" #include "libssh/misc.h" #include "libssh/pki.h" #include "libssh/pki_priv.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" -#define MAXLINESIZE 80 -#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" -#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define MAXLINESIZE 80 +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" #define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" -#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" -#define MAX_KEY_SIZE 32 -#define MAX_PASSPHRASE_SIZE 1024 -#define ASN1_INTEGER 2 -#define ASN1_BIT_STRING 3 -#define ASN1_OCTET_STRING 4 +#define MAX_KEY_SIZE 32 +#define MAX_PASSPHRASE_SIZE 1024 +#define ASN1_INTEGER 2 +#define ASN1_BIT_STRING 3 +#define ASN1_OCTET_STRING 4 #define ASN1_OBJECT_IDENTIFIER 6 -#define ASN1_SEQUENCE 48 -#define PKCS5_SALT_LEN 8 - -static int load_iv(const char *header, unsigned char *iv, int iv_len) { - int i; - int j; - int k; - - memset(iv, 0, iv_len); - for (i = 0; i < iv_len; i++) { - if ((header[2*i] >= '0') && (header[2*i] <= '9')) - j = header[2*i] - '0'; - else if ((header[2*i] >= 'A') && (header[2*i] <= 'F')) - j = header[2*i] - 'A' + 10; - else if ((header[2*i] >= 'a') && (header[2*i] <= 'f')) - j = header[2*i] - 'a' + 10; - else - return -1; - if ((header[2*i+1] >= '0') && (header[2*i+1] <= '9')) - k = header[2*i+1] - '0'; - else if ((header[2*i+1] >= 'A') && (header[2*i+1] <= 'F')) - k = header[2*i+1] - 'A' + 10; - else if ((header[2*i+1] >= 'a') && (header[2*i+1] <= 'f')) - k = header[2*i+1] - 'a' + 10; - else - return -1; - iv[i] = (j << 4) + k; - } - return 0; +#define ASN1_SEQUENCE 48 +#define PKCS5_SALT_LEN 8 + +static int load_iv(const char *header, unsigned char *iv, int iv_len) +{ + int i; + int j; + int k; + + memset(iv, 0, iv_len); + for (i = 0; i < iv_len; i++) { + if ((header[2 * i] >= '0') && (header[2 * i] <= '9')) + j = header[2 * i] - '0'; + else if ((header[2 * i] >= 'A') && (header[2 * i] <= 'F')) + j = header[2 * i] - 'A' + 10; + else if ((header[2 * i] >= 'a') && (header[2 * i] <= 'f')) + j = header[2 * i] - 'a' + 10; + else + return -1; + if ((header[2 * i + 1] >= '0') && (header[2 * i + 1] <= '9')) + k = header[2 * i + 1] - '0'; + else if ((header[2 * i + 1] >= 'A') && (header[2 * i + 1] <= 'F')) + k = header[2 * i + 1] - 'A' + 10; + else if ((header[2 * i + 1] >= 'a') && (header[2 * i + 1] <= 'f')) + k = header[2 * i + 1] - 'a' + 10; + else + return -1; + iv[i] = (j << 4) + k; + } + return 0; } -static uint32_t char_to_u32(unsigned char *data, uint32_t size) { - uint32_t ret; - uint32_t i; +static uint32_t char_to_u32(unsigned char *data, uint32_t size) +{ + uint32_t ret; + uint32_t i; - for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) - ; - return ret; + for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) + ; + return ret; } -static uint32_t asn1_get_len(ssh_buffer buffer) { - uint32_t len; - unsigned char tmp[4]; - - if (ssh_buffer_get_data(buffer,tmp,1) == 0) { - return 0; - } +static uint32_t asn1_get_len(ssh_buffer buffer) +{ + uint32_t len; + unsigned char tmp[4]; - if (tmp[0] > 127) { - len = tmp[0] & 127; - if (len > 4) { - return 0; /* Length doesn't fit in u32. Can this really happen? */ + if (ssh_buffer_get_data(buffer, tmp, 1) == 0) { + return 0; } - if (ssh_buffer_get_data(buffer,tmp,len) == 0) { - return 0; + + if (tmp[0] > 127) { + len = tmp[0] & 127; + if (len > 4) { + return 0; /* Length doesn't fit in u32. Can this really happen? */ + } + if (ssh_buffer_get_data(buffer, tmp, len) == 0) { + return 0; + } + len = char_to_u32(tmp, len); + } else { + len = char_to_u32(tmp, 1); } - len = char_to_u32(tmp, len); - } else { - len = char_to_u32(tmp, 1); - } - return len; + return len; } -static ssh_string asn1_get(ssh_buffer buffer, unsigned char want) { - ssh_string str; - unsigned char type; - uint32_t size; +static ssh_string asn1_get(ssh_buffer buffer, unsigned char want) +{ + ssh_string str = NULL; + unsigned char type; + uint32_t size; - if (ssh_buffer_get_data(buffer, &type, 1) == 0 || type != want) { - return NULL; - } - size = asn1_get_len(buffer); - if (size == 0) { - return NULL; - } + if (ssh_buffer_get_data(buffer, &type, 1) == 0 || type != want) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } - str = ssh_string_new(size); - if (str == NULL) { - return NULL; - } + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } - if (ssh_buffer_get_data(buffer, ssh_string_data(str), size) == 0) { - SSH_STRING_FREE(str); - return NULL; - } + if (ssh_buffer_get_data(buffer, ssh_string_data(str), size) == 0) { + SSH_STRING_FREE(str); + return NULL; + } - return str; + return str; } -static ssh_string asn1_get_int(ssh_buffer buffer) { - return asn1_get(buffer, ASN1_INTEGER); +static ssh_string asn1_get_int(ssh_buffer buffer) +{ + return asn1_get(buffer, ASN1_INTEGER); } static ssh_string asn1_get_bit_string(ssh_buffer buffer) { - ssh_string str; + ssh_string str = NULL; unsigned char type; uint32_t size; - unsigned char unused, last, *p; + unsigned char unused, last, *p = NULL; uint32_t len; len = ssh_buffer_get_data(buffer, &type, 1); @@ -205,34 +210,35 @@ static ssh_string asn1_get_bit_string(ssh_buffer buffer) return str; } -static int asn1_check_sequence(ssh_buffer buffer) { - unsigned char *j = NULL; - unsigned char tmp; - int i; - uint32_t size; - uint32_t padding; +static int asn1_check_sequence(ssh_buffer buffer) +{ + unsigned char *j = NULL; + unsigned char tmp; + int i; + uint32_t size; + uint32_t padding; - if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { - return 0; - } - - size = asn1_get_len(buffer); - if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { - for (i = ssh_buffer_get_len(buffer) - size, - j = (unsigned char*)ssh_buffer_get(buffer) + size; - i; - i--, j++) - { - if (*j != padding) { /* padding is allowed */ - return 0; /* but nothing else */ - } + if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { + return 0; + } + + size = asn1_get_len(buffer); + if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { + for (i = ssh_buffer_get_len(buffer) - size, + j = (unsigned char *)ssh_buffer_get(buffer) + size; + i; + i--, j++) { + if (*j != padding) { /* padding is allowed */ + return 0; /* but nothing else */ + } + } } - } - return 1; + return 1; } -static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) { +static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) +{ unsigned char tmp; uint32_t len; @@ -241,44 +247,48 @@ static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) { return 0; } - (void) asn1_get_len(buffer); + (void)asn1_get_len(buffer); return 1; } -static int passphrase_to_key(char *data, unsigned int datalen, - unsigned char *salt, unsigned char *key, unsigned int keylen) { - MD5CTX md; - unsigned char digest[MD5_DIGEST_LEN] = {0}; - unsigned int i; - unsigned int j; - unsigned int md_not_empty; - - for (j = 0, md_not_empty = 0; j < keylen; ) { - md = md5_init(); - if (md == NULL) { - return -1; - } +static int passphrase_to_key(char *data, + unsigned int datalen, + unsigned char *salt, + unsigned char *key, + unsigned int keylen) +{ + MD5CTX md; + unsigned char digest[MD5_DIGEST_LEN] = {0}; + unsigned int i; + unsigned int j; + unsigned int md_not_empty; + + for (j = 0, md_not_empty = 0; j < keylen;) { + md = md5_init(); + if (md == NULL) { + return -1; + } - if (md_not_empty) { - md5_update(md, digest, MD5_DIGEST_LEN); - } else { - md_not_empty = 1; - } + if (md_not_empty) { + md5_update(md, digest, MD5_DIGEST_LEN); + } else { + md_not_empty = 1; + } - md5_update(md, data, datalen); - if (salt) { - md5_update(md, salt, PKCS5_SALT_LEN); - } - md5_final(digest, md); + md5_update(md, data, datalen); + if (salt) { + md5_update(md, salt, PKCS5_SALT_LEN); + } + md5_final(digest, md); - for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { - if (key) { - key[j] = digest[i]; - } + for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { + if (key) { + key[j] = digest[i]; + } + } } - } - return 0; + return 0; } void pki_key_clean(ssh_key key) @@ -295,127 +305,139 @@ void pki_key_clean(ssh_key key) 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, - void *userdata, - const char *desc) +static int privatekey_decrypt(int algo, + int mode, + unsigned int key_len, + unsigned char *iv, + unsigned int iv_len, + ssh_buffer data, + ssh_auth_callback cb, + void *userdata, + const char *desc) { - char passphrase[MAX_PASSPHRASE_SIZE] = {0}; - unsigned char key[MAX_KEY_SIZE] = {0}; - unsigned char *tmp = NULL; - gcry_cipher_hd_t cipher; - int rc = -1; + char passphrase[MAX_PASSPHRASE_SIZE] = {0}; + unsigned char key[MAX_KEY_SIZE] = {0}; + unsigned char *tmp = NULL; + gcry_cipher_hd_t cipher; + int rc = -1; - if (!algo) { - return -1; - } + if (!algo) { + return -1; + } - if (cb) { - rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); - if (rc < 0) { - return -1; + if (cb) { + rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); + if (rc < 0) { + return -1; + } + } else if (cb == NULL && userdata != NULL) { + snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *)userdata); } - } else if (cb == NULL && userdata != NULL) { - snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *) userdata); - } - if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < 0) { - return -1; - } - - if (gcry_cipher_open(&cipher, algo, mode, 0) - || gcry_cipher_setkey(cipher, key, key_len) - || gcry_cipher_setiv(cipher, iv, iv_len) - || (tmp = calloc(ssh_buffer_get_len(data), sizeof(unsigned char))) == NULL - || gcry_cipher_decrypt(cipher, tmp, ssh_buffer_get_len(data), - ssh_buffer_get(data), ssh_buffer_get_len(data))) { - gcry_cipher_close(cipher); - return -1; - } + if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < + 0) { + return -1; + } + + if (gcry_cipher_open(&cipher, algo, mode, 0) || + gcry_cipher_setkey(cipher, key, key_len) || + gcry_cipher_setiv(cipher, iv, iv_len) || + (tmp = calloc(ssh_buffer_get_len(data), sizeof(unsigned char))) == + NULL || + gcry_cipher_decrypt(cipher, + tmp, + ssh_buffer_get_len(data), + ssh_buffer_get(data), + ssh_buffer_get_len(data))) { + gcry_cipher_close(cipher); + return -1; + } - memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); + memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); - SAFE_FREE(tmp); - gcry_cipher_close(cipher); + SAFE_FREE(tmp); + gcry_cipher_close(cipher); - return 0; + return 0; } -static int privatekey_dek_header(const char *header, unsigned int header_len, - int *algo, int *mode, unsigned int *key_len, unsigned char **iv, - unsigned int *iv_len) { - unsigned int iv_pos; - - if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) - { - *algo = GCRY_CIPHER_3DES; - iv_pos = 13; - *mode = GCRY_CIPHER_MODE_CBC; - *key_len = 24; - *iv_len = 8; - } - else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) - { - *algo = GCRY_CIPHER_DES; - iv_pos = 8; - *mode = GCRY_CIPHER_MODE_CBC; - *key_len = 8; - *iv_len = 8; - } - else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) - { - *algo = GCRY_CIPHER_AES128; - iv_pos = 12; - *mode = GCRY_CIPHER_MODE_CBC; - *key_len = 16; - *iv_len = 16; - } - else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) - { - *algo = GCRY_CIPHER_AES192; - iv_pos = 12; - *mode = GCRY_CIPHER_MODE_CBC; - *key_len = 24; - *iv_len = 16; - } - else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) - { - *algo = GCRY_CIPHER_AES256; - iv_pos = 12; - *mode = GCRY_CIPHER_MODE_CBC; - *key_len = 32; - *iv_len = 16; - } else { - return -1; - } +static int privatekey_dek_header(const char *header, + unsigned int header_len, + int *algo, + int *mode, + unsigned int *key_len, + unsigned char **iv, + unsigned int *iv_len) +{ + unsigned int iv_pos; + + if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) { + *algo = GCRY_CIPHER_3DES; + iv_pos = 13; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 8; + } else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) { + *algo = GCRY_CIPHER_DES; + iv_pos = 8; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 8; + *iv_len = 8; + } else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES128; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 16; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES192; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES256; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 32; + *iv_len = 16; + } else { + return -1; + } - *iv = malloc(*iv_len); - if (*iv == NULL) { - return -1; - } + *iv = malloc(*iv_len); + if (*iv == NULL) { + return -1; + } - return load_iv(header + iv_pos, *iv, *iv_len); + return load_iv(header + iv_pos, *iv, *iv_len); } -#define get_next_line(p, len) { \ - while(p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ +#define get_next_line(p, len) \ + { \ + while (p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ len++; \ - if(p[len] == '\0') /* EOL */ \ + if (p[len] == '\0') /* EOL */ \ eol = true; \ - else /* calculate length */ \ - for(p += len, len = 0; p[len] && p[len] != '\n' \ - && p[len] != '\r'; len++); \ - } - -static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, - ssh_auth_callback cb, void *userdata, const char *desc) { + else /* calculate length */ \ + for (p += len, len = 0; \ + p[len] && p[len] != '\n' && p[len] != '\r'; \ + len++) \ + ; \ + } + +static ssh_buffer privatekey_string_to_buffer(const char *pkey, + int type, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ ssh_buffer buffer = NULL; ssh_buffer out = NULL; - const char *p; + const char *p = NULL; unsigned char *iv = NULL; - const char *header_begin; - const char *header_end; + const char *header_begin = NULL; + const char *header_end = NULL; unsigned int header_begin_size; unsigned int header_end_size; unsigned int key_len = 0; @@ -430,20 +452,20 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, return NULL; } - switch(type) { - case SSH_KEYTYPE_RSA: - header_begin = RSA_HEADER_BEGIN; - header_end = RSA_HEADER_END; - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - header_begin = ECDSA_HEADER_BEGIN; - header_end = ECDSA_HEADER_END; - break; - default: - SSH_BUFFER_FREE(buffer); - return NULL; + switch (type) { + case SSH_KEYTYPE_RSA: + header_begin = RSA_HEADER_BEGIN; + header_end = RSA_HEADER_END; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + header_begin = ECDSA_HEADER_BEGIN; + header_end = ECDSA_HEADER_END; + break; + default: + SSH_BUFFER_FREE(buffer); + return NULL; } header_begin_size = strlen(header_begin); @@ -453,7 +475,7 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, len = 0; get_next_line(p, len); - while(!eol && strncmp(p, header_begin, header_begin_size)) { + while (!eol && strncmp(p, header_begin, header_begin_size)) { /* skip line */ get_next_line(p, len); } @@ -485,8 +507,13 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, SSH_BUFFER_FREE(buffer); return NULL; } - if (privatekey_dek_header(p, len, &algo, &mode, &key_len, - &iv, &iv_len) < 0) { + if (privatekey_dek_header(p, + len, + &algo, + &mode, + &key_len, + &iv, + &iv_len) < 0) { SSH_BUFFER_FREE(buffer); SAFE_FREE(iv); return NULL; @@ -497,7 +524,7 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, return NULL; } } else { - if(len > 0) { + if (len > 0) { if (ssh_buffer_add_data(buffer, p, len) < 0) { SSH_BUFFER_FREE(buffer); SAFE_FREE(iv); @@ -507,7 +534,7 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, } get_next_line(p, len); - while(!eol && strncmp(p, header_end, header_end_size) != 0) { + while (!eol && strncmp(p, header_end, header_end_size) != 0) { if (ssh_buffer_add_data(buffer, p, len) < 0) { SSH_BUFFER_FREE(buffer); SAFE_FREE(iv); @@ -536,8 +563,15 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, } if (algo) { - if (privatekey_decrypt(algo, mode, key_len, iv, iv_len, out, - cb, userdata, desc) < 0) { + if (privatekey_decrypt(algo, + mode, + key_len, + iv, + iv_len, + out, + cb, + userdata, + desc) < 0) { SSH_BUFFER_FREE(out); SAFE_FREE(iv); return NULL; @@ -548,97 +582,112 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, return out; } -static int b64decode_rsa_privatekey(const char *pkey, gcry_sexp_t *r, - ssh_auth_callback cb, void *userdata, const char *desc) { - const unsigned char *data; - ssh_string n = NULL; - ssh_string e = NULL; - ssh_string d = NULL; - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string unused1 = NULL; - ssh_string unused2 = NULL; - ssh_string u = NULL; - ssh_string v = NULL; - ssh_buffer buffer = NULL; - int rc = 1; - - buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); - if (buffer == NULL) { - return 0; - } +static int b64decode_rsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + gcry_error_t rv = 0; - if (!asn1_check_sequence(buffer)) { - SSH_BUFFER_FREE(buffer); - return 0; - } + buffer = + privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); + if (buffer == NULL) { + return 0; + } - v = asn1_get_int(buffer); - if (v == NULL) { - SSH_BUFFER_FREE(buffer); - return 0; - } + if (!asn1_check_sequence(buffer)) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + SSH_STRING_FREE(v); + SSH_BUFFER_FREE(buffer); + return 0; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); - data = ssh_string_data(v); - if (ssh_string_len(v) != 1 || data[0] != 0) { - SSH_STRING_FREE(v); SSH_BUFFER_FREE(buffer); - return 0; - } - - n = asn1_get_int(buffer); - e = asn1_get_int(buffer); - d = asn1_get_int(buffer); - q = asn1_get_int(buffer); - p = asn1_get_int(buffer); - unused1 = asn1_get_int(buffer); - unused2 = asn1_get_int(buffer); - u = asn1_get_int(buffer); - - SSH_BUFFER_FREE(buffer); - - if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || - unused1 == NULL || unused2 == NULL|| u == NULL) { - rc = 0; - goto error; - } - - if (gcry_sexp_build(r, NULL, - "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", - ssh_string_len(n), ssh_string_data(n), - ssh_string_len(e), ssh_string_data(e), - ssh_string_len(d), ssh_string_data(d), - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(u), ssh_string_data(u))) { - rc = 0; - } + + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL || u == NULL) { + rc = 0; + goto error; + } + + rv = gcry_sexp_build( + r, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(u), + ssh_string_data(u)); + if (rv) { + rc = 0; + } error: - ssh_string_burn(n); - SSH_STRING_FREE(n); - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(d); - SSH_STRING_FREE(d); - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - SSH_STRING_FREE(unused1); - SSH_STRING_FREE(unused2); - ssh_string_burn(u); - SSH_STRING_FREE(u); - SSH_STRING_FREE(v); - - return rc; + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + SSH_STRING_FREE(unused1); + SSH_STRING_FREE(unused2); + ssh_string_burn(u); + SSH_STRING_FREE(u); + SSH_STRING_FREE(v); + + return rc; } #ifdef HAVE_GCRYPT_ECC static int pki_key_ecdsa_to_nid(gcry_sexp_t k) { - gcry_sexp_t sexp; - const char *tmp; + gcry_sexp_t sexp = NULL; + const char *tmp = NULL; size_t size; sexp = gcry_sexp_find_token(k, "curve", 0); @@ -681,14 +730,14 @@ static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(gcry_sexp_t k) nid = pki_key_ecdsa_to_nid(k); switch (nid) { - case NID_gcrypt_nistp256: - return SSH_KEYTYPE_ECDSA_P256; - case NID_gcrypt_nistp384: - return SSH_KEYTYPE_ECDSA_P384; - case NID_gcrypt_nistp521: - return SSH_KEYTYPE_ECDSA_P521; - default: - return SSH_KEYTYPE_UNKNOWN; + case NID_gcrypt_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_gcrypt_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_gcrypt_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; } } @@ -706,7 +755,6 @@ static const char *pki_key_ecdsa_nid_to_gcrypt_name(int nid) return "unknown"; } - const char *pki_key_ecdsa_nid_to_name(int nid) { switch (nid) { @@ -766,27 +814,28 @@ static int asn1_oi_to_nid(const ssh_string oi) size_t length; const char *identifier; } *e, mapping[] = { - {NID_gcrypt_nistp256, 8, "\x2a\x86\x48\xce\x3d\x03\x01\x07"}, - {NID_gcrypt_nistp384, 5, "\x2b\x81\x04\x00\x22"}, - {NID_gcrypt_nistp521, 5, "\x2b\x81\x04\x00\x23"}, - {0}, - }; + {NID_gcrypt_nistp256, 8, "\x2a\x86\x48\xce\x3d\x03\x01\x07"}, + {NID_gcrypt_nistp384, 5, "\x2b\x81\x04\x00\x22"}, + {NID_gcrypt_nistp521, 5, "\x2b\x81\x04\x00\x23"}, + {0}, + }; size_t len = ssh_string_len(oi); for (e = mapping; e->length; e++) { - if (len == e->length - && memcmp(ssh_string_data(oi), e->identifier, len) == 0) { + if (len == e->length && + memcmp(ssh_string_data(oi), e->identifier, len) == 0) { return e->nid; } } return -1; } -static int b64decode_ecdsa_privatekey(const char *pkey, gcry_sexp_t *r, +static int b64decode_ecdsa_privatekey(const char *pkey, + gcry_sexp_t *r, ssh_auth_callback cb, void *userdata, const char *desc) { - const unsigned char *data; + const unsigned char *data = NULL; ssh_buffer buffer = NULL; gcry_error_t err = 0; ssh_string v = NULL; @@ -850,7 +899,7 @@ static int b64decode_ecdsa_privatekey(const char *pkey, gcry_sexp_t *r, valid = 1; } - error: +error: SSH_BUFFER_FREE(buffer); SSH_STRING_FREE(v); ssh_string_burn(d); @@ -868,10 +917,10 @@ ssh_string pki_private_key_to_pem(const ssh_key key, ssh_auth_callback auth_fn, void *auth_data) { - (void) key; - (void) passphrase; - (void) auth_fn; - (void) auth_data; + (void)key; + (void)passphrase; + (void)auth_fn; + (void)auth_data; SSH_LOG(SSH_LOG_TRACE, "PEM export not supported by gcrypt backend!"); @@ -896,72 +945,79 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } switch (type) { - case SSH_KEYTYPE_RSA: - if (passphrase == NULL) { - if (auth_fn) { - valid = b64decode_rsa_privatekey(b64_key, &rsa, auth_fn, - auth_data, "Passphrase for private key:"); - } else { - valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, - NULL); - } + case SSH_KEYTYPE_RSA: + if (passphrase == NULL) { + if (auth_fn) { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + auth_fn, + auth_data, + "Passphrase for private key:"); } else { - valid = b64decode_rsa_privatekey(b64_key, &rsa, NULL, - (void *)passphrase, NULL); + valid = + b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, NULL); } + } else { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + NULL, + (void *)passphrase, + NULL); + } - if (!valid) { - SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); - goto fail; - } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #if HAVE_GCRYPT_ECC - if (passphrase == NULL) { - if (auth_fn != NULL) { - valid = b64decode_ecdsa_privatekey(b64_key, - &ecdsa, - auth_fn, - auth_data, - "Passphrase for private key:"); - } else { - valid = b64decode_ecdsa_privatekey(b64_key, - &ecdsa, - NULL, - NULL, - NULL); - } + if (passphrase == NULL) { + if (auth_fn != NULL) { + valid = + b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + auth_fn, + auth_data, + "Passphrase for private key:"); } else { valid = b64decode_ecdsa_privatekey(b64_key, &ecdsa, NULL, - (void *)passphrase, + NULL, NULL); } + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + (void *)passphrase, + NULL); + } - if (!valid) { - SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); - goto fail; - } + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } - /* pki_privatekey_type_from_string always returns P256 for ECDSA - * keys, so we need to figure out the correct type here */ - type = pki_key_ecdsa_to_key_type(ecdsa); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); - goto fail; - } - break; + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + break; #endif - case SSH_KEYTYPE_ED25519: - /* Cannot open ed25519 keys with libgcrypt */ - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); - return NULL; + case SSH_KEYTYPE_ED25519: + /* Cannot open ed25519 keys with libgcrypt */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); + return NULL; } key = ssh_key_new(); @@ -1000,14 +1056,21 @@ int pki_privkey_build_rsa(ssh_key key, /* in gcrypt, there is no iqmp (inverse of q mod p) argument, * but it is ipmq (inverse of p mod q) so we need to swap * the p and q arguments */ - gcry_sexp_build(&key->rsa, NULL, - "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", - ssh_string_len(n), ssh_string_data(n), - ssh_string_len(e), ssh_string_data(e), - ssh_string_len(d), ssh_string_data(d), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(iqmp), ssh_string_data(iqmp)); + gcry_sexp_build(&key->rsa, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(iqmp), + ssh_string_data(iqmp)); if (key->rsa == NULL) { return SSH_ERROR; } @@ -1015,13 +1078,15 @@ int pki_privkey_build_rsa(ssh_key key, return SSH_OK; } -int pki_pubkey_build_rsa(ssh_key key, - ssh_string e, - ssh_string n) { - gcry_sexp_build(&key->rsa, NULL, - "(public-key(rsa(n %b)(e %b)))", - ssh_string_len(n), ssh_string_data(n), - ssh_string_len(e),ssh_string_data(e)); +int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) +{ + gcry_sexp_build(&key->rsa, + NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e)); if (key->rsa == NULL) { return SSH_ERROR; } @@ -1035,13 +1100,15 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) gpg_error_t err; key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); - err = gcry_sexp_build(&key->ecdsa, NULL, + err = gcry_sexp_build(&key->ecdsa, + NULL, "(private-key(ecdsa(curve %s)(d %b)(q %b)))", pki_key_ecdsa_nid_to_gcrypt_name(nid), - ssh_string_len(exp), ssh_string_data(exp), - ssh_string_len(e), ssh_string_data(e)); + ssh_string_len(exp), + ssh_string_data(exp), + ssh_string_len(e), + ssh_string_data(e)); if (err) { return SSH_ERROR; } @@ -1054,12 +1121,13 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) gpg_error_t err; key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); - err = gcry_sexp_build(&key->ecdsa, NULL, + err = gcry_sexp_build(&key->ecdsa, + NULL, "(public-key(ecdsa(curve %s)(q %b)))", pki_key_ecdsa_nid_to_gcrypt_name(nid), - ssh_string_len(e), ssh_string_data(e)); + ssh_string_len(e), + ssh_string_data(e)); if (err) { return SSH_ERROR; } @@ -1070,7 +1138,7 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) ssh_key pki_key_dup(const ssh_key key, int demote) { - ssh_key new; + ssh_key new = NULL; gcry_error_t err = 0; int rc; @@ -1087,108 +1155,106 @@ ssh_key pki_key_dup(const ssh_key key, int demote) gcry_sexp_t curve = NULL; - new = ssh_key_new(); + new = pki_key_dup_common_init(key, demote); if (new == NULL) { return NULL; } - new->type = key->type; - new->type_c = key->type_c; - if (demote) { - new->flags = SSH_KEY_FLAG_PUBLIC; - } else { - new->flags = key->flags; - } - - switch(key->type) { - case SSH_KEYTYPE_RSA: - err = gcry_sexp_extract_param(key->rsa, - NULL, - "ned?p?q?u?", - &n, - &e, - &d, - &p, - &q, - &u, - NULL); - if (err != 0) { - break; - } - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - err = gcry_sexp_build(&new->rsa, - NULL, - "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", - n, e, d, p, q, u); - } else { - err = gcry_sexp_build(&new->rsa, + switch (key->type) { + case SSH_KEYTYPE_RSA: + err = gcry_sexp_extract_param(key->rsa, NULL, - "(public-key(rsa(n %m)(e %m)))", - n, e); - } + "ned?p?q?u?", + &n, + &e, + &d, + &p, + &q, + &u, + NULL); + if (err != 0) { break; - case SSH_KEYTYPE_ED25519: - rc = pki_ed25519_key_dup(new, key); - if (rc != SSH_OK) { - ssh_key_free(new); - return NULL; - } - break; - - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: -#ifdef HAVE_GCRYPT_ECC - new->ecdsa_nid = key->ecdsa_nid; - - err = gcry_sexp_extract_param(key->ecdsa, - NULL, - "qd?", - &q, - &d, - NULL); - if (err) { - break; - } - - curve = gcry_sexp_find_token(key->ecdsa, "curve", 0); - if (curve == NULL) { - break; - } + } - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - err = gcry_sexp_build(&new->ecdsa, - NULL, - "(private-key(ecdsa %S (d %m)(q %m)))", - curve, - d, - q); - } else { - err = gcry_sexp_build(&new->ecdsa, - NULL, - "(private-key(ecdsa %S (q %m)))", - curve, - q); - } - break; -#endif - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + err = gcry_sexp_build( + &new->rsa, + NULL, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, + e, + d, + p, + q, + u); + } else { + err = gcry_sexp_build(&new->rsa, + NULL, + "(public-key(rsa(n %m)(e %m)))", + n, + e); + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { ssh_key_free(new); return NULL; - } - - if (err) { - ssh_key_free(new); - new = NULL; - } + } + break; - gcry_mpi_release(p); - gcry_mpi_release(q); - gcry_mpi_release(g); - gcry_mpi_release(y); - gcry_mpi_release(x); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + new->ecdsa_nid = key->ecdsa_nid; + + err = gcry_sexp_extract_param(key->ecdsa, NULL, "qd?", &q, &d, NULL); + if (err) { + break; + } + + curve = gcry_sexp_find_token(key->ecdsa, "curve", 0); + if (curve == NULL) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + !is_sk_key_type(key->type)) { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (d %m)(q %m)))", + curve, + d, + q); + } else { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (q %m)))", + curve, + q); + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + if (err) { + ssh_key_free(new); + new = NULL; + } + + gcry_mpi_release(p); + gcry_mpi_release(q); + gcry_mpi_release(g); + gcry_mpi_release(y); + gcry_mpi_release(x); gcry_mpi_release(e); gcry_mpi_release(n); @@ -1200,16 +1266,20 @@ ssh_key pki_key_dup(const ssh_key key, int demote) return new; } -static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ - gcry_sexp_t params; +static int +pki_key_generate(ssh_key key, int parameter, const char *type_s, int type) +{ + gcry_sexp_t params = NULL; int rc; rc = gcry_sexp_build(¶ms, - NULL, - "(genkey(%s(nbits %d)(transient-key)))", - type_s, - parameter); - if (rc != 0) + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) { return SSH_ERROR; + } + switch (type) { case SSH_KEYTYPE_RSA: rc = gcry_pk_genkey(&key->rsa, params); @@ -1220,7 +1290,7 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int rc = gcry_pk_genkey(&key->ecdsa, params); break; default: - assert (! "reached"); + assert(!"reached"); } gcry_sexp_release(params); if (rc != 0) @@ -1228,40 +1298,51 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int return SSH_OK; } -int pki_key_generate_rsa(ssh_key key, int parameter){ +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); } #ifdef HAVE_GCRYPT_ECC -int pki_key_generate_ecdsa(ssh_key key, int parameter) { +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ switch (parameter) { - case 384: - key->ecdsa_nid = NID_gcrypt_nistp384; - key->type = SSH_KEYTYPE_ECDSA_P384; - return pki_key_generate(key, parameter, "ecdsa", - SSH_KEYTYPE_ECDSA_P384); - case 521: - key->ecdsa_nid = NID_gcrypt_nistp521; - key->type = SSH_KEYTYPE_ECDSA_P521; - return pki_key_generate(key, parameter, "ecdsa", - SSH_KEYTYPE_ECDSA_P521); - case 256: - default: - key->ecdsa_nid = NID_gcrypt_nistp256; - key->type = SSH_KEYTYPE_ECDSA_P256; - return pki_key_generate(key, parameter, "ecdsa", - SSH_KEYTYPE_ECDSA_P256); + case 384: + key->ecdsa_nid = NID_gcrypt_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P384); + case 521: + key->ecdsa_nid = NID_gcrypt_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P521); + case 256: + default: + key->ecdsa_nid = NID_gcrypt_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P256); } } #endif -static int _bignum_cmp(const gcry_sexp_t s1, - const gcry_sexp_t s2, - const char *what) +static int +_bignum_cmp(const gcry_sexp_t s1, const gcry_sexp_t s2, const char *what) { - gcry_sexp_t sexp; - bignum b1; - bignum b2; + gcry_sexp_t sexp = NULL; + bignum b1 = NULL; + bignum b2 = NULL; int result; sexp = gcry_sexp_find_token(s1, what, 0); @@ -1286,102 +1367,104 @@ static int _bignum_cmp(const gcry_sexp_t s1, return 1; } - result = !! bignum_cmp(b1, b2); + result = !!bignum_cmp(b1, b2); bignum_safe_free(b1); bignum_safe_free(b2); return result; } -int pki_key_compare(const ssh_key k1, - const ssh_key k2, - enum ssh_keycmp_e what) +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { switch (k1->type) { - case SSH_KEYTYPE_RSA: - if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { return 1; } - if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { return 1; } - if (what == SSH_KEY_CMP_PRIVATE) { - if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { - return 1; - } - - if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { - return 1; - } - - if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { - return 1; - } - - if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { - return 1; - } - } - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - /* ed25519 keys handled globally */ - return 0; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: -#ifdef HAVE_GCRYPT_ECC - if (k1->ecdsa_nid != k2->ecdsa_nid) { + if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { return 1; } - if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "q") != 0) { + if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { return 1; } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys handled globally */ + return 1; + 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; + } - if (what == SSH_KEY_CMP_PRIVATE) { - if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "d") != 0) { - return 1; - } + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "q") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "d") != 0) { + return 1; } - break; + } + break; #endif - case SSH_KEYTYPE_DSS: /* deprecated */ - case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ - 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_RSA1: - case SSH_KEYTYPE_UNKNOWN: - return 1; + 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; } 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_buffer buffer = NULL; + ssh_string type_s = NULL; 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(); if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); @@ -1406,95 +1489,211 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_RSA: - e = ssh_sexp_extract_mpi(key->rsa, - "e", + case SSH_KEYTYPE_RSA: + e = ssh_sexp_extract_mpi(key->rsa, + "e", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + n = ssh_sexp_extract_mpi(key->rsa, + "n", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (n == NULL) { + goto fail; + } + + 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 (e == NULL) { + if (d == NULL) { goto fail; } - n = ssh_sexp_extract_mpi(key->rsa, - "n", + p = ssh_sexp_extract_mpi(key->rsa, + "p", GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); - if (n == NULL) { + if (p == NULL) { goto fail; } - rc = ssh_buffer_add_ssh_string(buffer, e); + 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, n); + rc = ssh_buffer_add_ssh_string(buffer, u); if (rc < 0) { goto fail; } - - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(n); - SSH_STRING_FREE(n); - - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK){ + /* 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; } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { goto fail; } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: -#ifdef HAVE_GCRYPT_ECC - type_s = ssh_string_from_char( - pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); - if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + 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: + 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 { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + 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; + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } } + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + goto fail; + } + + e = ssh_sexp_extract_mpi(key->ecdsa, + "q", + GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } - e = ssh_sexp_extract_mpi(key->ecdsa, "q", GCRYMPI_FMT_STD, + 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 && !is_sk_key_type(key->type)) { + d = ssh_sexp_extract_mpi(key->ecdsa, + "d", + GCRYMPI_FMT_STD, GCRYMPI_FMT_STD); - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + if (d == NULL) { + goto fail; } - rc = ssh_buffer_add_ssh_string(buffer, e); + rc = ssh_buffer_add_ssh_string(buffer, d); if (rc < 0) { goto fail; } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; - - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (type == SSH_KEY_PRIVATE && is_sk_key_type(key->type)) { + /* Add security key private data for SK_ECDSA */ + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { goto fail; } + } else if (type == SSH_KEY_PUBLIC && + 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; + break; #endif - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - goto fail; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; } makestring: @@ -1503,7 +1702,9 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); if (rc < 0) { goto fail; } @@ -1530,103 +1731,118 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string pki_signature_to_blob(const ssh_signature sig) { - const char *s = NULL; /* used in RSA */ + const char *s = NULL; /* used in RSA */ - gcry_sexp_t sexp; + gcry_sexp_t sexp = NULL; size_t size = 0; ssh_string sig_blob = NULL; int rc; - switch(sig->type) { - case SSH_KEYTYPE_RSA: - sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); - if (sexp == NULL) { - return NULL; - } - s = gcry_sexp_nth_data(sexp, 1, &size); - if (*s == 0) { - size--; - s++; - } + switch (sig->type) { + case SSH_KEYTYPE_RSA: + sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); + if (sexp == NULL) { + return NULL; + } + s = gcry_sexp_nth_data(sexp, 1, &size); + + /* + * Remove leading zeroes, but only the ones that do not make the MPI + * representation look like a negative value (first bit is one), + * which might confuse some implementations. + */ + while (size > 1 && s[0] == 0 && (s[1] & 0x80) == 0) { + size--; + s++; + } - sig_blob = ssh_string_new(size); - if (sig_blob == NULL) { - return NULL; - } - rc = ssh_string_fill(sig_blob, discard_const_p(char, s), size); - gcry_sexp_release(sexp); - if (rc < 0) { - SSH_STRING_FREE(sig_blob); - return NULL; - } - break; - case SSH_KEYTYPE_ED25519: - sig_blob = pki_ed25519_signature_to_blob(sig); - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + sig_blob = ssh_string_new(size); + if (sig_blob == NULL) { + return NULL; + } + rc = ssh_string_fill(sig_blob, discard_const_p(char, s), size); + gcry_sexp_release(sexp); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_GCRYPT_ECC - { - ssh_string R; - ssh_string S; - ssh_buffer b; - - b = ssh_buffer_new(); - if (b == NULL) { - return NULL; - } + { + ssh_string R = NULL; + ssh_string S = NULL; + ssh_buffer b = NULL; - R = ssh_sexp_extract_mpi(sig->ecdsa_sig, "r", - GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); - if (R == NULL) { - SSH_BUFFER_FREE(b); - return NULL; - } + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } - rc = ssh_buffer_add_ssh_string(b, R); - SSH_STRING_FREE(R); - if (rc < 0) { - SSH_BUFFER_FREE(b); - return NULL; - } + R = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "r", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (R == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } - S = ssh_sexp_extract_mpi(sig->ecdsa_sig, "s", - GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); - if (S == NULL) { - SSH_BUFFER_FREE(b); - return NULL; - } + rc = ssh_buffer_add_ssh_string(b, R); + SSH_STRING_FREE(R); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } - rc = ssh_buffer_add_ssh_string(b, S); - SSH_STRING_FREE(S); - if (rc < 0) { - SSH_BUFFER_FREE(b); - return NULL; - } + S = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (S == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } - sig_blob = ssh_string_new(ssh_buffer_get_len(b)); - if (sig_blob == NULL) { - SSH_BUFFER_FREE(b); - return NULL; - } + rc = ssh_buffer_add_ssh_string(b, S); + SSH_STRING_FREE(S); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } - rc = ssh_string_fill(sig_blob, - ssh_buffer_get(b), ssh_buffer_get_len(b)); - SSH_BUFFER_FREE(b); - if (rc < 0) { - SSH_STRING_FREE(sig_blob); - return NULL; - } - break; - } -#endif - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); return NULL; - break; + } + + rc = + ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + } +#endif + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); + return NULL; + break; } return sig_blob; @@ -1637,7 +1853,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, enum ssh_keytypes_e type, enum ssh_digest_e hash_type) { - ssh_signature sig; + ssh_signature sig = NULL; gcry_error_t err; size_t len; size_t rsalen; @@ -1662,132 +1878,133 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, len = ssh_string_len(sig_blob); - switch(type) { - case SSH_KEYTYPE_RSA: - rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; + switch (type) { + case SSH_KEYTYPE_RSA: + rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; - if (len > rsalen) { - SSH_LOG(SSH_LOG_TRACE, - "Signature is too big: %lu > %lu", - (unsigned long)len, - (unsigned long)rsalen); - ssh_signature_free(sig); - return NULL; - } + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); + ssh_signature_free(sig); + return NULL; + } - if (len < rsalen) { - SSH_LOG(SSH_LOG_DEBUG, - "RSA signature len %lu < %lu", - (unsigned long)len, - (unsigned long)rsalen); - } + if (len < rsalen) { + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); - ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); #endif - err = gcry_sexp_build(&sig->rsa_sig, - NULL, - "(sig-val(rsa(s %b)))", - ssh_string_len(sig_blob), - ssh_string_data(sig_blob)); - if (err) { - ssh_signature_free(sig); - return NULL; - } - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - rc = pki_signature_from_ed25519_blob(sig, sig_blob); - if (rc != SSH_OK){ - ssh_signature_free(sig); - return NULL; - } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: + err = gcry_sexp_build(&sig->rsa_sig, + NULL, + "(sig-val(rsa(s %b)))", + ssh_string_len(sig_blob), + ssh_string_data(sig_blob)); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_GCRYPT_ECC - { /* build ecdsa siganature */ - ssh_buffer b; - ssh_string r, s; - uint32_t rlen; - - b = ssh_buffer_new(); - if (b == NULL) { - ssh_signature_free(sig); - return NULL; - } + { /* build ecdsa siganature */ + ssh_buffer b = NULL; + ssh_string r = NULL, s = NULL; + uint32_t rlen; - rc = ssh_buffer_add_data(b, - ssh_string_data(sig_blob), - ssh_string_len(sig_blob)); - if (rc < 0) { - SSH_BUFFER_FREE(b); - ssh_signature_free(sig); - return NULL; - } + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + /* The buffer will contain sensitive information. */ + ssh_buffer_set_secure(b); - r = ssh_buffer_get_ssh_string(b); - if (r == NULL) { - SSH_BUFFER_FREE(b); - ssh_signature_free(sig); - return NULL; - } + rc = ssh_buffer_add_data(b, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } - s = ssh_buffer_get_ssh_string(b); - rlen = ssh_buffer_get_len(b); - SSH_BUFFER_FREE(b); - if (s == NULL) { - ssh_string_burn(r); - SSH_STRING_FREE(r); - ssh_signature_free(sig); - return NULL; - } + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } - if (rlen != 0) { - SSH_LOG(SSH_LOG_TRACE, - "Signature has remaining bytes in inner " - "sigblob: %lu", - (unsigned long)rlen); - ssh_string_burn(r); - SSH_STRING_FREE(r); - ssh_string_burn(s); - SSH_STRING_FREE(s); - ssh_signature_free(sig); - return NULL; - } + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + ssh_signature_free(sig); + return NULL; + } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); - ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); #endif - err = gcry_sexp_build(&sig->ecdsa_sig, - NULL, - "(sig-val(ecdsa(r %b)(s %b)))", - ssh_string_len(r), - ssh_string_data(r), - ssh_string_len(s), - ssh_string_data(s)); - ssh_string_burn(r); - SSH_STRING_FREE(r); - ssh_string_burn(s); - SSH_STRING_FREE(s); - if (err) { - ssh_signature_free(sig); - return NULL; - } - } - break; -#endif - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + err = gcry_sexp_build(&sig->ecdsa_sig, + NULL, + "(sig-val(ecdsa(r %b)(s %b)))", + ssh_string_len(r), + ssh_string_data(r), + ssh_string_len(s), + ssh_string_data(s)); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + ssh_signature_free(sig); return NULL; + } + } break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + return NULL; } return sig; @@ -1799,8 +2016,8 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, enum ssh_digest_e hash_type) { const char *hash_c = NULL; - ssh_signature sig; - gcry_sexp_t sexp; + ssh_signature sig = NULL; + gcry_sexp_t sexp = NULL; gcry_error_t err; sig = ssh_signature_new(); @@ -1811,74 +2028,74 @@ 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_RSA: - switch (hash_type) { - case SSH_DIGEST_SHA1: - hash_c = "sha1"; - break; - case SSH_DIGEST_SHA256: - hash_c = "sha256"; - break; - case SSH_DIGEST_SHA512: - hash_c = "sha512"; - break; - case SSH_DIGEST_AUTO: - default: - SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); - return NULL; - } - err = gcry_sexp_build(&sexp, - NULL, - "(data(flags pkcs1)(hash %s %b))", - hash_c, - hlen, - hash); - if (err) { - ssh_signature_free(sig); - return NULL; - } - - err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); - gcry_sexp_release(sexp); - if (err) { - ssh_signature_free(sig); - return NULL; - } + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA1: + hash_c = "sha1"; break; - case SSH_KEYTYPE_ED25519: - err = pki_ed25519_sign(privkey, sig, hash, hlen); - if (err != SSH_OK){ - ssh_signature_free(sig); - return NULL; - } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: -#ifdef HAVE_GCRYPT_ECC - err = gcry_sexp_build(&sexp, - NULL, - "(data(flags raw)(value %b))", - hlen, - hash); - if (err) { - ssh_signature_free(sig); - return NULL; - } - - err = gcry_pk_sign(&sig->ecdsa_sig, sexp, privkey->ecdsa); - gcry_sexp_release(sexp); - if (err) { - ssh_signature_free(sig); - return NULL; - } + case SSH_DIGEST_SHA256: + hash_c = "sha256"; break; -#endif - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: + case SSH_DIGEST_SHA512: + hash_c = "sha512"; + break; + case SSH_DIGEST_AUTO: default: + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); + return NULL; + } + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_c, + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + err = pki_ed25519_sign(privkey, sig, hash, hlen); + if (err != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { ssh_signature_free(sig); return NULL; + } + + err = gcry_pk_sign(&sig->ecdsa_sig, sexp, privkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_signature_free(sig); + return NULL; } return sig; @@ -1908,8 +2125,9 @@ ssh_signature pki_sign_data(const ssh_key privkey, int rc; if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { - SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " - "pki_sign_data()"); + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_sign_data()"); return NULL; } @@ -1949,7 +2167,8 @@ ssh_signature pki_sign_data(const ssh_key privkey, } FALL_THROUGH; default: - SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + SSH_LOG(SSH_LOG_TRACE, + "Unknown hash algorithm for type: %d", hash_type); return NULL; } @@ -1976,7 +2195,7 @@ int pki_verify_data_signature(ssh_signature signature, size_t input_len) { const char *hash_type = NULL; - gcry_sexp_t sexp; + gcry_sexp_t sexp = NULL; gcry_error_t err; unsigned char ghash[SHA512_DIGEST_LEN + 1] = {0}; @@ -1988,10 +2207,10 @@ int pki_verify_data_signature(ssh_signature signature, int rc; if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || - signature == NULL) - { - SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " - "pki_verify_data_signature()"); + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_verify_data_signature()"); return SSH_ERROR; } @@ -2030,91 +2249,90 @@ int pki_verify_data_signature(ssh_signature signature, 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) - { + pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) { verify_input = input; hlen = input_len; break; } FALL_THROUGH; default: - SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d", signature->hash_type); + SSH_LOG(SSH_LOG_TRACE, + "Unknown sig->hash_type: %d", + signature->hash_type); return SSH_ERROR; } - switch(pubkey->type) { - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA_CERT01: - err = gcry_sexp_build(&sexp, - NULL, - "(data(flags pkcs1)(hash %s %b))", - hash_type, hlen, hash); - if (err) { + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_type, + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "RSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->rsa_sig, sexp, pubkey->rsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid RSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { SSH_LOG(SSH_LOG_TRACE, - "RSA hash error: %s", - gcry_strerror(err)); - return SSH_ERROR; - } - err = gcry_pk_verify(signature->rsa_sig, sexp, pubkey->rsa); - gcry_sexp_release(sexp); - if (err) { - SSH_LOG(SSH_LOG_TRACE, "Invalid RSA signature"); - if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { - SSH_LOG(SSH_LOG_TRACE, - "RSA verify error: %s", - gcry_strerror(err)); - } - return SSH_ERROR; + "RSA verify error: %s", + gcry_strerror(err)); } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_SK_ECDSA: - case SSH_KEYTYPE_SK_ECDSA_CERT01: + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: #ifdef HAVE_GCRYPT_ECC - err = gcry_sexp_build(&sexp, - NULL, - "(data(flags raw)(value %b))", - hlen, - hash); - if (err) { + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "ECDSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->ecdsa_sig, sexp, pubkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ECDSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { SSH_LOG(SSH_LOG_TRACE, - "ECDSA hash error: %s", + "ECDSA verify error: %s", gcry_strerror(err)); - return SSH_ERROR; } - err = gcry_pk_verify(signature->ecdsa_sig, sexp, pubkey->ecdsa); - gcry_sexp_release(sexp); - if (err) { - SSH_LOG(SSH_LOG_TRACE, "Invalid ECDSA signature"); - if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { - SSH_LOG(SSH_LOG_TRACE, - "ECDSA verify error: %s", - gcry_strerror(err)); - } - return SSH_ERROR; - } - break; + return SSH_ERROR; + } + break; #endif - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ED25519: - case SSH_KEYTYPE_SK_ED25519_CERT01: - rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); - return SSH_ERROR; - } - break; - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_UNKNOWN: - default: - SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; } return SSH_OK; @@ -2142,8 +2360,8 @@ int ssh_key_size(ssh_key key) 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_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ case SSH_KEYTYPE_UNKNOWN: default: return SSH_ERROR; @@ -2153,11 +2371,10 @@ int ssh_key_size(ssh_key key) #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_TRACE, - "gcrypt does not support PKCS #11"); + (void)uri_name; + (void)key; + (void)key_type; + SSH_LOG(SSH_LOG_TRACE, "gcrypt does not support PKCS #11"); return SSH_ERROR; } #endif /* WITH_PKCS11_URI */ diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index 86717ca7..01a9ca80 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -43,9 +43,9 @@ 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->pk != NULL) { + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); } if (key->ecdsa != NULL) { @@ -128,56 +128,43 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, if (valid < 0) { goto fail; } -#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), + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , 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)); + ctr_drbg #endif + ); } else { -#if MBEDTLS_VERSION_MAJOR > 2 valid = mbedtls_pk_parse_key(pk, (const unsigned char *)b64_key, b64len, NULL, - 0, + 0 +#if MBEDTLS_VERSION_MAJOR > 2 + , mbedtls_ctr_drbg_random, - ctr_drbg); -#else - valid = mbedtls_pk_parse_key(pk, - (const unsigned char *)b64_key, - b64len, - NULL, - 0); + ctr_drbg #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), + strnlen(passphrase, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , 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)); + ctr_drbg #endif + ); } if (valid != 0) { char error_buf[100]; @@ -196,7 +183,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, switch (mbed_type) { case MBEDTLS_PK_RSA: case MBEDTLS_PK_RSA_ALT: - key->rsa = pk; + key->pk = pk; pk = NULL; key->type = SSH_KEYTYPE_RSA; break; @@ -204,7 +191,6 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, 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) { @@ -213,8 +199,7 @@ 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(pk); - SAFE_FREE(pk); + key->pk = pk; key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); @@ -259,21 +244,21 @@ int pki_privkey_build_rsa(ssh_key key, const mbedtls_pk_info_t *pk_info = NULL; int rc; - key->rsa = malloc(sizeof(mbedtls_pk_context)); - if (key->rsa == NULL) { + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { return SSH_ERROR; } - mbedtls_pk_init(key->rsa); + mbedtls_pk_init(key->pk); pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); - mbedtls_pk_setup(key->rsa, pk_info); + mbedtls_pk_setup(key->pk, pk_info); - rc = mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA); + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); if (rc == 0) { goto fail; } - rsa = mbedtls_pk_rsa(*key->rsa); + rsa = mbedtls_pk_rsa(*key->pk); rc = mbedtls_rsa_import_raw(rsa, ssh_string_data(n), ssh_string_len(n), ssh_string_data(p), ssh_string_len(p), @@ -300,8 +285,8 @@ int pki_privkey_build_rsa(ssh_key key, return SSH_OK; fail: - mbedtls_pk_free(key->rsa); - SAFE_FREE(key->rsa); + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); return SSH_ERROR; } @@ -315,27 +300,25 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) #endif int rc; - key->rsa = malloc(sizeof(mbedtls_pk_context)); - if (key->rsa == NULL) { + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { return SSH_ERROR; } - mbedtls_pk_init(key->rsa); + mbedtls_pk_init(key->pk); pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); - mbedtls_pk_setup(key->rsa, pk_info); + mbedtls_pk_setup(key->pk, pk_info); - rc = mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA); + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); if (rc == 0) { goto fail; } + rsa = mbedtls_pk_rsa(*key->pk); #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 @@ -374,8 +357,8 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) goto exit; fail: rc = SSH_ERROR; - mbedtls_pk_free(key->rsa); - SAFE_FREE(key->rsa); + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); exit: #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_free(&N); @@ -397,19 +380,11 @@ ssh_key pki_key_dup(const ssh_key key, int demote) mbedtls_mpi Q; #endif - new = ssh_key_new(); + new = pki_key_dup_common_init(key, demote); if (new == NULL) { return NULL; } - new->type = key->type; - new->type_c = key->type_c; - if (demote) { - new->flags = SSH_KEY_FLAG_PUBLIC; - } else { - new->flags = key->flags; - } - #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_init(&N); mbedtls_mpi_init(&E); @@ -422,23 +397,22 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_RSA: { mbedtls_rsa_context *rsa, *new_rsa; - new->rsa = malloc(sizeof(mbedtls_pk_context)); - if (new->rsa == NULL) { + new->pk = malloc(sizeof(mbedtls_pk_context)); + if (new->pk == NULL) { goto fail; } - mbedtls_pk_init(new->rsa); + mbedtls_pk_init(new->pk); pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); - mbedtls_pk_setup(new->rsa, pk_info); + mbedtls_pk_setup(new->pk, pk_info); - if (!mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) || - !mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) - { + if (!mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->pk, MBEDTLS_PK_RSA)) { goto fail; } - rsa = mbedtls_pk_rsa(*key->rsa); - new_rsa = mbedtls_pk_rsa(*new->rsa); + rsa = mbedtls_pk_rsa(*key->pk); + new_rsa = mbedtls_pk_rsa(*new->pk); if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { #if MBEDTLS_VERSION_MAJOR > 2 @@ -530,6 +504,7 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: new->ecdsa_nid = key->ecdsa_nid; new->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); @@ -540,7 +515,8 @@ ssh_key pki_key_dup(const ssh_key key, int demote) mbedtls_ecdsa_init(new->ecdsa); - if (demote && ssh_key_is_private(key)) { + if ((demote && ssh_key_is_private(key)) || + is_sk_key_type(key->type)) { rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (rc != 0) { @@ -558,6 +534,7 @@ ssh_key pki_key_dup(const ssh_key key, int demote) break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: rc = pki_ed25519_key_dup(new, key); if (rc != SSH_OK) { goto fail; @@ -587,27 +564,31 @@ int pki_key_generate_rsa(ssh_key key, int parameter) int rc; const mbedtls_pk_info_t *info = NULL; - key->rsa = malloc(sizeof(mbedtls_pk_context)); - if (key->rsa == NULL) { + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { return SSH_ERROR; } - mbedtls_pk_init(key->rsa); + mbedtls_pk_init(key->pk); info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); - rc = mbedtls_pk_setup(key->rsa, info); + rc = mbedtls_pk_setup(key->pk, info); if (rc != 0) { return SSH_ERROR; } - if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA)) { - rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->rsa), + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA)) { + rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->pk), mbedtls_ctr_drbg_random, ssh_get_mbedtls_ctr_drbg_context(), parameter, 65537); if (rc != 0) { - mbedtls_pk_free(key->rsa); + mbedtls_pk_free(key->pk); return SSH_ERROR; } } @@ -638,33 +619,31 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) mbedtls_mpi_init(&E2); #endif - switch (k1->type) { + 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_can_do(k1->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->pk, 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)) - { + if (mbedtls_pk_get_type(k1->pk) != mbedtls_pk_get_type(k2->pk) || + mbedtls_pk_get_bitlen(k1->pk) != + mbedtls_pk_get_bitlen(k2->pk)) { rc = 1; goto cleanup; } if (what == SSH_KEY_CMP_PUBLIC) { #if MBEDTLS_VERSION_MAJOR > 2 - rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa1 = mbedtls_pk_rsa(*k1->pk); rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); if (rc != 0) { rc = 1; goto cleanup; } - rsa2 = mbedtls_pk_rsa(*k2->rsa); + rsa2 = mbedtls_pk_rsa(*k2->pk); rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); if (rc != 0) { rc = 1; @@ -681,8 +660,8 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) goto cleanup; } #else - rsa1 = mbedtls_pk_rsa(*k1->rsa); - rsa2 = mbedtls_pk_rsa(*k2->rsa); + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { rc = 1; goto cleanup; @@ -695,14 +674,14 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) #endif } else if (what == SSH_KEY_CMP_PRIVATE) { #if MBEDTLS_VERSION_MAJOR > 2 - rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa1 = mbedtls_pk_rsa(*k1->pk); rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); if (rc != 0) { rc = 1; goto cleanup; } - rsa2 = mbedtls_pk_rsa(*k2->rsa); + rsa2 = mbedtls_pk_rsa(*k2->pk); rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); if (rc != 0) { rc = 1; @@ -729,8 +708,8 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) goto cleanup; } #else - rsa1 = mbedtls_pk_rsa(*k1->rsa); - rsa2 = mbedtls_pk_rsa(*k2->rsa); + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { rc = 1; goto cleanup; @@ -788,7 +767,8 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) goto cleanup; } - if (what == SSH_KEY_CMP_PRIVATE) { + if (what == SSH_KEY_CMP_PRIVATE && + k1->type != SSH_KEYTYPE_SK_ECDSA) { if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), &ecdsa2->MBEDTLS_PRIVATE(d))) { @@ -802,7 +782,7 @@ 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 */ - rc = 0; + rc = 1; break; default: rc = 1; @@ -878,28 +858,40 @@ 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 p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; ssh_string str = NULL; -#if MBEDTLS_VERSION_MAJOR > 2 - mbedtls_mpi E; - mbedtls_mpi N; -#endif int rc; - #if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi E = {0}; + mbedtls_mpi N = {0}; + mbedtls_mpi D = {0}; + mbedtls_mpi IQMP = {0}; + mbedtls_mpi P = {0}; + mbedtls_mpi Q = {0}; + mbedtls_mpi_init(&E); mbedtls_mpi_init(&N); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&IQMP); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); #endif buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); @@ -925,146 +917,260 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } switch (key->type) { - case SSH_KEYTYPE_RSA: { - mbedtls_rsa_context *rsa; - if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) == 0) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa = NULL; + mbedtls_mpi *E_ptr = NULL, *N_ptr = NULL; - rsa = mbedtls_pk_rsa(*key->rsa); + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + rsa = mbedtls_pk_rsa(*key->pk); #if MBEDTLS_VERSION_MAJOR > 2 - rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); - if (rc != 0) { - goto fail; + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto out; + } + + E_ptr = &E; + N_ptr = &N; +#else + E_ptr = &rsa->E; + N_ptr = &rsa->N; +#endif + + e = ssh_make_bignum_string(E_ptr); + if (e == NULL) { + goto out; + } + + n = ssh_make_bignum_string(N_ptr); + if (n == NULL) { + goto out; + } + + 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 out; } - e = ssh_make_bignum_string(&E); - if (e == NULL) { - goto fail; + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; } + } else if (type == SSH_KEY_PRIVATE) { + mbedtls_mpi *P_ptr = NULL, *Q_ptr = NULL, *D_ptr = NULL; + mbedtls_mpi *IQMP_ptr = NULL; - n = ssh_make_bignum_string(&N); - if (n == NULL) { - goto fail; + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; } -#else - e = ssh_make_bignum_string(&rsa->E); - if (e == NULL) { - goto fail; + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; } - n = ssh_make_bignum_string(&rsa->N); - if (n == NULL) { - goto fail; +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto out; + } + + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); + if (rc != 0) { + goto out; } + + P_ptr = &P; + Q_ptr = &Q; + D_ptr = &D; + IQMP_ptr = &IQMP; +#else + P_ptr = &rsa->P; + Q_ptr = &rsa->Q; + D_ptr = &rsa->D; + IQMP_ptr = &rsa->QP; #endif - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { - goto fail; + p = ssh_make_bignum_string(P_ptr); + if (p == NULL) { + goto out; } - if (ssh_buffer_add_ssh_string(buffer, n) < 0) { - goto fail; + q = ssh_make_bignum_string(Q_ptr); + if (q == NULL) { + goto out; } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; - ssh_string_burn(n); - SSH_STRING_FREE(n); - n = NULL; + d = ssh_make_bignum_string(D_ptr); + if (d == NULL) { + goto out; + } - break; - } - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: - 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; + iqmp = ssh_make_bignum_string(IQMP_ptr); + if (iqmp == NULL) { + goto out; } - rc = ssh_buffer_add_ssh_string(buffer, type_s); - SSH_STRING_FREE(type_s); + rc = ssh_buffer_add_ssh_string(buffer, d); if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto out; } - e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), - &key->ecdsa->MBEDTLS_PRIVATE(Q)); + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto out; + } - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto out; } - rc = ssh_buffer_add_ssh_string(buffer, e); + rc = ssh_buffer_add_ssh_string(buffer, q); if (rc < 0) { - goto fail; + goto out; } + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + 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; + } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = 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 (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); + + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + goto out; } - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + rc = pki_buffer_pack_sk_priv_data(buffer, key); if (rc != SSH_OK) { - goto fail; + goto out; } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + } else if (type == SSH_KEY_PUBLIC && + 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 out; } - break; - default: - goto fail; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + /* 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 out; + } + } + } else { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } + } + break; + default: + goto out; } makestring: str = ssh_string_new(ssh_buffer_get_len(buffer)); if (str == NULL) { - goto fail; + goto out; } - rc = ssh_string_fill(str, ssh_buffer_get(buffer), - ssh_buffer_get_len(buffer)); + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); if (rc < 0) { - goto fail; + ssh_string_burn(str); + SSH_STRING_FREE(str); } +out: 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); - ssh_string_burn(str); - SSH_STRING_FREE(str); ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_free(&N); mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&IQMP); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); #endif - return NULL; + return str; } ssh_string pki_signature_to_blob(const ssh_signature sig) @@ -1078,9 +1184,9 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: { - ssh_string r; - ssh_string s; - ssh_buffer b; + ssh_string r = NULL; + ssh_string s = NULL; + ssh_buffer b = NULL; int rc; b = ssh_buffer_new(); @@ -1132,6 +1238,11 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) case SSH_KEYTYPE_ED25519: sig_blob = pki_ed25519_signature_to_blob(sig); break; + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; default: SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); @@ -1152,12 +1263,12 @@ static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const size_t rsalen = 0; size_t len = ssh_string_len(sig_blob); - if (pubkey->rsa == NULL) { + if (pubkey->pk == NULL) { SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL"); goto errout; } - rsalen = mbedtls_pk_get_bitlen(pubkey->rsa) / 8; + rsalen = mbedtls_pk_get_bitlen(pubkey->pk) / 8; if (len > rsalen) { SSH_LOG(SSH_LOG_TRACE, "Signature is too big: %lu > %lu", @@ -1186,7 +1297,7 @@ static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const blob_padded_data = (char *) ssh_string_data(sig_blob_padded); blob_orig = (char *) ssh_string_data(sig_blob); - explicit_bzero(blob_padded_data, pad_len); + ssh_burn(blob_padded_data, pad_len); memcpy(blob_padded_data + pad_len, blob_orig, len); sig->rsa_sig = sig_blob_padded; @@ -1234,9 +1345,9 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: case SSH_KEYTYPE_SK_ECDSA: { - ssh_buffer b; - ssh_string r; - ssh_string s; + ssh_buffer b = NULL; + ssh_string r = NULL; + ssh_string s = NULL; size_t rlen; b = ssh_buffer_new(); @@ -1375,7 +1486,7 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, } ok = ssh_string_fill(sig_blob, sig, slen); - explicit_bzero(sig, slen); + ssh_burn(sig, slen); SAFE_FREE(sig); if (ok < 0) { SSH_STRING_FREE(sig_blob); @@ -1405,7 +1516,7 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, switch(privkey->type) { case SSH_KEYTYPE_RSA: - sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->rsa, hash_type); + sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->pk, hash_type); if (sig->rsa_sig == NULL) { ssh_signature_free(sig); return NULL; @@ -1613,9 +1724,12 @@ int pki_verify_data_signature(ssh_signature signature, switch (pubkey->type) { case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA_CERT01: - rc = mbedtls_pk_verify(pubkey->rsa, md, hash, hlen, - ssh_string_data(signature->rsa_sig), - ssh_string_len(signature->rsa_sig)); + rc = mbedtls_pk_verify(pubkey->pk, + md, + hash, + hlen, + ssh_string_data(signature->rsa_sig), + ssh_string_len(signature->rsa_sig)); if (rc != 0) { char error_buf[100]; mbedtls_strerror(rc, error_buf, 100); @@ -1712,7 +1826,6 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) mbedtls_ecp_point Q; key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); if (key->ecdsa == NULL) { @@ -1780,7 +1893,6 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) mbedtls_ecp_point Q; key->ecdsa_nid = nid; - key->type_c = pki_key_ecdsa_nid_to_name(nid); key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); if (key->ecdsa == NULL) { @@ -1880,7 +1992,7 @@ int ssh_key_size(ssh_key key) case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_RSA1: - return mbedtls_pk_get_bitlen(key->rsa); + return mbedtls_pk_get_bitlen(key->pk); case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_SK_ECDSA: diff --git a/src/pki_sk.c b/src/pki_sk.c new file mode 100644 index 00000000..5a25251c --- /dev/null +++ b/src/pki_sk.c @@ -0,0 +1,971 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/pki_sk.h" +#include "libssh/sk_common.h" + +#include +#include + +#define DEFAULT_PIN_PROMPT "Enter SK PIN: " +#define PIN_BUF_SIZE 64 + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Serialize FIDO2 attestation data into an SSH buffer + * + * Serializes the attestation certificate, signature, and authenticator data + * from a FIDO2 enrollment response into an SSH buffer in the + * "ssh-sk-attest-v01" format. + * + * @param[in] enroll_response The sk_enroll_response struct containing + * attestation data from FIDO2 enrollment + * @param[in,out] attestation_buffer SSH buffer to store the serialized + * attestation data + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_serialise_attestation_cert( + const struct sk_enroll_response *enroll_response, + ssh_buffer attestation_buffer) +{ + int rc; + + if (attestation_buffer == NULL || enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Parameters cannot be NULL"); + return SSH_ERROR; + } + + /* Check if attestation data is available */ + if (enroll_response->attestation_cert == NULL || + enroll_response->attestation_cert_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation certificate available"); + return SSH_ERROR; + } + + if (enroll_response->signature == NULL || + enroll_response->signature_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation signature available"); + return SSH_ERROR; + } + + if (enroll_response->authdata == NULL || + enroll_response->authdata_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No authenticator data available"); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(attestation_buffer, + "sdPdPdPds", + "ssh-sk-attest-v01", + (uint32_t)enroll_response->attestation_cert_len, + enroll_response->attestation_cert_len, + enroll_response->attestation_cert, + (uint32_t)enroll_response->signature_len, + enroll_response->signature_len, + enroll_response->signature, + (uint32_t)enroll_response->authdata_len, + enroll_response->authdata_len, + enroll_response->authdata, + (uint32_t)0, /* reserved flags */ + ""); /* reserved */ + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack attestation data into buffer"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_key from an sk_enroll_response struct + * + * Constructs an ssh_key structure from an sk_enroll_response + * struct for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm type (SSH_SK_ECDSA or + * SSH_SK_ED25519) + * @param[in] application The application string (relying party ID) + * @param[in] enroll_response The sk_enroll_response struct containing key data + * @param[out] ssh_key_result Pointer to store the newly created ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_enroll_response_to_ssh_key( + int algorithm, + const char *application, + const struct sk_enroll_response *enroll_response, + ssh_key *ssh_key_result) +{ + ssh_key key_to_build = NULL; + ssh_string public_key_string = NULL; + int rc, ret = SSH_ERROR; + + /* Validate input parameters */ + if (ssh_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_key pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_key_result = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrollment response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data */ + if (enroll_response->public_key == NULL || + enroll_response->key_handle == NULL) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid enrollment response: missing public key or key handle"); + return SSH_ERROR; + } + + key_to_build = ssh_key_new(); + if (key_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_key"); + return SSH_ERROR; + } + + /* Set key type based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + key_to_build->type = SSH_KEYTYPE_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + key_to_build->type = SSH_KEYTYPE_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + goto out; + } + key_to_build->type_c = ssh_key_type_to_char(key_to_build->type); + + public_key_string = ssh_string_from_data(enroll_response->public_key, + enroll_response->public_key_len); + if (public_key_string == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create public key string"); + goto out; + } + + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + rc = pki_pubkey_build_ecdsa(key_to_build, + pki_key_ecdsa_nid_from_name("nistp256"), + public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + goto out; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + rc = pki_pubkey_build_ed25519(key_to_build, public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ED25519 public key"); + goto out; + } + break; + } + + /* Set security key specific fields */ + key_to_build->sk_application = ssh_string_from_char(application); + if (key_to_build->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_application string"); + goto out; + } + + /* Set key handle */ + key_to_build->sk_key_handle = + ssh_string_from_data(enroll_response->key_handle, + enroll_response->key_handle_len); + if (key_to_build->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_key_handle string"); + goto out; + } + + key_to_build->sk_reserved = ssh_string_from_data(NULL, 0); + if (key_to_build->sk_reserved == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_reserved string"); + goto out; + } + + key_to_build->sk_flags = enroll_response->flags; + key_to_build->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + *ssh_key_result = key_to_build; + key_to_build = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(public_key_string); + SSH_STRING_FREE(public_key_string); + SSH_KEY_FREE(key_to_build); + + return ret; +} + +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + + struct sk_enroll_response *enroll_response = NULL; + ssh_key enrolled_key = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + unsigned char random_challenge[32]; + const unsigned char *challenge = NULL; + size_t challenge_length = 0; + + ssh_buffer challenge_buffer = NULL; + ssh_buffer attestation = NULL; + + int rc, ret = SSH_ERROR; + int algorithm; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK context cannot be NULL"); + return SSH_ERROR; + } + + if (enrolled_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrolled key result pointer cannot be NULL"); + return SSH_ERROR; + } + + /* Initialize output parameter */ + *enrolled_key_result = NULL; + + /* Clear any existing attestation data */ + SSH_BUFFER_FREE(context->sk_attestation_buffer); + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return SSH_ERROR; + } + + if (!ssh_callbacks_exists(sk_callbacks, enroll)) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback is not implemented"); + return SSH_ERROR; + } + + /* Validate required fields */ + if (context->sk_application == NULL || *context->sk_application == '\0') { + SSH_LOG(SSH_LOG_WARN, "Application identifier cannot be NULL or empty"); + return SSH_ERROR; + } + + /* Extract parameters from context */ + challenge_buffer = context->sk_challenge_buffer; + + /* Determine algorithm based on key type */ + switch (key_type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported key type for security key enrollment"); + goto out; + } + + /* Determine challenge to use */ + if (challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Using randomly generated challenge"); + + rc = ssh_get_random(random_challenge, sizeof(random_challenge), 0); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARN, "Failed to generate random challenge"); + goto out; + } + + challenge = random_challenge; + challenge_length = sizeof(random_challenge); + + } else { + challenge_length = ssh_buffer_get_len(challenge_buffer); + if (challenge_length == 0) { + SSH_LOG(SSH_LOG_WARN, "Challenge buffer cannot be empty"); + goto out; + } + + challenge = ssh_buffer_get(challenge_buffer); + SSH_LOG(SSH_LOG_DEBUG, + "Using provided challenge of length %zu", + challenge_length); + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->enroll(algorithm, + challenge, + challenge_length, + context->sk_application, + context->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &enroll_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Convert SK enroll response to ssh_key */ + rc = pki_sk_enroll_response_to_ssh_key(algorithm, + context->sk_application, + enroll_response, + &enrolled_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert enroll response to ssh_key"); + goto out; + } + + /* Try to serialize attestation data and store in context */ + attestation = ssh_buffer_new(); + if (attestation == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate attestation buffer"); + goto out; + } else { + rc = pki_sk_serialise_attestation_cert(enroll_response, attestation); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to serialize attestation data, continuing without " + "attestation"); + } else { + context->sk_attestation_buffer = attestation; + attestation = NULL; + } + } + + *enrolled_key_result = enrolled_key; + enrolled_key = NULL; + ret = SSH_OK; + +out: + if (challenge == random_challenge) { + ssh_burn(random_challenge, sizeof(random_challenge)); + } + + SK_ENROLL_RESPONSE_FREE(enroll_response); + SSH_KEY_FREE(enrolled_key); + SSH_BUFFER_FREE(attestation); + + return ret; +} + +static int +pki_sk_pack_ecdsa_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + + bignum r_bn = NULL, s_bn = NULL; + ssh_buffer inner_buffer = NULL; + int rc = SSH_ERROR; + + /* Convert raw r and s bytes to bignums */ + bignum_bin2bn(sign_response->sig_r, (int)sign_response->sig_r_len, &r_bn); + if (r_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_r to bignum"); + goto out; + } + + bignum_bin2bn(sign_response->sig_s, (int)sign_response->sig_s_len, &s_bn); + if (s_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_s to bignum"); + goto out; + } + + /* Create inner buffer with r and s as SSH strings */ + inner_buffer = ssh_buffer_new(); + if (inner_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create inner buffer"); + goto out; + } + ssh_buffer_set_secure(inner_buffer); + + rc = ssh_buffer_pack(inner_buffer, "BB", r_bn, s_bn); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack r and s into inner buffer"); + goto out; + } + + rc = ssh_buffer_pack(sig_buffer, + "P", + (size_t)ssh_buffer_get_len(inner_buffer), + ssh_buffer_get(inner_buffer)); + if (rc != SSH_OK) { + goto out; + } + + rc = SSH_OK; + +out: + SSH_BUFFER_FREE(inner_buffer); + bignum_safe_free(s_bn); + bignum_safe_free(r_bn); + + return rc; +} + +static int +pki_sk_pack_ed25519_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + int rc = SSH_ERROR; + + rc = ssh_buffer_pack(sig_buffer, + "P", + sign_response->sig_r_len, + sign_response->sig_r); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_signature from a sk_sign_response structure + * + * Serializes a security key sign response into an ssh_signature structure + * for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param[in] key_type The SSH key type for setting signature type + * @param[in] sign_response The sk_sign_response containing signature data + * @param[out] ssh_signature_result Pointer to store the created ssh_signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_sign_response_to_ssh_signature( + int algorithm, + enum ssh_keytypes_e key_type, + const struct sk_sign_response *sign_response, + ssh_signature *ssh_signature_result) +{ + ssh_signature signature_to_build = NULL; + ssh_buffer sig_buffer = NULL; + int rc; + + /* Validate input parameters */ + if (ssh_signature_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_signature pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_signature_result = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Sign response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + if (sign_response->sig_r == NULL || sign_response->sig_s == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid ECDSA sign response: missing sig_r or sig_s"); + return SSH_ERROR; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + if (sign_response->sig_r == NULL || + sign_response->sig_r_len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Invalid sig_r in Ed25519 sign response"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + return SSH_ERROR; + } + + /* Create new ssh_signature */ + signature_to_build = ssh_signature_new(); + if (signature_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_signature"); + return SSH_ERROR; + } + + /* Set signature type and metadata */ + signature_to_build->type = key_type; + signature_to_build->type_c = ssh_key_type_to_char(key_type); + + /* Set security key specific fields */ + signature_to_build->sk_flags = sign_response->flags; + signature_to_build->sk_counter = sign_response->counter; + + /* Create a buffer to hold the signature data */ + sig_buffer = ssh_buffer_new(); + if (sig_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create signature buffer"); + goto error; + } + ssh_buffer_set_secure(sig_buffer); + + /* Build the signature based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + signature_to_build->hash_type = SSH_DIGEST_SHA256; + + rc = pki_sk_pack_ecdsa_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack ECDSA signature"); + goto error; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + signature_to_build->hash_type = SSH_DIGEST_AUTO; + + rc = pki_sk_pack_ed25519_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack Ed25519 signature"); + goto error; + } + break; + } + + /* Set the signature data */ + signature_to_build->raw_sig = + ssh_string_from_data(ssh_buffer_get(sig_buffer), + ssh_buffer_get_len(sig_buffer)); + if (signature_to_build->raw_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create raw signature string"); + goto error; + } + + *ssh_signature_result = signature_to_build; + SSH_BUFFER_FREE(sig_buffer); + + return SSH_OK; + +error: + SSH_SIGNATURE_FREE(signature_to_build); + SSH_BUFFER_FREE(sig_buffer); + + return SSH_ERROR; +} + +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const unsigned char *data, + size_t data_len) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_sign_response *sign_response = NULL; + ssh_signature signature = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int algorithm; + int rc = SSH_ERROR; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context cannot be NULL"); + return NULL; + } + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return NULL; + } + + if (!ssh_callbacks_exists(sk_callbacks, sign)) { + SSH_LOG(SSH_LOG_WARN, "Security key sign callback is not implemented"); + return NULL; + } + + if (key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Key cannot be NULL"); + return NULL; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "Data cannot be NULL or empty"); + return NULL; + } + + /* Validate key type and determine algorithm */ + switch (key->type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported key type for security key signing"); + return NULL; + } + + /* Validate security key specific fields */ + if (key->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key handle cannot be NULL"); + return NULL; + } + + if (key->sk_application == NULL || + ssh_string_len(key->sk_application) == 0) { + SSH_LOG(SSH_LOG_WARN, + "Security key application cannot be NULL or empty"); + return NULL; + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto error; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->sign(algorithm, + data, + data_len, + ssh_string_get_char(key->sk_application), + ssh_string_data(key->sk_key_handle), + ssh_string_len(key->sk_key_handle), + key->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &sign_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key sign callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto error; + } + + /* Convert SK sign response to ssh_signature */ + rc = pki_sk_sign_response_to_ssh_signature(algorithm, + key->type, + sign_response, + &signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sign response to signature"); + goto error; + } + + SK_SIGN_RESPONSE_FREE(sign_response); + return signature; + +error: + SK_SIGN_RESPONSE_FREE(sign_response); + SSH_SIGNATURE_FREE(signature); + return NULL; +} + +/** + * @brief Load resident keys from FIDO2 security keys + * + * This function loads all resident keys (discoverable credentials) stored + * on FIDO2 security keys using the context's security key callbacks. + * Resident keys are credentials stored directly on the security key device + * and can be discovered without prior knowledge of key handles. + * + * Only resident keys with SSH application identifiers (starting with + * "ssh:") are returned. + * + * @param[in] pki_context The PKI context containing security key callbacks. + * Can be NULL, in which case a default context with + * default callbacks will be used. If provided, the context + * must have valid sk_callbacks configured. + * @param[out] resident_keys_result Array of ssh_key structs representing the + * resident keys found and loaded + * @param[out] num_keys_found_result Number of resident keys found and loaded + * + * @return SSH_OK on success, SSH_ERROR on error + * + * @note The resident_keys_result array and its contents must be freed by + * the caller using ssh_sk_resident_key_free() for each key and then + * freeing the array itself when no longer needed. + */ +int ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_resident_key **raw_resident_keys = NULL; + + ssh_key cur_resident_key = NULL, *result_keys = NULL, *temp_keys = NULL; + ssh_pki_ctx temp_ctx = NULL; + const struct ssh_pki_ctx_struct *ctx_to_use = NULL; + + size_t raw_keys_count = 0, result_keys_count = 0, i; + uint8_t sk_flags; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int rc = SSH_ERROR; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Get security key callbacks from context */ + sk_callbacks = ctx_to_use->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + goto out; + } + + if (!ssh_callbacks_exists(sk_callbacks, load_resident_keys)) { + SSH_LOG(SSH_LOG_WARN, + "Security key load resident keys callback is not implemented"); + goto out; + } + + if (resident_keys_result == NULL || num_keys_found_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Result pointers cannot be NULL"); + goto out; + } + + /* Initialize output parameters */ + *resident_keys_result = NULL; + *num_keys_found_result = 0; + + if (ctx_to_use->sk_pin_callback != NULL) { + rc = ctx_to_use->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + ctx_to_use->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->load_resident_keys(pin_to_use, + ctx_to_use->sk_callbacks_options, + &raw_resident_keys, + &raw_keys_count); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key load_resident_keys callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Process each raw resident key */ + for (i = 0; i < raw_keys_count; i++) { + SSH_LOG( + SSH_LOG_DEBUG, + "Processing resident key %zu: alg %d, app \"%s\", user_id_len %zu", + i, + raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + raw_resident_keys[i]->user_id_len); + + /* Filter out non-SSH applications */ + if (strncmp(raw_resident_keys[i]->application, "ssh:", 4) != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH application: %s", + raw_resident_keys[i]->application); + continue; + } + + /* Check supported algorithms */ + switch (raw_resident_keys[i]->alg) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported algorithm %d, skipping", + raw_resident_keys[i]->alg); + continue; + } + + /* Set up security key flags */ + sk_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + if (raw_resident_keys[i]->flags & SSH_SK_USER_VERIFICATION_REQD) { + sk_flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + /* Convert raw resident key to libssh key structure */ + rc = + pki_sk_enroll_response_to_ssh_key(raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + &raw_resident_keys[i]->key, + &cur_resident_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to convert resident key %zu to ssh_key", + i); + continue; + } + + /* Set the security key flags on the converted key */ + cur_resident_key->sk_flags = sk_flags; + + /* Copy user ID if present */ + if (raw_resident_keys[i]->user_id != NULL && + raw_resident_keys[i]->user_id_len > 0) { + + cur_resident_key->sk_user_id = + ssh_string_from_data(raw_resident_keys[i]->user_id, + raw_resident_keys[i]->user_id_len); + if (cur_resident_key->sk_user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate user_id string for key %zu", + i); + goto out; + } + } + + /* Grow the result array */ + temp_keys = + realloc(result_keys, sizeof(ssh_key) * (result_keys_count + 1)); + if (temp_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate result keys array"); + goto out; + } + + /* Add the current resident key to the result array */ + result_keys = temp_keys; + result_keys[result_keys_count] = cur_resident_key; + result_keys_count++; + cur_resident_key = NULL; + } + + /* Set output parameters */ + *resident_keys_result = result_keys; + *num_keys_found_result = result_keys_count; + result_keys = NULL; + result_keys_count = 0; + rc = SSH_OK; + +out: + + if (raw_resident_keys != NULL) { + for (i = 0; i < raw_keys_count; i++) { + SK_RESIDENT_KEY_FREE(raw_resident_keys[i]); + } + SAFE_FREE(raw_resident_keys); + } + + SSH_KEY_FREE(cur_resident_key); + for (i = 0; i < result_keys_count; i++) { + SSH_KEY_FREE(result_keys[i]); + } + SAFE_FREE(result_keys); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** @} */ diff --git a/src/poll.c b/src/poll.c index 828f5e0a..23d18256 100644 --- a/src/poll.c +++ b/src/poll.c @@ -61,24 +61,24 @@ */ struct ssh_poll_handle_struct { - ssh_poll_ctx ctx; - ssh_session session; - union { - socket_t fd; - size_t idx; - } x; - short events; - uint32_t lock_cnt; - ssh_poll_callback cb; - void *cb_data; + ssh_poll_ctx ctx; + ssh_session session; + union { + socket_t fd; + size_t idx; + } x; + short events; + uint32_t lock_cnt; + ssh_poll_callback cb; + void *cb_data; }; struct ssh_poll_ctx_struct { - ssh_poll_handle *pollptrs; - ssh_pollfd_t *pollfds; - size_t polls_allocated; - size_t polls_used; - size_t chunk_size; + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + size_t polls_allocated; + size_t polls_used; + size_t chunk_size; }; #ifdef HAVE_POLL @@ -96,7 +96,7 @@ void ssh_poll_cleanup(void) int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { - return poll((struct pollfd *) fds, nfds, timeout); + return poll((struct pollfd *)fds, nfds, timeout); } #else /* HAVE_POLL */ @@ -249,14 +249,15 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) } #endif - // we use the readfds to get POLLHUP and POLLERR, which are provided even when not requested - 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); + FD_SET(fds[i].fd, &writefds); } if (fds[i].events & (POLLPRI | POLLRDBAND)) { - FD_SET (fds[i].fd, &exceptfds); + FD_SET(fds[i].fd, &exceptfds); } if (fds[i].fd > max_fd) { @@ -297,11 +298,12 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) fds[i].revents = 0; if (FD_ISSET(fds[i].fd, &readfds)) { - fds[i].revents = bsd_socket_compute_revents(fds[i].fd, - fds[i].events); + fds[i].revents = + bsd_socket_compute_revents(fds[i].fd, fds[i].events); } if (FD_ISSET(fds[i].fd, &writefds)) { - fds[i].revents |= fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); + fds[i].revents |= + fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); } if (FD_ISSET(fds[i].fd, &exceptfds)) { @@ -319,22 +321,26 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) return rc; } -void ssh_poll_init(void) { +void ssh_poll_init(void) +{ ssh_poll_emu = bsd_poll; } -void ssh_poll_cleanup(void) { +void ssh_poll_cleanup(void) +{ ssh_poll_emu = bsd_poll; } -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 (ssh_poll_emu)(fds, nfds, timeout); } #endif /* HAVE_POLL */ /** - * @brief Allocate a new poll object, which could be used within a poll context. + * @brief Allocate a new poll object, which could be used within a poll + * context. * * @param[in] fd Socket that will be polled. * @param[in] events Poll events that will be monitored for the socket. @@ -354,7 +360,7 @@ int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) { - ssh_poll_handle p; + ssh_poll_handle p = NULL; p = malloc(sizeof(struct ssh_poll_handle_struct)); if (p == NULL) { @@ -370,7 +376,6 @@ ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) return p; } - /** * @brief Free a poll object. * @@ -422,7 +427,7 @@ void ssh_poll_set_events(ssh_poll_handle p, short events) { p->events = events; if (p->ctx != NULL) { - if (p->lock_cnt == 0) { + if (!ssh_poll_is_locked(p)) { 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 @@ -433,8 +438,8 @@ void ssh_poll_set_events(ssh_poll_handle p, short events) } /** - * @brief Set the file descriptor of a poll object. The FD will also be propagated - * to an associated poll context. + * @brief Set the file descriptor of a poll object. The FD will also be + * propagated to an associated poll context. * * @param p Pointer to an already allocated poll object. * @param fd New file descriptor. @@ -496,7 +501,9 @@ 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) +void ssh_poll_set_callback(ssh_poll_handle p, + ssh_poll_callback cb, + void *userdata) { if (cb != NULL) { p->cb = cb; @@ -542,7 +549,7 @@ ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) void ssh_poll_ctx_free(ssh_poll_ctx ctx) { if (ctx->polls_allocated > 0) { - while (ctx->polls_used > 0){ + while (ctx->polls_used > 0) { ssh_poll_handle p = ctx->pollptrs[0]; /* * The free function calls ssh_poll_ctx_remove() and decrements @@ -560,29 +567,38 @@ void ssh_poll_ctx_free(ssh_poll_ctx ctx) static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { - ssh_poll_handle *pollptrs; - ssh_pollfd_t *pollfds; - - pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); - if (pollptrs == NULL) { - return -1; - } - ctx->pollptrs = pollptrs; - - pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); - if (pollfds == NULL) { - pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * ctx->polls_allocated); + ssh_poll_handle *pollptrs = NULL; + ssh_pollfd_t *pollfds = NULL; + + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); if (pollptrs == NULL) { - return -1; + /* Fail, but keep the old value to be freed later */ + return SSH_ERROR; } ctx->pollptrs = pollptrs; - return -1; - } - ctx->pollfds = pollfds; - ctx->polls_allocated = new_size; + pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); + if (pollfds == NULL) { + if (ctx->polls_allocated == 0) { + /* This was initial allocation -- just free what we allocated above + * and fail */ + SAFE_FREE(ctx->pollptrs); + return SSH_ERROR; + } + /* Try to realloc the pollptrs back to the original size */ + pollptrs = realloc(ctx->pollptrs, + sizeof(ssh_poll_handle) * ctx->polls_allocated); + if (pollptrs == NULL) { + return SSH_ERROR; + } + ctx->pollptrs = pollptrs; + return SSH_ERROR; + } + + ctx->pollfds = pollfds; + ctx->polls_allocated = new_size; - return 0; + return SSH_OK; } /** @@ -595,27 +611,27 @@ static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) */ int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { - socket_t fd; - - if (p->ctx != NULL) { - /* already attached to a context */ - return -1; - } - - if (ctx->polls_used == ctx->polls_allocated && - ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { - return -1; - } - - fd = p->x.fd; - p->x.idx = ctx->polls_used++; - ctx->pollptrs[p->x.idx] = p; - ctx->pollfds[p->x.idx].fd = fd; - ctx->pollfds[p->x.idx].events = p->events; - ctx->pollfds[p->x.idx].revents = 0; - p->ctx = ctx; - - return 0; + socket_t fd; + + if (p->ctx != NULL) { + /* already attached to a context */ + return -1; + } + + if (ctx->polls_used == ctx->polls_allocated && + ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { + return -1; + } + + fd = p->x.fd; + p->x.idx = ctx->polls_used++; + ctx->pollptrs[p->x.idx] = p; + ctx->pollfds[p->x.idx].fd = fd; + ctx->pollfds[p->x.idx].events = p->events; + ctx->pollfds[p->x.idx].revents = 0; + p->ctx = ctx; + + return 0; } /** @@ -626,20 +642,17 @@ int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) * * @return 0 on success, < 0 on error */ -int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) +int ssh_poll_ctx_add_socket(ssh_poll_ctx ctx, ssh_socket s) { ssh_poll_handle p = NULL; - int ret; p = ssh_socket_get_poll_handle(s); if (p == NULL) { return -1; } - ret = ssh_poll_ctx_add(ctx,p); - return ret; + return ssh_poll_ctx_add(ctx, p); } - /** * @brief Remove a poll object from a poll context. * @@ -648,25 +661,39 @@ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) */ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { - size_t i; + size_t i; - i = p->x.idx; - p->x.fd = ctx->pollfds[i].fd; - p->ctx = NULL; + i = p->x.idx; + p->x.fd = ctx->pollfds[i].fd; + p->ctx = NULL; - ctx->polls_used--; + ctx->polls_used--; - /* fill the empty poll slot with the last one */ - if (ctx->polls_used > 0 && ctx->polls_used != i) { - ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; - ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; - ctx->pollptrs[i]->x.idx = i; - } + /* fill the empty poll slot with the last one */ + if (ctx->polls_used > 0 && ctx->polls_used != i) { + ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; + ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; + ctx->pollptrs[i]->x.idx = i; + } + + /* this will always leave at least chunk_size polls allocated */ + if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { + ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); + } +} - /* this will always leave at least chunk_size polls allocated */ - if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { - ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); - } +/** + * @brief Returns if a poll object is locked. + * + * @param p Pointer to an already allocated poll object. + * @returns true if the poll object is locked; false otherwise. + */ +bool ssh_poll_is_locked(ssh_poll_handle p) +{ + if (p == NULL) { + return false; + } + return p->lock_cnt > 0; } /** @@ -689,7 +716,7 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) { int rc; size_t i, used; - ssh_poll_handle p; + ssh_poll_handle p = NULL; socket_t fd; int revents; struct ssh_timestamp ts; @@ -698,13 +725,13 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) return SSH_ERROR; } - /* Ignore any pollin events on locked sockets as that means we are called + /* 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 prevents invoking POLLIN events: drop them now */ - if (ctx->pollptrs[i]->lock_cnt > 0) { - ctx->pollfds[i].events &= ~POLLIN; + /* The lock allows only POLLOUT events: drop the rest */ + if (ssh_poll_is_locked(ctx->pollptrs[i])) { + ctx->pollfds[i].events &= POLLOUT; } } ssh_timestamp_init(&ts); @@ -722,14 +749,21 @@ 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 == 0) { + 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_cnt++; @@ -737,7 +771,8 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) if (ret == -2) { return -1; } - /* the poll was removed, reload the used counter and start again */ + /* the poll was removed, reload the used counter and start again + */ used = ctx->polls_used; i = 0; } else { @@ -763,8 +798,9 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) */ ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) { - if(session->default_poll_ctx != NULL) + 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; @@ -774,7 +810,7 @@ ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) struct ssh_event_fd_wrapper { ssh_event_callback cb; - void * userdata; + void *userdata; }; struct ssh_event_struct { @@ -803,14 +839,14 @@ ssh_event ssh_event_new(void) ZERO_STRUCTP(event); event->ctx = ssh_poll_ctx_new(2); - if(event->ctx == NULL) { + if (event->ctx == NULL) { free(event); return NULL; } #ifdef WITH_SERVER event->sessions = ssh_list_new(); - if(event->sessions == NULL) { + if (event->sessions == NULL) { ssh_poll_ctx_free(event->ctx); free(event); return NULL; @@ -820,9 +856,10 @@ 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; @@ -850,19 +887,22 @@ ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, * @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; + ssh_poll_handle p = NULL; + struct ssh_event_fd_wrapper *pw = NULL; + int rc; - if(event == NULL || event->ctx == NULL || cb == NULL - || fd == SSH_INVALID_SOCKET) { + if (event == NULL || event->ctx == NULL || cb == NULL || + fd == SSH_INVALID_SOCKET) { return SSH_ERROR; } pw = malloc(sizeof(struct ssh_event_fd_wrapper)); - if(pw == NULL) { + if (pw == NULL) { return SSH_ERROR; } @@ -871,12 +911,13 @@ ssh_event_add_fd(ssh_event event, socket_t fd, short events, /* pw is freed by ssh_event_remove_fd */ p = ssh_poll_new(fd, events, ssh_event_fd_wrapper_callback, pw); - if(p == NULL) { + if (p == NULL) { free(pw); return SSH_ERROR; } - if(ssh_poll_ctx_add(event->ctx, p) < 0) { + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc < 0) { free(pw); ssh_poll_free(p); return SSH_ERROR; @@ -908,7 +949,7 @@ int ssh_event_add_poll(ssh_event event, ssh_poll_handle p) */ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) { - ssh_poll_ctx_remove(event->ctx,p); + ssh_poll_ctx_remove(event->ctx, p); } /** @@ -923,15 +964,16 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) */ int ssh_event_add_session(ssh_event event, ssh_session session) { - ssh_poll_handle p; + ssh_poll_handle p = NULL; #ifdef WITH_SERVER - struct ssh_iterator *iterator; + struct ssh_iterator *iterator = NULL; #endif + int rc; - if(event == NULL || event->ctx == NULL || session == NULL) { + if (event == NULL || event->ctx == NULL || session == NULL) { return SSH_ERROR; } - if(session->default_poll_ctx == NULL) { + if (session->default_poll_ctx == NULL) { return SSH_ERROR; } while (session->default_poll_ctx->polls_used > 0) { @@ -941,7 +983,10 @@ int ssh_event_add_session(ssh_event event, ssh_session session) * session->default_poll_ctx->polls_used */ ssh_poll_ctx_remove(session->default_poll_ctx, p); - ssh_poll_ctx_add(event->ctx, p); + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc != SSH_OK) { + return rc; + } /* associate the pollhandler with a session so we can put it back * at ssh_event_free() */ @@ -949,14 +994,14 @@ int ssh_event_add_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) { /* allow only one instance of this session */ return SSH_OK; } iterator = iterator->next; } - if(ssh_list_append(event->sessions, session) == SSH_ERROR) { + if (ssh_list_append(event->sessions, session) == SSH_ERROR) { return SSH_ERROR; } #endif @@ -1024,17 +1069,17 @@ int ssh_event_remove_fd(ssh_event event, socket_t fd) register size_t i, used; int rc = SSH_ERROR; - if(event == NULL || event->ctx == NULL) { + if (event == NULL || event->ctx == NULL) { return SSH_ERROR; } used = event->ctx->polls_used; for (i = 0; i < used; i++) { - if(fd == event->ctx->pollfds[i].fd) { + if (fd == event->ctx->pollfds[i].fd) { ssh_poll_handle p = event->ctx->pollptrs[i]; - if (p->session != NULL){ - /* we cannot free that handle, it's owned by its session */ - continue; + if (p->session != NULL) { + /* we cannot free that handle, it's owned by its session */ + continue; } if (p->cb == ssh_event_fd_wrapper_callback) { struct ssh_event_fd_wrapper *pw = p->cb_data; @@ -1068,11 +1113,11 @@ int ssh_event_remove_fd(ssh_event event, socket_t fd) */ int ssh_event_remove_session(ssh_event event, ssh_session session) { - ssh_poll_handle p; + ssh_poll_handle p = NULL; register size_t i, used; int rc = SSH_ERROR; #ifdef WITH_SERVER - struct ssh_iterator *iterator; + struct ssh_iterator *iterator = NULL; #endif if (event == NULL || event->ctx == NULL || session == NULL) { @@ -1089,7 +1134,10 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) */ ssh_poll_ctx_remove(event->ctx, p); p->session = NULL; - ssh_poll_ctx_add(session->default_poll_ctx, p); + rc = ssh_poll_ctx_add(session->default_poll_ctx, p); + if (rc != SSH_OK) { + return rc; + } rc = SSH_OK; /* * Restart the loop! @@ -1097,7 +1145,6 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) */ used = event->ctx->polls_used; i = 0; - } } #ifdef WITH_SERVER @@ -1138,7 +1185,7 @@ int ssh_event_remove_connector(ssh_event event, ssh_connector connector) void ssh_event_free(ssh_event event) { size_t used, i; - ssh_poll_handle p; + ssh_poll_handle p = NULL; if (event == NULL) { return; diff --git a/src/scp.c b/src/scp.c index 40f45dbc..ec2d1a2e 100644 --- a/src/scp.c +++ b/src/scp.c @@ -30,6 +30,7 @@ #include "libssh/priv.h" #include "libssh/scp.h" #include "libssh/misc.h" +#include "libssh/session.h" /** * @defgroup libssh_scp The SSH scp functions @@ -197,6 +198,17 @@ int ssh_scp_init(ssh_scp scp) return SSH_ERROR; } + /* Some servers do not handle the quoting well. Pass in the raw file + * location */ + if (scp->session->flags & SSH_SESSION_FLAG_SCP_QUOTING_BROKEN) { + free(quoted_location); + quoted_location = strdup(scp->location); + if (quoted_location == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + } + if (scp->mode == SSH_SCP_WRITE) { snprintf(execbuffer, sizeof(execbuffer), "scp -t %s %s", scp->recursive ? "-r" : "", quoted_location); @@ -266,7 +278,7 @@ int ssh_scp_close(ssh_scp scp) */ while (!ssh_channel_is_eof(scp->channel)) { rc = ssh_channel_read(scp->channel, buffer, sizeof(buffer), 0); - if (rc == SSH_ERROR || rc == 0) { + if (rc == SSH_ERROR || rc == SSH_AGAIN || rc == 0) { break; } } @@ -603,6 +615,12 @@ int ssh_scp_response(ssh_scp scp, char **response) rc = ssh_channel_read(scp->channel, &code, 1, 0); if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); + scp->state = SSH_SCP_ERROR; return SSH_ERROR; } @@ -760,6 +778,14 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) break; } + if (err == SSH_AGAIN) { + ssh_set_error(scp->session, + SSH_FATAL, + "SCP: ssh_channel_read timeout"); + err = SSH_ERROR; + break; + } + read++; if (buffer[read - 1] == '\n') { break; @@ -848,6 +874,22 @@ int ssh_scp_pull_request(ssh_scp scp) size = strtoull(tmp, NULL, 10); p++; name = strdup(p); + /* Catch invalid name: + * - empty ones + * - containing any forward slash -- directory traversal handled + * differently + * - special names "." and ".." referring to the current and parent + * directories -- they are not expected either + */ + if (name == NULL || name[0] == '\0' || strchr(name, '/') || + strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + ssh_set_error(scp->session, + SSH_FATAL, + "Received invalid filename: %s", + name == NULL ? "" : name); + SAFE_FREE(name); + goto error; + } SAFE_FREE(scp->request_name); scp->request_name = name; if (buffer[0] == 'C') { @@ -1027,12 +1069,16 @@ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) } rc = ssh_channel_read(scp->channel, buffer, size, 0); - if (rc != SSH_ERROR) { - scp->processed += rc; - } else { + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); scp->state = SSH_SCP_ERROR; return SSH_ERROR; } + scp->processed += rc; /* Check if we arrived at end of file */ if (scp->processed == scp->filelen) { diff --git a/src/server.c b/src/server.c index 290b4250..32cee6ed 100644 --- a/src/server.c +++ b/src/server.c @@ -44,21 +44,22 @@ # include #endif -#include "libssh/priv.h" -#include "libssh/libssh.h" -#include "libssh/server.h" -#include "libssh/ssh2.h" #include "libssh/buffer.h" -#include "libssh/packet.h" -#include "libssh/socket.h" -#include "libssh/session.h" -#include "libssh/kex.h" -#include "libssh/misc.h" -#include "libssh/pki.h" +#include "libssh/curve25519.h" #include "libssh/dh.h" +#include "libssh/gssapi.h" +#include "libssh/kex.h" +#include "libssh/libssh.h" #include "libssh/messages.h" +#include "libssh/misc.h" #include "libssh/options.h" -#include "libssh/curve25519.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" #include "libssh/token.h" #define set_status(session, status) do {\ @@ -72,25 +73,35 @@ * @{ */ -/** @internal +/** + * @internal + * @brief Sets the server's key exchange, encryption, MAC, and compression + * algorithms. + * + * Prepares the server key exchange (KEX) proposals by prioritizing the + * available host keys (Ed25519, ECDSA, RSA) based on their strength and fills + * in the KEX method lists based on session options or defaults. This is + * essential for negotiating secure communication parameters in the SSH + * handshake. * - * @brief initialize the set of key exchange, hostkey, ciphers, MACs, and - * compression algorithms for the given ssh_session + * @param[in] session The SSH session to set up. * - * The selection of algorithms and keys used are determined by the - * options that are currently set in the given ssh_session structure. + * @return `SSH_OK` on success, `SSH_ERROR` on failure (e.g., no host keys + * available, random number generation error, or memory allocation failure). */ - int server_set_kex(ssh_session session) { struct ssh_kex_struct *server = &session->next_crypto->server_kex; int i, j, rc; - const char *wanted, *allowed; - char *kept; + const char *wanted = NULL, *allowed = NULL; + char *kept = NULL; char hostkeys[128] = {0}; enum ssh_keytypes_e keytype; size_t len; int ok; +#ifdef WITH_GSSAPI + char *gssapi_algs = NULL; +#endif /* WITH_GSSAPI */ /* 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. */ @@ -130,10 +141,6 @@ int server_set_kex(ssh_session session) ",%s", ssh_key_type_to_char(keytype)); } - if (strlen(hostkeys) == 0) { - return -1; - } - if (session->opts.wanted_methods[SSH_HOSTKEYS]) { allowed = session->opts.wanted_methods[SSH_HOSTKEYS]; } else { @@ -144,23 +151,52 @@ int server_set_kex(ssh_session session) } } - /* It is expected for the list of allowed hostkeys to be ordered by - * preference */ - kept = ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, - allowed); - if (kept == NULL) { - /* Nothing was allowed */ - return -1; + if (strlen(hostkeys) != 0) { + /* It is expected for the list of allowed hostkeys to be ordered by + * preference */ + kept = + ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, + allowed); + if (kept == NULL) { + /* Nothing was allowed */ + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); + SAFE_FREE(kept); + if (rc < 0) { + return -1; + } } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } - rc = ssh_options_set_algo(session, - SSH_HOSTKEYS, - kept, - &session->opts.wanted_methods[SSH_HOSTKEYS]); - SAFE_FREE(kept); - if (rc < 0) { - return -1; + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + ssh_gssapi_free(session); + + /* Prefix the default algorithms with gsskex algs */ + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(ssh_kex_get_default_methods(SSH_KEX), + gssapi_algs); + + if (strlen(hostkeys) == 0) { + session->opts.wanted_methods[SSH_HOSTKEYS] = strdup("null"); + } + + SAFE_FREE(gssapi_algs); } +#endif /* WITH_GSSAPI */ for (i = 0; i < SSH_KEX_METHODS; i++) { wanted = session->opts.wanted_methods[i]; @@ -187,7 +223,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) { @@ -205,9 +247,27 @@ int ssh_server_init_kex(ssh_session session) { return server_set_kex(session); } -static int ssh_server_send_extensions(ssh_session session) { +/** + * @internal + * + * @brief Sends SSH extension information from the server to client. + * + * A server may send this message (`SSH_MSG_EXT_INFO`) after its first + * `SSH_MSG_NEWKEYS` message or just before sending `SSH_MSG_USERAUTH_SUCCESS` + * to provide additional extensions support that are not meant for an + * unauthenticated client. + * + * If any error occurs during the packing or sending of the packet, the function + * aborts to avoid partial or corrupted sends. + * + * @param[in] session The SSH session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +static int ssh_server_send_extensions(ssh_session session) +{ int rc; - const char *hostkey_algorithms; + const char *hostkey_algorithms = NULL; SSH_LOG(SSH_LOG_PACKET, "Sending SSH_MSG_EXT_INFO"); @@ -223,11 +283,13 @@ static int ssh_server_send_extensions(ssh_session session) { } rc = ssh_buffer_pack(session->out_buffer, - "bdss", + "bdssss", SSH2_MSG_EXT_INFO, - 1, /* nr. of extensions */ + 2, /* nr. of extensions */ "server-sig-algs", - hostkey_algorithms); + hostkey_algorithms, + "publickey-hostbound@openssh.com", + "0"); if (rc != SSH_OK) { goto error; } @@ -267,13 +329,29 @@ SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ return SSH_PACKET_NOT_USED; } +/** + * @brief Prepares server host key parameters for the key exchange process. + * + * Selects the appropriate private host key (RSA, ECDSA, or Ed25519) based on + * the session's configured host key type, sets the corresponding @p digest + * algorithm, and imports the public key blob into the Diffie-Hellman key + * exchange state. + * + * @param[in] session The SSH session to which we are preparing host key + * parameters. + * @param[out] privkey Pointer to receive the selected private host key. + * @param[out] digest Pointer to receive the host key digest algorithm. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure (invalid key type, export + * failure, or DH import error). + */ int ssh_get_key_params(ssh_session session, ssh_key *privkey, enum ssh_digest_e *digest) { - ssh_key pubkey; - ssh_string pubkey_blob; + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; int rc; switch(session->srv.hostkey) { @@ -476,8 +554,8 @@ static size_t callback_receive_banner(const void *data, size_t len, void *user) ssh_pcap_context_write(session->pcap_ctx, SSH_PCAP_DIR_IN, buffer, - i + 1, - i + 1); + (uint32_t)(i + 1), + (uint32_t)(i + 1)); } #endif if (buffer[i] == '\r') { @@ -488,6 +566,11 @@ static size_t callback_receive_banner(const void *data, size_t len, void *user) buffer[i] = '\0'; str = strdup(buffer); + if (str == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error_oom(session); + return 0; + } /* number of bytes read */ processed = i + 1; session->clientbanner = str; @@ -516,6 +599,7 @@ static int ssh_server_kex_termination(void *s){ ssh_session session = s; if (session->session_state != SSH_SESSION_STATE_ERROR && session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED && session->session_state != SSH_SESSION_STATE_DISCONNECTED) return 0; else @@ -618,6 +702,14 @@ int ssh_auth_reply_default(ssh_session session,int partial) { strncat(methods_c,"gssapi-with-mic,", sizeof(methods_c) - strlen(methods_c) - 1); } + /* Check if GSSAPI Key exchange was performed */ + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + if (ssh_kex_is_gss(session->current_crypto)) { + strncat(methods_c, + "gssapi-keyex,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + } if (session->auth.supported_methods & SSH_AUTH_METHOD_INTERACTIVE) { strncat(methods_c, "keyboard-interactive,", sizeof(methods_c) - strlen(methods_c) - 1); @@ -654,6 +746,20 @@ int ssh_auth_reply_default(ssh_session session,int partial) { return rc; } +/** + * @internal + * + * @brief Sends default refusal for a channel open request. + * + * Default handler that rejects incoming SSH channel open requests by sending + * a `SSH2_MSG_CHANNEL_OPEN_FAILURE` packet with "administratively prohibited" + * reason code. Used when no custom channel open handler is registered. + * + * @param[in] msg The SSH message containing the channel open request details. + * + * @return `SSH_OK` on successful packet send; `SSH_ERROR` on buffer allocation + * or packet send failure. + */ static int ssh_message_channel_request_open_reply_default(ssh_message msg) { int rc; @@ -675,6 +781,21 @@ static int ssh_message_channel_request_open_reply_default(ssh_message msg) { return rc; } +/** + * @internal + * + * @brief Sends default refusal for a channel request. + * + * Default handler that rejects incoming SSH channel requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_CHANNEL_FAILURE` to the + * specific channel. If no reply requested, logs the refusal and returns + * success. + * + * @param[in] msg The SSH message containing the channel request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ static int ssh_message_channel_request_reply_default(ssh_message msg) { uint32_t channel; int rc; @@ -708,14 +829,15 @@ static int ssh_message_service_request_reply_default(ssh_message msg) { } /** - * @brief Sends SERVICE_ACCEPT to the client + * @brief Sends `SSH2_MSG_SERVICE_ACCEPT` to the client * * @param msg The message to reply to * - * @returns SSH_OK when success otherwise SSH_ERROR + * @returns `SSH_OK` when success otherwise `SSH_ERROR` */ -int ssh_message_service_reply_success(ssh_message msg) { - ssh_session session; +int ssh_message_service_reply_success(ssh_message msg) +{ + ssh_session session = NULL; int rc; if (msg == NULL) { @@ -745,7 +867,7 @@ int ssh_message_service_reply_success(ssh_message msg) { * * @param bound_port The remote bind port * - * @returns SSH_OK on success, otherwise SSH_ERROR + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` */ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { int rc; @@ -781,6 +903,20 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por return SSH_ERROR; } +/** + * @internal + * + * @brief Sends default refusal for a global request. + * + * Default handler that rejects incoming SSH global requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_REQUEST_FAILURE`. If no + * reply requested, logs the refusal and returns success immediately. + * + * @param[in] msg The SSH message containing the global request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ static int ssh_message_global_request_reply_default(ssh_message msg) { SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a global request"); @@ -919,6 +1055,28 @@ int ssh_message_auth_set_methods(ssh_message msg, int methods) { return 0; } +/** + * @brief Sends an interactive authentication request message. + * + * Builds and sends an `SSH2_MSG_USERAUTH_INFO_REQUEST` packet containing the + * given name and @p instruction, followed by a number of @p prompts with + * associated @p echo flags to control whether user input is echoed. + * It initializes the keyboard-interactive state in the session. + * + * @param[in] msg The SSH message representing the client + * authentication request. + * @param[in] name The name of the authentication request. + * @param[in] instruction Instruction string with information for the user. + * @param[in] num_prompts Number of prompts to send. The arrays prompts and + * echo must both have num_prompts elements. + * @param[in] prompts Array of @p num_prompts prompt strings to display. + * @param[in] echo Array of num_prompts boolean values (0 or 1). A + * non-zero value means the user input for that prompt + * is echoed (visible); 0 means the input is hidden + * (typically for passwords). + * + * @return `SSH_OK` on successful send; `SSH_ERROR` on failure. + */ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, const char *instruction, unsigned int num_prompts, const char **prompts, char *echo) { @@ -1025,15 +1183,15 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, } /** - * @brief Sends SSH2_MSG_USERAUTH_SUCCESS or SSH2_MSG_USERAUTH_FAILURE message - * depending on the success of the authentication method + * @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 + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` */ int ssh_auth_reply_success(ssh_session session, int partial) { @@ -1057,9 +1215,9 @@ int ssh_auth_reply_success(ssh_session session, int partial) /* * Consider the session as having been authenticated only after sending - * the USERAUTH_SUCCESS message. Setting these flags after ssh_packet_send - * ensures that a rekey is not triggered prematurely, causing the message - * to be queued. + * the `USERAUTH_SUCCESS` message. Setting these flags after + * ssh_packet_send ensures that a rekey is not triggered prematurely, + * causing the message to be queued. */ session->session_state = SSH_SESSION_STATE_AUTHENTICATED; session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; @@ -1078,6 +1236,18 @@ int ssh_auth_reply_success(ssh_session session, int partial) return r; } +/** + * @brief Replies to an authentication request with success. + * + * Sends an authentication success message (`SSH2_MSG_USERAUTH_SUCCESS`) to the + * client, or a partial success if further authentication steps are required. + * + * @param[in] msg The SSH authentication message being handled. + * @param[in] partial Set to nonzero if partial success (more auth needed), zero + * for full success. + * + * @return `SSH_OK` on success, `SSH_ERROR` if msg is NULL or on send failure. + */ int ssh_message_auth_reply_success(ssh_message msg, int partial) { if(msg == NULL) return SSH_ERROR; @@ -1085,7 +1255,7 @@ int ssh_message_auth_reply_success(ssh_message msg, int partial) { } /** - * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request * * @param msg The message * @@ -1093,7 +1263,7 @@ int ssh_message_auth_reply_success(ssh_message msg, int partial) { * * @param pubkey The accepted public key * - * @returns SSH_OK on success, otherwise SSH_ERROR + * @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; @@ -1116,14 +1286,15 @@ int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pu } /** - * @brief Answer SSH2_MSG_USERAUTH_PK_OK to a pubkey authentication request + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request * * @param msg The message * - * @returns SSH_OK on success, otherwise SSH_ERROR + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` */ -int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { - ssh_string algo; +int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) +{ + ssh_string algo = NULL; ssh_string pubkey_blob = NULL; int ret; @@ -1146,83 +1317,271 @@ int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { return ret; } - +/** + * @brief Get the originator address from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator address, or NULL. + */ const char *ssh_message_channel_request_open_originator(ssh_message msg){ return msg->channel_request_open.originator; } +/** + * @brief Get the originator port from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator port. + */ int ssh_message_channel_request_open_originator_port(ssh_message msg){ return msg->channel_request_open.originator_port; } +/** + * @brief Get the destination address from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination address, or NULL. + */ const char *ssh_message_channel_request_open_destination(ssh_message msg){ return msg->channel_request_open.destination; } +/** + * @brief Get the destination port from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination port. + */ int ssh_message_channel_request_open_destination_port(ssh_message msg){ return msg->channel_request_open.destination_port; } +/** + * @brief Get the channel associated with the message. + * + * @param[in] msg The message. + * + * @return The channel associated with the message. + */ ssh_channel ssh_message_channel_request_channel(ssh_message msg){ return msg->channel_request.channel; } +/** + * @brief Get the terminal type from the message. + * + * @param[in] msg The message. + * + * @return The terminal type (e.g. "xterm"), or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ const char *ssh_message_channel_request_pty_term(ssh_message msg){ return msg->channel_request.TERM; } +/** + * @brief Get the terminal width from the message. + * + * @param[in] msg The message. + * + * @return The terminal width in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ int ssh_message_channel_request_pty_width(ssh_message msg){ return msg->channel_request.width; } +/** + * @brief Get the terminal height from the message. + * + * @param[in] msg The message. + * + * @return The terminal height in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ int ssh_message_channel_request_pty_height(ssh_message msg){ return msg->channel_request.height; } +/** + * @brief Get the terminal pixel width from the message. + * + * @param[in] msg The message. + * + * @return The pixel width. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ int ssh_message_channel_request_pty_pxwidth(ssh_message msg){ return msg->channel_request.pxwidth; } +/** + * @brief Get the terminal pixel height from the message. + * + * @param[in] msg The message. + * + * @return The pixel height. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ int ssh_message_channel_request_pty_pxheight(ssh_message msg){ return msg->channel_request.pxheight; } +/** + * @brief Get the name of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable name, or NULL. + */ const char *ssh_message_channel_request_env_name(ssh_message msg){ return msg->channel_request.var_name; } +/** + * @brief Get the value of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable value, or NULL. + */ const char *ssh_message_channel_request_env_value(ssh_message msg){ return msg->channel_request.var_value; } +/** + * @brief Get the command from a channel request message. + * + * @param[in] msg The message. + * + * @return The command, or NULL. + */ const char *ssh_message_channel_request_command(ssh_message msg){ return msg->channel_request.command; } +/** + * @brief Get the subsystem from a channel request message. + * + * @param[in] msg The message. + * + * @return The subsystem, or NULL. + */ const char *ssh_message_channel_request_subsystem(ssh_message msg){ return msg->channel_request.subsystem; } +/** + * @brief Check if the X11 request is for a single connection. + * + * @param[in] msg The message. + * + * @return 1 if single connection, 0 otherwise. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ int ssh_message_channel_request_x11_single_connection(ssh_message msg){ return msg->channel_request.x11_single_connection ? 1 : 0; } +/** + * @brief Get the X11 authentication protocol from the message. + * + * @param[in] msg The message. + * + * @return The authentication protocol, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg){ return msg->channel_request.x11_auth_protocol; } +/** + * @brief Get the X11 authentication cookie from the message. + * + * @param[in] msg The message. + * + * @return The authentication cookie, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg){ return msg->channel_request.x11_auth_cookie; } +/** + * @brief Get the X11 screen number from the message. + * + * @param[in] msg The message. + * + * @return The screen number. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ int ssh_message_channel_request_x11_screen_number(ssh_message msg){ return msg->channel_request.x11_screen_number; } +/** + * @brief Get the bind address from the global request message. + * + * @param[in] msg The message. + * + * @return The bind address, or NULL. + */ const char *ssh_message_global_request_address(ssh_message msg){ return msg->global_request.bind_address; } +/** + * @brief Get the bind port from the global request message. + * + * @param[in] msg The message. + * + * @return The bind port. + */ int ssh_message_global_request_port(ssh_message msg){ return msg->global_request.bind_port; } @@ -1242,6 +1601,13 @@ void ssh_set_message_callback(ssh_session session, session->ssh_message_callback_data = data; } +/** + * @brief Execute callbacks for the messages in the queue. + * + * @param[in] session The session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ int ssh_execute_message_callbacks(ssh_session session){ ssh_message msg=NULL; int ret; @@ -1277,7 +1643,7 @@ int ssh_execute_message_callbacks(ssh_session session){ * * @param session The session to send the message to * - * @returns SSH_OK + * @returns `SSH_OK` */ int ssh_send_keepalive(ssh_session session) { diff --git a/src/session.c b/src/session.c index 742ac8bb..8e4ef0d4 100644 --- a/src/session.c +++ b/src/session.c @@ -26,6 +26,10 @@ #include #include +#ifdef _WIN32 +#include +#endif + #include "libssh/priv.h" #include "libssh/libssh.h" #include "libssh/crypto.h" @@ -39,6 +43,7 @@ #include "libssh/buffer.h" #include "libssh/poll.h" #include "libssh/pki.h" +#include "libssh/gssapi.h" #define FIRST_CHANNEL 42 // why not ? it helps to find bugs. @@ -58,7 +63,7 @@ */ ssh_session ssh_new(void) { - ssh_session session; + ssh_session session = NULL; char *id = NULL; int rc; @@ -96,12 +101,19 @@ ssh_session ssh_new(void) session->auth.supported_methods = 0; ssh_set_blocking(session, 1); session->maxchannel = FIRST_CHANNEL; + session->proxy_root = true; session->agent = ssh_agent_new(session); if (session->agent == NULL) { goto err; } + /* Initialise a default PKI context */ + session->pki_context = ssh_pki_ctx_new(); + if (session->pki_context == NULL) { + goto err; + } + /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; session->opts.port = 22; @@ -127,7 +139,36 @@ ssh_session ssh_new(void) goto err; } - id = strdup("%d/id_ed25519"); + 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 */ + + session->opts.proxy_jumps = ssh_list_new(); + if (session->opts.proxy_jumps == NULL) { + goto err; + } + + session->opts.proxy_jumps_user_cb = ssh_list_new(); + if (session->opts.proxy_jumps_user_cb == NULL) { + goto err; + } + +#ifdef WITH_GSSAPI + session->opts.gssapi_key_exchange_algs = + strdup(GSSAPI_KEY_EXCHANGE_SUPPORTED); + if (session->opts.gssapi_key_exchange_algs == NULL) { + goto err; + } +#endif /* WITH_GSSAPI */ + + id = strdup("%d/.ssh/id_ed25519"); if (id == NULL) { goto err; } @@ -138,7 +179,7 @@ ssh_session ssh_new(void) } #ifdef HAVE_ECC - id = strdup("%d/id_ecdsa"); + id = strdup("%d/.ssh/id_ecdsa"); if (id == NULL) { goto err; } @@ -148,7 +189,28 @@ ssh_session ssh_new(void) } #endif - id = strdup("%d/id_rsa"); + id = strdup("%d/.ssh/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = strdup("%d/.ssh/id_ed25519_sk"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/.ssh/id_ecdsa_sk"); if (id == NULL) { goto err; } @@ -156,6 +218,8 @@ ssh_session ssh_new(void) if (rc == SSH_ERROR) { goto err; } +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ /* Explicitly initialize states */ session->session_state = SSH_SESSION_STATE_NONE; @@ -237,9 +301,9 @@ 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_PKI_CTX_FREE(session->pki_context); ssh_key_free(session->srv.rsa_key); session->srv.rsa_key = NULL; @@ -267,9 +331,14 @@ void ssh_free(ssh_session session) ssh_list_free(session->packet_callbacks); } +#ifdef WITH_GSSAPI + ssh_gssapi_free(session); + SAFE_FREE(session->opts.gssapi_key_exchange_algs); +#endif + /* options */ if (session->opts.identity) { - char *id; + char *id = NULL; for (id = ssh_list_pop_head(char *, session->opts.identity); id != NULL; @@ -280,7 +349,7 @@ void ssh_free(ssh_session session) } if (session->opts.identity_non_exp) { - char *id; + char *id = NULL; for (id = ssh_list_pop_head(char *, session->opts.identity_non_exp); id != NULL; @@ -290,15 +359,40 @@ void ssh_free(ssh_session session) 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); + } + + ssh_proxyjumps_free(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps_user_cb); + SAFE_FREE(session->opts.proxy_jumps_str); + 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); @@ -310,10 +404,9 @@ void ssh_free(ssh_session session) SAFE_FREE(session->opts.agent_socket); SAFE_FREE(session->opts.bindaddr); - SAFE_FREE(session->opts.custombanner); - SAFE_FREE(session->opts.moduli_file); SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.homedir); SAFE_FREE(session->opts.sshdir); SAFE_FREE(session->opts.knownhosts); SAFE_FREE(session->opts.global_knownhosts); @@ -329,8 +422,13 @@ void ssh_free(ssh_session session) } } + SAFE_FREE(session->server_opts.custombanner); + SAFE_FREE(session->server_opts.moduli_file); + + _ssh_remove_legacy_log_cb(); + /* burn connection, it could contain sensitive data */ - explicit_bzero(session, sizeof(struct ssh_session_struct)); + ssh_burn(session, sizeof(struct ssh_session_struct)); SAFE_FREE(session); } @@ -357,10 +455,10 @@ const char* ssh_get_clientbanner(ssh_session session) { * @return Returns the server banner string or NULL. */ const char* ssh_get_serverbanner(ssh_session session) { - if(!session) { - return NULL; - } - return session->serverbanner; + if (!session) { + return NULL; + } + return session->serverbanner; } /** @@ -377,28 +475,52 @@ const char* ssh_get_kex_algo(ssh_session session) { } switch (session->current_crypto->kex_type) { - case SSH_KEX_DH_GROUP1_SHA1: - return "diffie-hellman-group1-sha1"; - case SSH_KEX_DH_GROUP14_SHA1: - return "diffie-hellman-group14-sha1"; - case SSH_KEX_DH_GROUP14_SHA256: - return "diffie-hellman-group14-sha256"; - case SSH_KEX_DH_GROUP16_SHA512: - return "diffie-hellman-group16-sha512"; - case SSH_KEX_DH_GROUP18_SHA512: - return "diffie-hellman-group18-sha512"; - case SSH_KEX_ECDH_SHA2_NISTP256: - return "ecdh-sha2-nistp256"; - case SSH_KEX_ECDH_SHA2_NISTP384: - return "ecdh-sha2-nistp384"; - case SSH_KEX_ECDH_SHA2_NISTP521: - return "ecdh-sha2-nistp521"; - case SSH_KEX_CURVE25519_SHA256: - return "curve25519-sha256"; - case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: - return "curve25519-sha256@libssh.org"; - default: - break; + case SSH_KEX_DH_GROUP1_SHA1: + return "diffie-hellman-group1-sha1"; + case SSH_KEX_DH_GROUP14_SHA1: + return "diffie-hellman-group14-sha1"; + case SSH_KEX_DH_GROUP14_SHA256: + return "diffie-hellman-group14-sha256"; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + return "gss-group14-sha256-"; + case SSH_KEX_DH_GROUP16_SHA512: + return "diffie-hellman-group16-sha512"; + case SSH_GSS_KEX_DH_GROUP16_SHA512: + return "gss-group16-sha512-"; + case SSH_KEX_DH_GROUP18_SHA512: + return "diffie-hellman-group18-sha512"; + case SSH_KEX_ECDH_SHA2_NISTP256: + return "ecdh-sha2-nistp256"; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return "gss-nistp256-sha256-"; + case SSH_KEX_ECDH_SHA2_NISTP384: + return "ecdh-sha2-nistp384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "ecdh-sha2-nistp521"; + case SSH_KEX_CURVE25519_SHA256: + return "curve25519-sha256"; + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + return "curve25519-sha256@libssh.org"; + case SSH_GSS_KEX_CURVE25519_SHA256: + return "gss-curve25519-sha256-"; + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + return "sntrup761x25519-sha512@openssh.com"; + case SSH_KEX_SNTRUP761X25519_SHA512: + return "sntrup761x25519-sha512"; + case SSH_KEX_MLKEM768X25519_SHA256: + return "mlkem768x25519-sha256"; + case SSH_KEX_MLKEM768NISTP256_SHA256: + return "mlkem768nistp256-sha256"; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return "mlkem1024nistp384-sha384"; +#endif /* HAVE_MLKEM1024 */ +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + return "diffie-hellman-group-exchange-sha1"; + case SSH_KEX_DH_GEX_SHA256: + return "diffie-hellman-group-exchange-sha256"; +#endif /* WITH_GEX */ } return NULL; @@ -466,6 +588,21 @@ const char* ssh_get_hmac_out(ssh_session session) { return NULL; } +/** + * @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. * @@ -473,14 +610,15 @@ const char* ssh_get_hmac_out(ssh_session 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); } /** @@ -527,10 +665,9 @@ static int ssh_flush_termination(void *c){ * will block, in milliseconds. Specifying -1 * means an infinite timeout. This parameter is passed to * the poll() function. - * @returns SSH_OK on success, SSH_AGAIN if timeout occurred, - * SSH_ERROR otherwise. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. */ - int ssh_blocking_flush(ssh_session session, int timeout){ int rc; if (session == NULL) { @@ -612,7 +749,7 @@ void ssh_set_fd_towrite(ssh_session session) { /** * @brief Tell the session it has an exception to catch on the file descriptor. * - * \param[in] session The ssh session to use. + * @param[in] session The ssh session to use. */ void ssh_set_fd_except(ssh_session session) { if (session == NULL) { @@ -626,20 +763,22 @@ void ssh_set_fd_except(ssh_session session) { * @internal * * @brief Poll the current session for an event and call the appropriate - * callbacks. This function will not loop until the timeout is expired. + * callbacks. This function will not loop until the @p timeout is expired. * * This will block until one event happens. * * @param[in] session The session handle to use. * * @param[in] timeout Set an upper limit on the time for which this function - * will block, in milliseconds. Specifying SSH_TIMEOUT_INFINITE + * will block, in milliseconds. Specifying + * `SSH_TIMEOUT_INFINITE` * (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout - * specified in options. 0 means poll will return immediately. + * Specifying `SSH_TIMEOUT_USER` means to use the timeout + * specified in options. 0 means poll will return + * immediately. * This parameter is passed to the poll() function. * - * @return SSH_OK on success, SSH_ERROR otherwise. + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. */ int ssh_handle_packets(ssh_session session, int timeout) { @@ -666,7 +805,10 @@ int ssh_handle_packets(ssh_session session, int timeout) ssh_set_error_oom(session); return SSH_ERROR; } - ssh_poll_ctx_add(ctx, spoll); + rc = ssh_poll_ctx_add(ctx, spoll); + if (rc != SSH_OK) { + return SSH_ERROR; + } } if (timeout == SSH_TIMEOUT_USER) { @@ -691,13 +833,14 @@ int ssh_handle_packets(ssh_session session, int timeout) * @brief Poll the current session for an event and call the appropriate * callbacks. * - * This will block until termination function returns true, or timeout expired. + * This will block until termination function returns true, or @p timeout + * expired. * * @param[in] session The session handle to use. * * @param[in] timeout Set an upper limit on the time for which this function * will block, in milliseconds. Specifying - * SSH_TIMEOUT_INFINITE (-1) means an infinite timeout. + * `SSH_TIMEOUT_INFINITE` (-1) means an infinite timeout. * Specifying SSH_TIMEOUT_USER means using the timeout * specified in options. 0 means poll will return * immediately. @@ -708,8 +851,8 @@ int ssh_handle_packets(ssh_session session, int timeout) * @param[in] fct Termination function to be used to determine if it is * possible to stop polling. * @param[in] user User parameter to be passed to fct termination function. - * @returns SSH_OK on success, SSH_AGAIN if timeout occurred, - * SSH_ERROR otherwise. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. */ int ssh_handle_packets_termination(ssh_session session, int timeout, @@ -766,9 +909,10 @@ int ssh_handle_packets_termination(ssh_session session, * * @param session The ssh session to use. * - * @returns A bitmask including SSH_CLOSED, SSH_READ_PENDING, SSH_WRITE_PENDING - * or SSH_CLOSED_ERROR which respectively means the session is closed, - * has data to read on the connection socket and session was closed + * @returns A bitmask including `SSH_CLOSED`, `SSH_READ_PENDING`, + * `SSH_WRITE_PENDING` or `SSH_CLOSED_ERROR` which + * respectively means the session is closed, has data to + * read on the connection socket and session was closed * due to an error. */ int ssh_get_status(ssh_session session) { @@ -804,9 +948,9 @@ int ssh_get_status(ssh_session session) { * * @param session The ssh session to use. * - * @returns A bitmask including SSH_READ_PENDING or SSH_WRITE_PENDING. - * For SSH_READ_PENDING, your invocation of poll() should include - * POLLIN. For SSH_WRITE_PENDING, your invocation of poll() should + * @returns A bitmask including `SSH_READ_PENDING` or `SSH_WRITE_PENDING`. + * For `SSH_READ_PENDING`, your invocation of poll() should include + * POLLIN. For `SSH_WRITE_PENDING`, your invocation of poll() should * include POLLOUT. */ int ssh_get_poll_flags(ssh_session session) @@ -868,16 +1012,41 @@ int ssh_get_version(ssh_session session) { * @param user is a pointer to session */ void ssh_socket_exception_callback(int code, int errno_code, void *user){ - ssh_session session=(ssh_session)user; + ssh_session session = (ssh_session)user; - SSH_LOG(SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code); + SSH_LOG(SSH_LOG_RARE, + "Socket exception callback: %d (%d)", + code, + errno_code); session->session_state = SSH_SESSION_STATE_ERROR; if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); +#ifdef _WIN32 + } else if (errno_code == WSAENETDOWN) { + ssh_set_error(session, SSH_FATAL, "Socket error: network down"); + } else if (errno_code == WSAENETUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: network unreachable"); + } else if (errno_code == WSAENETRESET) { + ssh_set_error(session, SSH_FATAL, "Socket error: network reset"); + } else if (errno_code == WSAECONNABORTED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection aborted"); + } else if (errno_code == WSAECONNRESET) { + ssh_set_error(session, + SSH_FATAL, + "Socket error: connection reset by peer"); + } else if (errno_code == WSAETIMEDOUT) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection timed out"); + } else if (errno_code == WSAECONNREFUSED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection refused"); + } else if (errno_code == WSAEHOSTUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: host unreachable"); +#endif } else { 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)); + ssh_set_error(session, + SSH_FATAL, + "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); @@ -889,7 +1058,7 @@ void ssh_socket_exception_callback(int code, int errno_code, void *user){ * @param[in] session The SSH session * @param[in] data Data to be sent * - * @return SSH_OK on success, SSH_ERROR otherwise. + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. */ int ssh_send_ignore (ssh_session session, const char *data) { const int type = SSH2_MSG_IGNORE; @@ -924,7 +1093,7 @@ int ssh_send_ignore (ssh_session session, const char *data) { * SHOULD NOT be displayed unless debugging * information has been explicitly requested. * - * @return SSH_OK on success, SSH_ERROR otherwise. + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. */ int ssh_send_debug (ssh_session session, const char *message, int always_display) { int rc; @@ -997,8 +1166,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) { @@ -1018,40 +1187,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; @@ -1080,7 +1259,7 @@ void ssh_clean_pubkey_hash(unsigned char **hash) * @param[out] key A pointer to store the allocated key. You need to free * the key using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_key_free() */ @@ -1114,7 +1293,7 @@ int ssh_get_publickey(ssh_session session, ssh_key *key) /** * @brief Allocates a buffer with the hash of the public key. * - * This function allows you to get a hash of the public key. You can then + * This function allows you to get a @p hash of the public @p key. You can then * print this hash in a human-readable form to the user so that he is able to * verify it. Use ssh_get_hexa() or ssh_print_hash() to display it. * @@ -1127,7 +1306,7 @@ int ssh_get_publickey(ssh_session session, ssh_key *key) * * @param[in] hlen The length of the hash. * - * @return 0 on success, -1 if an error occurred. + * @return SSH_OK on success, SSH_ERROR 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 won't help @@ -1144,103 +1323,59 @@ int ssh_get_publickey_hash(const ssh_key key, unsigned char **hash, size_t *hlen) { - ssh_string blob; + ssh_string blob = NULL; unsigned char *h = NULL; - int rc; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + int rc, ret = SSH_ERROR; rc = ssh_pki_export_pubkey_blob(key, &blob); if (rc < 0) { - return rc; + goto out; } switch (type) { case SSH_PUBLICKEY_HASH_SHA1: - { - SHACTX ctx; - - h = calloc(1, SHA_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = sha1_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha1_final(h, ctx); - - *hlen = SHA_DIGEST_LEN; - } + digest = sha1; + *hlen = SHA_DIGEST_LEN; break; case SSH_PUBLICKEY_HASH_SHA256: - { - SHA256CTX ctx; - - h = calloc(1, SHA256_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = sha256_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha256_final(h, ctx); - - *hlen = SHA256_DIGEST_LEN; - } + digest = sha256; + *hlen = SHA256_DIGEST_LEN; break; case SSH_PUBLICKEY_HASH_MD5: - { - MD5CTX ctx; - - /* In FIPS mode, we cannot use MD5 */ - if (ssh_fips_mode()) { - SSH_LOG(SSH_LOG_TRACE, "In FIPS mode MD5 is not allowed." - "Try using SSH_PUBLICKEY_HASH_SHA256"); - rc = SSH_ERROR; - goto out; - } - - h = calloc(1, MD5_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = md5_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - md5_final(h, ctx); - - *hlen = MD5_DIGEST_LEN; +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, + "In FIPS mode MD5 is not allowed." + "Try using SSH_PUBLICKEY_HASH_SHA256"); + goto out; } +#endif + digest = md5; + *hlen = MD5_DIGEST_LEN; break; default: - rc = -1; + goto out; + } + + h = calloc(1, *hlen); + if (h == NULL) { + goto out; + } + + rc = digest(ssh_string_data(blob), ssh_string_len(blob), h); + if (rc != SSH_OK) { + free(h); goto out; } *hash = h; - rc = 0; + ret = SSH_OK; + out: SSH_STRING_FREE(blob); - return rc; + return ret; } /** @} */ diff --git a/src/sftp.c b/src/sftp.c index d95af819..eb4d6614 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -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; @@ -111,12 +102,21 @@ static void sftp_ext_free(sftp_ext ext) sftp_session sftp_new(ssh_session session) { - sftp_session sftp; + sftp_session sftp = NULL; + int rc; if (session == NULL) { return NULL; } + if (!ssh_is_blocking(session)) { + ssh_set_error(session, + SSH_FATAL, + "The SSH session needs to be set to blocking mode for " + "SFTP to work correctly."); + return NULL; + } + sftp = calloc(1, sizeof(struct sftp_session_struct)); if (sftp == NULL) { ssh_set_error_oom(session); @@ -149,11 +149,18 @@ sftp_session sftp_new(ssh_session session) goto error; } - if (ssh_channel_open_session(sftp->channel)) { + /* + * The following two calls shouldn't return SSH_AGAIN + * as the code has validated above that the SSH session + * is in blocking mode. + */ + rc = ssh_channel_open_session(sftp->channel); + if (rc != SSH_OK) { goto error; } - if (ssh_channel_request_sftp(sftp->channel)) { + rc = ssh_channel_request_sftp(sftp->channel); + if (rc != SSH_OK) { goto error; } @@ -356,6 +363,7 @@ void sftp_free(sftp_session sftp) SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); SAFE_FREE(sftp); } @@ -369,10 +377,10 @@ int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) { sftp_packet packet = sftp->read_packet; - int nread; - int payload_len; - unsigned int data_offset; - int to_read, rc; + size_t nread; + size_t payload_len; + size_t data_offset; + size_t to_read, rc; if (packet->sftp == NULL) { packet->sftp = sftp; @@ -388,7 +396,7 @@ sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) packet->type = PULL_BE_U8(data, 4); /* We should check the legality of payload length */ - if (payload_len + sizeof(uint32_t) > len || payload_len < 0) { + if (payload_len > len - sizeof(uint32_t) || payload_len < sizeof(uint8_t)) { return SSH_ERROR; } @@ -407,631 +415,278 @@ sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) } /* - * We should return how many bytes we decoded, including packet length header - * and the payload length. + * We should return how many bytes we decoded, including packet length + * header and the payload length. + * This can't overflow as we pulled this from unit32_t and checked this fits + * into the buffer's max size of 0x10000000 (256MB). */ - return payload_len + sizeof(uint32_t); + return (int)(payload_len + sizeof(uint32_t)); +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + return sftp->errnum; } -int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); + +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) { - uint8_t header[5] = {0}; - uint32_t payload_size; - int size; + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; 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) { + buffer = ssh_buffer_new(); + if (buffer == NULL) { 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) { + 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 ((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); + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + if (rc == SSH_ERROR) { + SSH_BUFFER_FREE(buffer); + return -1; } - return size; -} + SSH_BUFFER_FREE(buffer); -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_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); + return -1; + } + + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Unable to unpack SSH_FXP_VERSION packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - packet->sftp = sftp; + 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; - /* - * 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) { + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; + return -1; } - } else { - packet->payload = ssh_buffer_new(); - if (packet->payload == NULL) { + + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; + return -1; } - } - nread = 0; - do { - int s; + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; - // 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); + sftp->ext->count = count; - 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; + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); } - 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); + sftp->version = sftp->server_version = (int)version; - packet->type = tmpbuf[0]; + /* 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; + } - /* Remove the packet type size */ - size -= sizeof(uint8_t); + 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"); - /* 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; - } + /* 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); - 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; + 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; } } - return packet; -error: - ssh_buffer_reinit(packet->payload); - return NULL; -} - -static void sftp_set_error(sftp_session sftp, int errnum) { - if (sftp != NULL) { - sftp->errnum = errnum; - } -} - -/* Get the last sftp error */ -int sftp_get_error(sftp_session sftp) { - if (sftp == NULL) { - return -1; - } - - return sftp->errnum; + return 0; } -static void sftp_message_free(sftp_message msg) +unsigned int sftp_extensions_get_count(sftp_session sftp) { - if (msg == NULL) { - return; + if (sftp == NULL || sftp->ext == NULL) { + return 0; } - SSH_BUFFER_FREE(msg->payload); - SAFE_FREE(msg); + return sftp->ext->count; } -static sftp_message sftp_get_message(sftp_packet packet) +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) { - 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); + if (sftp == NULL) { 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); + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); 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); + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); return NULL; } - SSH_LOG(SSH_LOG_PACKET, - "Packet with id %" PRIu32 " type %d", - msg->id, - msg->packet_type); - - return msg; + return sftp->ext->name[idx]; } -static int sftp_read_and_dispatch(sftp_session sftp) +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) { - sftp_packet packet = NULL; - sftp_message msg = NULL; - - packet = sftp_packet_read(sftp); - if (packet == NULL) { - /* something nasty happened reading the packet */ - return -1; + if (sftp == NULL) { + return NULL; } - msg = sftp_get_message(packet); - if (msg == NULL) { - return -1; + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; } - if (sftp_enqueue(sftp, msg) < 0) { - sftp_message_free(msg); - return -1; + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; } - return 0; + return sftp->ext->data[idx]; } -void sftp_packet_free(sftp_packet packet) +int +sftp_extension_supported(sftp_session sftp, + const char *name, + const char *data) { - if (packet == NULL) { - return; - } - - SSH_BUFFER_FREE(packet->payload); - free(packet); -} - -/* Initialize the sftp session with the server. */ -int sftp_init(sftp_session sftp) { - sftp_packet packet = NULL; - ssh_buffer buffer = NULL; - char *ext_name = NULL; - char *ext_data = NULL; - uint32_t version; - int rc; - - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); - - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } - - if (packet->type != SSH_FXP_VERSION) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a %d messages instead of SSH_FXP_VERSION", packet->type); - return -1; - } + unsigned int i, n; - /* 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_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; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); - if (rc == SSH_ERROR) { - break; + if (sftp == NULL || name == NULL || data == NULL) { + return 0; } - SSH_LOG(SSH_LOG_DEBUG, - "SFTP server extension: %s, version: %s", - ext_name, ext_data); - - count++; - tmp = realloc(sftp->ext->name, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - tmp[count - 1] = ext_name; - sftp->ext->name = tmp; + n = sftp_extensions_get_count(sftp); + for (i = 0; i < n; i++) { + const char *ext_name = sftp_extensions_get_name(sftp, i); + const char *ext_data = sftp_extensions_get_data(sftp, i); - 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; + if (ext_name != NULL && ext_data != NULL && + strcmp(ext_name, name) == 0 && + strcmp(ext_data, data) == 0) { + return 1; + } } - tmp[count - 1] = ext_data; - sftp->ext->data = tmp; - - sftp->ext->count = count; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - } - - sftp->version = sftp->server_version = (int)version; - - - return 0; -} -unsigned int sftp_extensions_get_count(sftp_session sftp) { - if (sftp == NULL || sftp->ext == NULL) { return 0; - } - - return sftp->ext->count; } -const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) { - if (sftp == NULL) - return NULL; - if (sftp->ext == NULL || sftp->ext->name == NULL) { - ssh_set_error_invalid(sftp->session); - return NULL; - } +static sftp_file parse_handle_msg(sftp_message msg){ + sftp_file file; - if (idx > sftp->ext->count) { - ssh_set_error_invalid(sftp->session); + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); return NULL; } - return sftp->ext->name[idx]; -} - -const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) { - if (sftp == NULL) - return NULL; - if (sftp->ext == NULL || sftp->ext->name == NULL) { - ssh_set_error_invalid(sftp->session); + file = calloc(1, sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); return NULL; } - if (idx > sftp->ext->count) { - ssh_set_error_invalid(sftp->session); + file->handle = ssh_buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); return NULL; } - return sftp->ext->data[idx]; -} - -int sftp_extension_supported(sftp_session sftp, const char *name, - const char *data) { - size_t i, n; - - if (sftp == NULL || name == NULL || data == NULL) { - return 0; - } + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; - n = sftp_extensions_get_count(sftp); - for (i = 0; i < n; i++) { - const char *ext_name = sftp_extensions_get_name(sftp, i); - const char *ext_data = sftp_extensions_get_data(sftp, i); - - if (ext_name != NULL && ext_data != NULL && - strcmp(ext_name, name) == 0 && - strcmp(ext_data, data) == 0) { - return 1; - } - } - - return 0; -} - -static sftp_request_queue request_queue_new(sftp_message msg) { - sftp_request_queue queue = NULL; - - queue = calloc(1, sizeof(struct sftp_request_queue_struct)); - if (queue == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - queue->message = msg; - - return queue; -} - -static void request_queue_free(sftp_request_queue queue) { - if (queue == NULL) { - return; - } - - ZERO_STRUCTP(queue); - SAFE_FREE(queue); -} - -static int sftp_enqueue(sftp_session sftp, sftp_message msg) { - sftp_request_queue queue = NULL; - sftp_request_queue ptr; - - queue = request_queue_new(msg); - if (queue == NULL) { - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, - "Queued msg id %" 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. - */ -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 %" PRIu32 " type %d", - msg->id, - msg->packet_type); - return msg; - } - prev = queue; - queue = queue->next; - } - - return NULL; -} - -/* - * Assigns a new SFTP ID for new requests and assures there is no collision - * between them. - * Returns a new ID ready to use in a request - */ -static inline uint32_t sftp_get_new_id(sftp_session session) { - return ++session->id_counter; -} - -static sftp_status_message parse_status_msg(sftp_message msg){ - sftp_status_message status; - int rc; - - if (msg->packet_type != SSH_FXP_STATUS) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_status message passed in!"); - sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); - return NULL; - } - - status = calloc(1, sizeof(struct sftp_status_message_struct)); - if (status == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - status->id = msg->id; - rc = ssh_buffer_unpack(msg->payload, "d", - &status->status); - if (rc != SSH_OK){ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - rc = ssh_buffer_unpack(msg->payload, "ss", - &status->errormsg, - &status->langmsg); - - if(rc != SSH_OK && msg->sftp->version >=3){ - /* These are mandatory from version 3 */ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - if (status->errormsg == NULL) - status->errormsg = strdup("No error message in packet"); - if (status->langmsg == NULL) - status->langmsg = strdup(""); - if (status->errormsg == NULL || status->langmsg == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - status_msg_free(status); - return NULL; - } - - return status; -} - -static void status_msg_free(sftp_status_message status){ - if (status == NULL) { - return; - } - - SAFE_FREE(status->errormsg); - SAFE_FREE(status->langmsg); - SAFE_FREE(status); -} - -static sftp_file parse_handle_msg(sftp_message msg){ - sftp_file file; - - if(msg->packet_type != SSH_FXP_HANDLE) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_handle message passed in!"); - return NULL; - } - - file = calloc(1, sizeof(struct sftp_file_struct)); - if (file == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - file->handle = ssh_buffer_get_ssh_string(msg->payload); - if (file->handle == NULL) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_HANDLE message"); - SAFE_FREE(file); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - file->sftp = msg->sftp; - file->offset = 0; - file->eof = 0; - - return file; -} + return file; +} /* Open a directory */ sftp_dir sftp_opendir(sftp_session sftp, const char *path) @@ -1074,12 +729,9 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - /* something nasty has happened */ - return NULL; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } switch (msg->packet_type) { @@ -1126,585 +778,143 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) 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 will need it in the 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; - } +/* Get the version of the SFTP protocol supported by the server */ +int sftp_server_version(sftp_session sftp) { + return sftp->server_version; +} - flags = ntohl(flags); - attr->flags = flags; +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) +{ + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + int rc; - if (flags & SSH_FILEXFER_ATTR_SIZE) { - if (ssh_buffer_get_u64(buf, &attr->size) != 8) { - break; - } - attr->size = ntohll(attr->size); - } + if (dir->buffer == NULL) { + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } - 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; - } + id = sftp_get_new_id(sftp); - 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; - } - } + rc = ssh_buffer_pack(payload, + "dS", + id, + dir->handle); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(payload); + return NULL; + } - 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; - } - } + rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } - if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { - if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { - break; - } - attr->atime64 = ntohll(attr->atime64); + SSH_LOG(SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %" PRIu32, id); - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { - break; + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - 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); + switch (msg->packet_type){ + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } - 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); - } - } + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %" PRIu32, status->status); + status_msg_free(status); - if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { - if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { - break; - } - attr->mtime64 = ntohll(attr->mtime64); + return NULL; + case SSH_FXP_NAME: + ssh_buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { - break; + return NULL; } - attr->mtime_nseconds = ntohl(attr->mtime_nseconds); - } } - if (flags & SSH_FILEXFER_ATTR_ACL) { - if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { - break; - } + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; } - if (flags & SSH_FILEXFER_ATTR_EXTENDED) { - if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { - break; - } - attr->extended_count = ntohl(attr->extended_count); + SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->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--; - } + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } - if (attr->extended_count) { - break; - } + dir->count--; + if (dir->count == 0) { + SSH_BUFFER_FREE(dir->buffer); + dir->buffer = NULL; } - 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); + return attr; +} - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} - return NULL; +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; } - 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, -}; + SSH_STRING_FREE(file->acl); + SSH_STRING_FREE(file->extended_data); + SSH_STRING_FREE(file->extended_type); -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++; - } - } + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); - 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; -} - -int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) -{ - uint32_t flags = (attr ? attr->flags : 0); - int rc; - - flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); - - rc = ssh_buffer_pack(buffer, "d", flags); - if (rc != SSH_OK) { - return -1; - } - - if (attr != NULL) { - if (flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_pack(buffer, "q", attr->size); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_pack(buffer, "d", attr->permissions); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); - if (rc != SSH_OK) { - return -1; - } - } - } - return 0; -} - - -sftp_attributes sftp_parse_attr(sftp_session session, - ssh_buffer buf, - int expectname) -{ - switch(session->version) { - case 4: - return sftp_parse_attr_4(session, buf, expectname); - case 3: - case 2: - case 1: - case 0: - return sftp_parse_attr_3(session, buf, expectname); - default: - ssh_set_error(session->session, SSH_FATAL, - "Version %d unsupported by client", session->server_version); - return NULL; - } - - return NULL; -} - -/* Get the version of the SFTP protocol supported by the server */ -int sftp_server_version(sftp_session sftp) { - return sftp->server_version; -} - -/* Get a single file attributes structure of a directory. */ -sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) -{ - sftp_message msg = NULL; - sftp_status_message status; - sftp_attributes attr; - ssh_buffer payload; - uint32_t id; - int rc; - - if (dir->buffer == NULL) { - payload = ssh_buffer_new(); - if (payload == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - id = sftp_get_new_id(sftp); - - rc = ssh_buffer_pack(payload, - "dS", - id, - dir->handle); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - SSH_BUFFER_FREE(payload); - return NULL; - } - - rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); - SSH_BUFFER_FREE(payload); - if (rc < 0) { - return NULL; - } - - SSH_LOG(SSH_LOG_PACKET, - "Sent a ssh_fxp_readdir with id %" PRIu32, id); - - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - /* something nasty has happened */ - return NULL; - } - msg = sftp_dequeue(sftp, id); - } - - switch (msg->packet_type){ - case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return NULL; - } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_EOF: - dir->eof = 1; - status_msg_free(status); - return NULL; - default: - break; - } - - ssh_set_error(sftp->session, SSH_FATAL, - "Unknown error status: %" PRIu32, status->status); - status_msg_free(status); - - return NULL; - case SSH_FXP_NAME: - ssh_buffer_get_u32(msg->payload, &dir->count); - dir->count = ntohl(dir->count); - dir->buffer = msg->payload; - msg->payload = NULL; - sftp_message_free(msg); - break; - default: - ssh_set_error(sftp->session, SSH_FATAL, - "Unsupported message back %d", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - - return NULL; - } - } - - /* now dir->buffer contains a buffer and dir->count != 0 */ - if (dir->count == 0) { - ssh_set_error(sftp->session, SSH_FATAL, - "Count of files sent by the server is zero, which is invalid, or " - "libsftp bug"); - return NULL; - } - - SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->count); - - attr = sftp_parse_attr(sftp, dir->buffer, 1); - if (attr == NULL) { - ssh_set_error(sftp->session, SSH_FATAL, - "Couldn't parse the SFTP attributes"); - return NULL; - } - - dir->count--; - if (dir->count == 0) { - SSH_BUFFER_FREE(dir->buffer); - dir->buffer = NULL; - } - - return attr; -} - -/* Tell if the directory has reached EOF (End Of File). */ -int sftp_dir_eof(sftp_dir dir) { - return dir->eof; -} - -/* Free a SFTP_ATTRIBUTE handle */ -void sftp_attributes_free(sftp_attributes file){ - if (file == NULL) { - return; - } - - SSH_STRING_FREE(file->acl); - SSH_STRING_FREE(file->extended_data); - SSH_STRING_FREE(file->extended_type); - - SAFE_FREE(file->name); - SAFE_FREE(file->longname); - SAFE_FREE(file->group); - SAFE_FREE(file->owner); - - SAFE_FREE(file); -} + SAFE_FREE(file); +} static int sftp_handle_close(sftp_session sftp, ssh_string handle) { @@ -1740,12 +950,9 @@ static int sftp_handle_close(sftp_session sftp, ssh_string handle) return -1; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - /* something nasty has happened */ - return -1; - } - msg = sftp_dequeue(sftp,id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; } switch (msg->packet_type) { @@ -1885,12 +1092,9 @@ sftp_file sftp_open(sftp_session sftp, return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - /* something nasty has happened */ - return NULL; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } switch (msg->packet_type) { @@ -1952,449 +1156,985 @@ void sftp_file_set_blocking(sftp_file handle) } /* 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; - sftp_message msg = NULL; - sftp_status_message status; - ssh_string datastring; - size_t datalen; - ssh_buffer buffer; - uint32_t id; - int rc; +ssize_t +sftp_read(sftp_file handle, void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + size_t datalen; + ssh_buffer buffer = NULL; + uint32_t id, read_len; + int rc; - if (handle == NULL) { - return -1; - } - sftp = handle->sftp; + if (handle == NULL) { + return -1; + } + sftp = handle->sftp; - if (handle->eof) { - return 0; - } + if (handle->eof) { + return 0; + } - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - return -1; - } + /* + * 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. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, count); - id = sftp_get_new_id(handle->sftp); + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } - rc = ssh_buffer_pack(buffer, - "dSqd", - id, - handle->handle, - handle->offset, - count); - if (rc != SSH_OK){ - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + id = sftp_get_new_id(handle->sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + handle->handle, + handle->offset, + read_len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); - while (msg == NULL) { - if (handle->nonblocking) { - if (ssh_channel_poll(handle->sftp->channel, 0) == 0) { - /* we cannot block */ - return 0; - } + rc = sftp_recv_response_msg(handle->sftp, id, !handle->nonblocking, &msg); + if (rc == SSH_ERROR) { + return -1; } - if (sftp_read_and_dispatch(handle->sftp) < 0) { - /* something nasty has happened */ - return -1; + + if (rc == SSH_AGAIN) { + /* + * file opened in non blocking mode and the response has not arrived + * yet. Since we cannot block, return 0 as the number of bytes read. + */ + return 0; } - msg = sftp_dequeue(handle->sftp, id); - } - switch (msg->packet_type) { + switch (msg->packet_type) { case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; - } - sftp_set_error(sftp, status->status); - switch (status->status) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { case SSH_FX_EOF: - handle->eof = 1; - status_msg_free(status); - return 0; + handle->eof = 1; + status_msg_free(status); + return 0; default: - break; - } - ssh_set_error(sftp->session,SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return -1; - case SSH_FXP_DATA: - datastring = ssh_buffer_get_ssh_string(msg->payload); - sftp_message_free(msg); - if (datastring == NULL) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received invalid DATA packet from sftp server"); + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); return -1; - } + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } - datalen = ssh_string_len(datastring); - if (datalen > count) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a too big DATA packet from sftp server: " - "%zu and asked for %zu", - datalen, count); + datalen = ssh_string_len(datastring); + if (datalen > count) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %zu", + datalen, + count); + SSH_STRING_FREE(datastring); + return -1; + } + handle->offset += (uint64_t)datalen; + memcpy(buf, ssh_string_data(datastring), datalen); SSH_STRING_FREE(datastring); - return -1; - } - handle->offset += (uint64_t)datalen; - memcpy(buf, ssh_string_data(datastring), datalen); - SSH_STRING_FREE(datastring); - return datalen; + return datalen; default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during read!", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - return -1; - } + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } - return -1; /* not reached */ + return -1; /* not reached */ } /* Start an asynchronous read from a file using an opened sftp file handle. */ -int sftp_async_read_begin(sftp_file file, uint32_t len){ - sftp_session sftp = file->sftp; - ssh_buffer buffer; - uint32_t id; - int rc; +int +sftp_async_read_begin(sftp_file file, uint32_t len) +{ + sftp_session sftp = file->sftp; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + 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, - "dSqd", - id, - file->handle, - file->offset, - len); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + rc = ssh_buffer_pack(buffer, "dSqd", id, file->handle, file->offset, len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); - file->offset += len; /* assume we'll read len bytes */ + file->offset += len; /* assume we'll read len bytes */ - return id; + return id; } /* Wait for an asynchronous read to complete and save the data. */ -int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ - sftp_session sftp; - sftp_message msg = NULL; - sftp_status_message status; - ssh_string datastring; - int err = SSH_OK; - uint32_t len; - - if (file == NULL) { - return SSH_ERROR; - } - sftp = file->sftp; - - if (file->eof) { - return 0; - } +int +sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + int rc, err = SSH_OK; + size_t len; - /* 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 (file == NULL) { + return SSH_ERROR; } + sftp = file->sftp; - if (sftp_read_and_dispatch(sftp) < 0) { - /* something nasty has happened */ - return SSH_ERROR; + if (file->eof) { + return 0; } - msg = sftp_dequeue(sftp,id); - } + /* handle an existing request */ + rc = sftp_recv_response_msg(sftp, id, !file->nonblocking, &msg); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } - switch (msg->packet_type) { + switch (msg->packet_type) { case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; - } - sftp_set_error(sftp, status->status); - if (status->status != SSH_FX_EOF) { - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server : %s", status->errormsg); - err = SSH_ERROR; - } else { - file->eof = 1; - } - status_msg_free(status); - return err; + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server : %s", + status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + } + status_msg_free(status); + return err; case SSH_FXP_DATA: - datastring = ssh_buffer_get_ssh_string(msg->payload); - sftp_message_free(msg); - if (datastring == NULL) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received invalid DATA packet from sftp server"); - return SSH_ERROR; - } - if (ssh_string_len(datastring) > size) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a too big DATA packet from sftp server: " - "%zu and asked for %" PRIu32, - ssh_string_len(datastring), size); + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return SSH_ERROR; + } + if (ssh_string_len(datastring) > size) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %" PRIu32, + ssh_string_len(datastring), + size); + SSH_STRING_FREE(datastring); + return SSH_ERROR; + } + len = ssh_string_len(datastring); + /* Update the offset with the correct value */ + file->offset = file->offset - (size - len); + memcpy(data, ssh_string_data(datastring), len); SSH_STRING_FREE(datastring); - return SSH_ERROR; - } - len = ssh_string_len(datastring); - /* Update the offset with the correct value */ - file->offset = file->offset - (size - len); - memcpy(data, ssh_string_data(datastring), len); - SSH_STRING_FREE(datastring); - return len; + return (int)len; default: - ssh_set_error(sftp->session,SSH_FATAL,"Received message %d during read!",msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - return SSH_ERROR; - } + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; + } - return SSH_ERROR; + return SSH_ERROR; } -ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { - sftp_session sftp; - sftp_message msg = NULL; - sftp_status_message status; - ssh_buffer buffer; - uint32_t id; - ssize_t len; - size_t packetlen; - int rc; +ssize_t +sftp_write(sftp_file file, const void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_buffer buffer = NULL; + uint32_t id, write_len; + ssize_t len; + size_t packetlen; + int rc; - if (file == NULL) { - return -1; - } - sftp = file->sftp; + if (file == NULL) { + return -1; + } + sftp = file->sftp; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - id = sftp_get_new_id(file->sftp); + id = sftp_get_new_id(file->sftp); - rc = ssh_buffer_pack(buffer, - "dSqdP", - id, - file->handle, - file->offset, - count, /* len of datastring */ - (size_t)count, buf); - if (rc != SSH_OK){ - ssh_set_error_oom(sftp->session); + /* + * 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. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, count); + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + write_len, /* len of datastring */ + (size_t)write_len, + buf); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen = ssh_buffer_get_len(buffer); SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - 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; - } else if ((size_t)len != packetlen) { - SSH_LOG(SSH_LOG_PACKET, - "Could not write as much data as expected"); - } + if (len < 0) { + return -1; + } else if ((size_t)len != packetlen) { + SSH_LOG(SSH_LOG_PACKET, "Could not write as much data as expected"); + } - while (msg == NULL) { - if (sftp_read_and_dispatch(file->sftp) < 0) { - /* something nasty has happened */ - return -1; + /* Wait for the response in blocking mode */ + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; } - msg = sftp_dequeue(file->sftp, id); - } - switch (msg->packet_type) { + switch (msg->packet_type) { case SSH_FXP_STATUS: - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; - } - sftp_set_error(sftp, status->status); - switch (status->status) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { case SSH_FX_OK: - file->offset += count; - status_msg_free(status); - return count; + file->offset += write_len; + status_msg_free(status); + return write_len; default: - break; - } - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - file->offset += count; - status_msg_free(status); - return -1; + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + file->offset += write_len; + status_msg_free(status); + return -1; default: - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d during write!", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - return -1; - } - - return -1; /* not reached */ -} - -/* Seek to a specific location in a file. */ -int sftp_seek(sftp_file file, uint32_t new_offset) { - if (file == NULL) { - return -1; - } + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int +sftp_seek(sftp_file file, uint32_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +int +sftp_seek64(sftp_file file, uint64_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +/* Report current byte position in file. */ +unsigned long sftp_tell(sftp_file file) { + return (unsigned long)file->offset; +} +/* Report current byte position in file. */ +uint64_t sftp_tell64(sftp_file file) { + return (uint64_t) file->offset; +} - file->offset = new_offset; +/* Rewinds the position of the file pointer to the beginning of the file.*/ +void sftp_rewind(sftp_file file) { + file->offset = 0; file->eof = 0; +} + +/* code written by Nick */ +int sftp_unlink(sftp_session sftp, const char *file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return SSH_FXP_STATUS */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session,SSH_FATAL, + "Received message %d when attempting to remove file", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } - return 0; + return -1; } -int sftp_seek64(sftp_file file, uint64_t new_offset) { - if (file == NULL) { +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to remove directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - file->offset = new_offset; - file->eof = 0; + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + uint32_t id; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_FAILURE: + /* + * mkdir always returns a failure, even if the path already exists. + * To be POSIX conform and to be able to map it to EEXIST a stat + * call is needed here. + */ + errno_attr = sftp_lstat(sftp, directory); + if (errno_attr != NULL) { + SAFE_FREE(errno_attr); + sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); + } + break; + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to make directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by nick */ +int sftp_rename(sftp_session sftp, const char *original, const char *newname) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = 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; + } + + id = sftp_get_new_id(sftp); + + /* + * 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; + } + + 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->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_RENAME; + } + + rc = sftp_packet_write(sftp, request_type, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + 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, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + const char *extension_name = "lsetstat@openssh.com"; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + 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, "dss", id, extension_name, file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to lsetstat", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; - return 0; -} + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; -/* Report current byte position in file. */ -unsigned long sftp_tell(sftp_file file) { - return (unsigned long)file->offset; -} -/* Report current byte position in file. */ -uint64_t sftp_tell64(sftp_file file) { - return (uint64_t) file->offset; -} + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; -/* Rewinds the position of the file pointer to the beginning of the file.*/ -void sftp_rewind(sftp_file file) { - file->offset = 0; - file->eof = 0; + return sftp_setstat(sftp, file, &attr); } -/* code written by Nick */ -int sftp_unlink(sftp_session sftp, const char *file) { +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +{ sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); + if (sftp == NULL) return -1; - } - - id = sftp_get_new_id(sftp); - - rc = ssh_buffer_pack(buffer, - "ds", - id, - file); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); + if (target == NULL || dest == NULL) { + ssh_set_error_invalid(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); - - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp)) { - return -1; - } - msg = sftp_dequeue(sftp, id); - } - - if (msg->packet_type == SSH_FXP_STATUS) { - /* by specification, this command's only supposed to return SSH_FXP_STATUS */ - status = parse_status_msg(msg); - sftp_message_free(msg); - if (status == NULL) { - return -1; - } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: - status_msg_free(status); - return 0; - default: - break; - } - - /* - * The status should be SSH_FX_OK if the command was successful, if it - * didn't, then there was an error - */ - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return -1; - } else { - ssh_set_error(sftp->session,SSH_FATAL, - "Received message %d when attempting to remove file", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - } - - return -1; -} - -/* code written by Nick */ -int sftp_rmdir(sftp_session sftp, const char *directory) { - sftp_status_message status = NULL; - sftp_message msg = NULL; - ssh_buffer buffer; - uint32_t id; - int rc; - buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2404,30 +2144,42 @@ int sftp_rmdir(sftp_session sftp, const char *directory) { id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, - "ds", - id, - directory); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + /* 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", + id, + target, + dest); + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + dest, + target); } - if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { SSH_BUFFER_FREE(buffer); return -1; } SSH_BUFFER_FREE(buffer); - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { return -1; - } - msg = sftp_dequeue(sftp, id); } - /* By specification, this command returns SSH_FXP_STATUS */ + /* 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); @@ -2442,14 +2194,17 @@ int sftp_rmdir(sftp_session sftp, const char *directory) { default: break; } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ ssh_set_error(sftp->session, SSH_REQUEST_DENIED, "SFTP server: %s", status->errormsg); status_msg_free(status); return -1; } else { ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to remove directory", - msg->packet_type); + "Received message %d when attempting to set stats", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } @@ -2457,119 +2212,116 @@ int sftp_rmdir(sftp_session sftp, const char *directory) { return -1; } -/* Code written by Nick */ -int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) +char *sftp_readlink(sftp_session sftp, const char *path) { sftp_status_message status = NULL; sftp_message msg = NULL; - sftp_attributes errno_attr = NULL; - struct sftp_attributes_struct attr; ssh_buffer buffer; uint32_t id; int rc; + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + return NULL; } - ZERO_STRUCT(attr); - attr.permissions = mode; - attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "ds", id, - directory); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - rc = buffer_add_attributes(buffer, &attr); + path); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer); + rc = sftp_packet_write(sftp, SSH_FXP_READLINK, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { - return -1; + return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *lnk = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &lnk); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to retrieve link"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return lnk; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { - return -1; + return NULL; } sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_FAILURE: - /* - * mkdir always returns a failure, even if the path already exists. - * To be POSIX conform and to be able to map it to EEXIST a stat - * call is needed here. - */ - errno_attr = sftp_lstat(sftp, directory); - if (errno_attr != NULL) { - SAFE_FREE(errno_attr); - sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); - } - break; - case SSH_FX_OK: - status_msg_free(status); - return 0; - default: - break; - } - /* - * The status should be SSH_FX_OK if the command was successful, if it - * didn't, then there was an error - */ ssh_set_error(sftp->session, SSH_REQUEST_DENIED, "SFTP server: %s", status->errormsg); status_msg_free(status); - return -1; - } else { + } else { /* this shouldn't happen */ ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to make directory", - msg->packet_type); + "Received message %d when attempting to set stats", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - return -1; + return NULL; } -/* code written by nick */ -int sftp_rename(sftp_session sftp, const char *original, const char *newname) +int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) { - 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; + 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); @@ -2579,62 +2331,28 @@ int sftp_rename(sftp_session sftp, const char *original, const char *newname) id = sftp_get_new_id(sftp); - /* - * 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; - } - - 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->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_RENAME; + 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, request_type, buffer); + 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); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; } /* By specification, this command only returns SSH_FXP_STATUS */ @@ -2662,7 +2380,7 @@ int sftp_rename(sftp_session sftp, const char *original, const char *newname) return -1; } else { ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to rename", + "Received message %d when attempting to create hardlink", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); @@ -2671,248 +2389,229 @@ int sftp_rename(sftp_session sftp, const char *original, const char *newname) return -1; } -/* Code written by Nick */ -/* Set file attributes on a file, directory or symbolic link. */ -int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + int rc; + + statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", + &statvfs->f_bsize, /* file system block size */ + &statvfs->f_frsize, /* fundamental fs block size */ + &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ + &statvfs->f_bfree, /* free blocks in file system */ + &statvfs->f_bavail, /* free blocks for non-root */ + &statvfs->f_files, /* total file inodes */ + &statvfs->f_ffree, /* free file inodes */ + &statvfs->f_favail, /* free file inodes for to non-root */ + &statvfs->f_fsid, /* file system id */ + &statvfs->f_flag, /* bit mask of f_flag values */ + &statvfs->f_namemax/* maximum filename length */ + ); + if (rc != SSH_OK) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) { - uint32_t id; - ssh_buffer buffer; - sftp_message msg = NULL; sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; int rc; + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "ds", + "dss", id, - file); + "statvfs@openssh.com", + path); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - rc = buffer_add_attributes(buffer, attr); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer); + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { - return -1; + return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { - return -1; + return NULL; } sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: - status_msg_free(status); - return 0; - default: - break; - } - /* - * The status should be SSH_FX_OK if the command was successful, if it - * didn't, then there was an error - */ ssh_set_error(sftp->session, SSH_REQUEST_DENIED, "SFTP server: %s", status->errormsg); status_msg_free(status); - return -1; - } else { + } else { /* this shouldn't happen */ ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to set stats", msg->packet_type); + "Received message %d when attempting to get statvfs", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - return -1; -} - -/* Change the file owner and group */ -int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { - struct sftp_attributes_struct attr; - ZERO_STRUCT(attr); - - attr.uid = owner; - attr.gid = group; - - attr.flags = SSH_FILEXFER_ATTR_UIDGID; - - return sftp_setstat(sftp, file, &attr); -} - -/* Change permissions of a file */ -int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { - struct sftp_attributes_struct attr; - ZERO_STRUCT(attr); - attr.permissions = mode; - attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - - return sftp_setstat(sftp, file, &attr); -} - -/* Change the last modification and access time of a file. */ -int sftp_utimes(sftp_session sftp, const char *file, - const struct timeval *times) { - struct sftp_attributes_struct attr; - ZERO_STRUCT(attr); - - attr.atime = times[0].tv_sec; - attr.atime_nseconds = times[0].tv_usec; - - attr.mtime = times[1].tv_sec; - attr.mtime_nseconds = times[1].tv_usec; - - attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | - SSH_FILEXFER_ATTR_SUBSECOND_TIMES; - - return sftp_setstat(sftp, file, &attr); + return NULL; } -int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +int sftp_fsync(sftp_file file) { - sftp_status_message status = NULL; - sftp_message msg = NULL; - ssh_buffer buffer; - uint32_t id; - int rc; - - if (sftp == NULL) - return -1; - if (target == NULL || dest == NULL) { - ssh_set_error_invalid(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + sftp_session sftp; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + if (file == NULL) { + return -1; + } + sftp = file->sftp; - id = sftp_get_new_id(sftp); + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - /* 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", - id, - target, - dest); - } else { - rc = ssh_buffer_pack(buffer, - "dss", - id, - dest, - target); - } - if (rc != SSH_OK){ - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } + id = sftp_get_new_id(sftp); - if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); + rc = ssh_buffer_pack(buffer, + "dsS", + id, + "fsync@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto done; + } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; } - 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_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + sftp_status_message status; + + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + rc = -1; + goto done; + } + + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + /* SUCCESS, LEAVE */ + status_msg_free(status); + rc = 0; + goto done; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); status_msg_free(status); - return 0; - default: - break; + + rc = -1; + goto done; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to set stats", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - /* - * The status should be SSH_FX_OK if the command was successful, if it - * didn't, then there was an error - */ - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return -1; - } else { - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to set stats", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - } - return -1; + rc = -1; +done: + SSH_BUFFER_FREE(buffer); + + return rc; } -char *sftp_readlink(sftp_session sftp, const char *path) +sftp_statvfs_t sftp_fstatvfs(sftp_file file) { sftp_status_message status = NULL; sftp_message msg = NULL; + sftp_session sftp; ssh_buffer buffer; uint32_t id; int rc; - if (sftp == NULL) { + if (file == NULL) { return NULL; } + sftp = file->sftp; - if (path == NULL) { - ssh_set_error_invalid(sftp); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - if (sftp->version < 3){ - ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2923,9 +2622,10 @@ char *sftp_readlink(sftp_session sftp, const char *path) id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "ds", + "dsS", id, - path); + "fstatvfs@openssh.com", + file->handle); if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); @@ -2933,37 +2633,25 @@ char *sftp_readlink(sftp_session sftp, const char *path) return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_READLINK, buffer); + 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); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; } - if (msg->packet_type == SSH_FXP_NAME) { - uint32_t ignored = 0; - char *lnk = NULL; - - rc = ssh_buffer_unpack(msg->payload, - "ds", - &ignored, - &lnk); + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); sftp_message_free(msg); - if (rc != SSH_OK) { - ssh_set_error(sftp->session, - SSH_ERROR, - "Failed to retrieve link"); - sftp_set_error(sftp, SSH_FX_FAILURE); + if (buf == NULL) { return NULL; } - return lnk; + return buf; } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); @@ -2984,129 +2672,196 @@ char *sftp_readlink(sftp_session sftp, const char *path) return NULL; } -int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +static sftp_limits_t sftp_limits_new(void) { - ssh_buffer buffer = NULL; - uint32_t id; - const char *extension_name = "hardlink@openssh.com"; - sftp_status_message status = NULL; - sftp_message msg = NULL; + 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; - if (sftp == NULL) { - return -1; + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; } - if (oldpath == NULL || newpath == NULL) { - ssh_set_error_invalid(sftp->session); + 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 -1; + return NULL; } + /* cap the max read and write length to UINT32_MAX as we really can not read + * nor write more as the len member of the SSH_FXP_READ/WRITE packets is + * uint32 */ + limits->max_read_length = MIN(limits->max_read_length, UINT32_MAX); + limits->max_write_length = MIN(limits->max_write_length, UINT32_MAX); + + 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 -1; + return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "dsss", + "ds", id, - extension_name, - oldpath, - newpath); + "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 -1; + return NULL; } rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { - return -1; + return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return -1; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { + 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 -1; + return NULL; } 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); + 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", + } 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 -1; + 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; } -static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { - sftp_statvfs_t statvfs; - int rc; +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; - statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); - if (statvfs == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } + if (sftp == NULL) { + return NULL; + } - rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", - &statvfs->f_bsize, /* file system block size */ - &statvfs->f_frsize, /* fundamental fs block size */ - &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ - &statvfs->f_bfree, /* free blocks in file system */ - &statvfs->f_bavail, /* free blocks for non-root */ - &statvfs->f_files, /* total file inodes */ - &statvfs->f_ffree, /* free file inodes */ - &statvfs->f_favail, /* free file inodes for to non-root */ - &statvfs->f_fsid, /* file system id */ - &statvfs->f_flag, /* bit mask of f_flag values */ - &statvfs->f_namemax/* maximum filename length */ - ); - if (rc != SSH_OK) { - SAFE_FREE(statvfs); - ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } + 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; + } - return statvfs; + 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; } -sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) +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) { sftp_status_message status = NULL; sftp_message msg = NULL; @@ -3121,11 +2876,6 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } - if (sftp->version < 3){ - ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } buffer = ssh_buffer_new(); if (buffer == NULL) { @@ -3137,38 +2887,45 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "dss", + "ds", id, - "statvfs@openssh.com", path); - if (rc != SSH_OK) { + if (rc < 0) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + rc = sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return NULL; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; } - if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { - sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + 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 (buf == NULL) { + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse canonicalized path"); + sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } - return buf; + return cname; } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ status = parse_status_msg(msg); sftp_message_free(msg); @@ -3181,7 +2938,7 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) status_msg_free(status); } else { /* this shouldn't happen */ ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to get statvfs", msg->packet_type); + "Received message %d when attempting to set stats", msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } @@ -3189,110 +2946,94 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) return NULL; } -int sftp_fsync(sftp_file file) +static sftp_attributes sftp_xstat(sftp_session sftp, + const char *path, + int param) { - sftp_session sftp; + sftp_status_message status = NULL; sftp_message msg = NULL; ssh_buffer buffer; uint32_t id; int rc; - if (file == NULL) { - return -1; + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; } - sftp = file->sftp; buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + return NULL; } id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "dsS", + "ds", id, - "fsync@openssh.com", - file->handle); - if (rc < 0) { + path); + if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); sftp_set_error(sftp, SSH_FX_FAILURE); - goto done; + return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + rc = sftp_packet_write(sftp, param, buffer); + SSH_BUFFER_FREE(buffer); if (rc < 0) { - ssh_set_error_oom(sftp->session); - goto done; + return NULL; } - do { - rc = sftp_read_and_dispatch(sftp); - if (rc < 0) { - ssh_set_error_oom(sftp->session); - rc = -1; - goto done; - } - msg = sftp_dequeue(sftp, id); - } while (msg == NULL); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } - /* By specification, this command only returns SSH_FXP_STATUS */ - if (msg->packet_type == SSH_FXP_STATUS) { - sftp_status_message status; + if (msg->packet_type == SSH_FXP_ATTRS) { + sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + sftp_message_free(msg); + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { - rc = -1; - goto done; + return NULL; } - sftp_set_error(sftp, status->status); - switch (status->status) { - case SSH_FX_OK: - /* SUCCESS, LEAVE */ - status_msg_free(status); - rc = 0; - goto done; - default: - break; - } - - /* - * The status should be SSH_FX_OK if the command was successful, if it - * didn't, then there was an error - */ - ssh_set_error(sftp->session, - SSH_REQUEST_DENIED, - "SFTP server: %s", - status->errormsg); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); status_msg_free(status); - - rc = -1; - goto done; - } else { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received message %d when attempting to set stats", - msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return NULL; } + ssh_set_error(sftp->session, SSH_FATAL, + "Received mesg %d during stat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - rc = -1; -done: - SSH_BUFFER_FREE(buffer); + return NULL; +} - return rc; +sftp_attributes sftp_stat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_STAT); } -sftp_statvfs_t sftp_fstatvfs(sftp_file file) +sftp_attributes sftp_lstat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_LSTAT); +} + +sftp_attributes sftp_fstat(sftp_file file) { sftp_status_message status = NULL; sftp_message msg = NULL; - sftp_session sftp; ssh_buffer buffer; uint32_t id; int rc; @@ -3300,91 +3041,80 @@ sftp_statvfs_t sftp_fstatvfs(sftp_file file) if (file == NULL) { return NULL; } - sftp = file->sftp; buffer = ssh_buffer_new(); if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); + ssh_set_error_oom(file->sftp->session); + sftp_set_error(file->sftp, SSH_FX_FAILURE); return NULL; } - id = sftp_get_new_id(sftp); + id = sftp_get_new_id(file->sftp); rc = ssh_buffer_pack(buffer, - "dsS", + "dS", id, - "fstatvfs@openssh.com", file->handle); - if (rc < 0) { - ssh_set_error_oom(sftp->session); + if (rc != SSH_OK) { + ssh_set_error_oom(file->sftp->session); SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_set_error(file->sftp, SSH_FX_FAILURE); return NULL; } - rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); SSH_BUFFER_FREE(buffer); if (rc < 0) { return NULL; } - while (msg == NULL) { - if (sftp_read_and_dispatch(sftp) < 0) { - return NULL; - } - msg = sftp_dequeue(sftp, id); + rc = sftp_recv_response_msg(file->sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { - sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + if (msg->packet_type == SSH_FXP_ATTRS){ + sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); sftp_message_free(msg); - if (buf == NULL) { - return NULL; - } - return buf; - } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { return NULL; } - sftp_set_error(sftp, status->status); - ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + sftp_set_error(file->sftp, status->status); + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, "SFTP server: %s", status->errormsg); status_msg_free(status); - } else { /* this shouldn't happen */ - ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to set stats", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); - } - return NULL; -} - -void sftp_statvfs_free(sftp_statvfs_t statvfs) { - if (statvfs == NULL) { - return; + return NULL; } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); - SAFE_FREE(statvfs); + return NULL; } -/* another code written by Nick */ -char *sftp_canonicalize_path(sftp_session sftp, const char *path) +char *sftp_expand_path(sftp_session sftp, const char *path) { sftp_status_message status = NULL; sftp_message msg = NULL; - ssh_buffer buffer; + ssh_buffer buffer = NULL; uint32_t id; int rc; - if (sftp == NULL) + if (sftp == NULL) { return NULL; + } + if (path == NULL) { - ssh_set_error_invalid(sftp->session); + 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; } @@ -3399,27 +3129,26 @@ char *sftp_canonicalize_path(sftp_session sftp, const char *path) id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "ds", + "dss", id, + "expand-path@openssh.com", path); - if (rc < 0) { + 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_REALPATH, buffer); + 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); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } if (msg->packet_type == SSH_FXP_NAME) { @@ -3434,13 +3163,13 @@ char *sftp_canonicalize_path(sftp_session sftp, const char *path) if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_ERROR, - "Failed to parse canonicalized path"); + "Failed to parse expanded path"); sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } return cname; - } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + } else if (msg->packet_type == SSH_FXP_STATUS) { status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { @@ -3448,11 +3177,12 @@ char *sftp_canonicalize_path(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); - } else { /* this shouldn't happen */ + } else { ssh_set_error(sftp->session, SSH_FATAL, - "Received message %d when attempting to set stats", msg->packet_type); + "Received message %d when attempting to expand path", + msg->packet_type); sftp_message_free(msg); sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } @@ -3460,13 +3190,12 @@ char *sftp_canonicalize_path(sftp_session sftp, const char *path) return NULL; } -static sftp_attributes sftp_xstat(sftp_session sftp, - const char *path, - int param) +char * +sftp_home_directory(sftp_session sftp, const char *username) { sftp_status_message status = NULL; sftp_message msg = NULL; - ssh_buffer buffer; + ssh_buffer buffer = NULL; uint32_t id; int rc; @@ -3474,12 +3203,6 @@ static sftp_attributes sftp_xstat(sftp_session sftp, return NULL; } - if (path == NULL) { - ssh_set_error_invalid(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -3490,9 +3213,10 @@ static sftp_attributes sftp_xstat(sftp_session sftp, id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, - "ds", + "dss", id, - path); + "home-directory", + username ? username : ""); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); @@ -3500,121 +3224,306 @@ static sftp_attributes sftp_xstat(sftp_session sftp, return NULL; } - rc = sftp_packet_write(sftp, param, buffer); + 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); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; } - if (msg->packet_type == SSH_FXP_ATTRS) { - sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t count = 0; + char *homepath = NULL; + char *longpath = NULL; + sftp_attributes attr = NULL; + + rc = ssh_buffer_unpack(msg->payload, "ds", &count, &homepath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to query user home directory"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + /* + for SFTP version > 3, longname field in SSH_FXP_NAME is omitted. + */ + if (sftp->version <= 3) { + rc = ssh_buffer_unpack(msg->payload, "s", &longpath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to extract longname from payload"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + attr = sftp_parse_attr(sftp, msg->payload, 0); + if (attr == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } sftp_message_free(msg); - return attr; + if (count != 1) { + if (count > 1) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Multiple results returned"); + } else { + ssh_set_error(sftp->session, SSH_ERROR, "No result returned"); + } + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (longpath) { + free(longpath); + } + sftp_attributes_free(attr); + return homepath; } 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); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); status_msg_free(status); - return NULL; + } else { + ssh_set_error( + sftp->session, + SSH_FATAL, + "Received message %d when attempting to query user home directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - ssh_set_error(sftp->session, SSH_FATAL, - "Received mesg %d during stat()", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); return NULL; } -sftp_attributes sftp_stat(sftp_session session, const char *path) { - return sftp_xstat(session, path, SSH_FXP_STAT); +sftp_name_id_map sftp_name_id_map_new(uint32_t count) +{ + sftp_name_id_map map = NULL; + + map = calloc(1, sizeof(struct sftp_name_id_map_struct)); + if (map == NULL) { + return NULL; + } + + map->count = count; + + map->ids = calloc(count, sizeof(uint32_t)); + if (map->ids == NULL) { + SAFE_FREE(map); + return NULL; + } + + map->names = calloc(count, sizeof(char *)); + if (map->names == NULL) { + SAFE_FREE(map->ids); + SAFE_FREE(map); + return NULL; + } + + return map; } -sftp_attributes sftp_lstat(sftp_session session, const char *path) { - return sftp_xstat(session, path, SSH_FXP_LSTAT); +void sftp_name_id_map_free(sftp_name_id_map map) +{ + if (map == NULL) { + return; + } + + SAFE_FREE(map->ids); + + if (map->names != NULL) { + for (uint32_t i = 0; i < map->count; i++) { + SAFE_FREE(map->names[i]); + } + SAFE_FREE(map->names); + } + + SAFE_FREE(map); } -sftp_attributes sftp_fstat(sftp_file file) +static int sftp_buffer_add_ids(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_pack(buffer, "d", sizeof(uint32_t) * id_count); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_pack(buffer, "d", map->ids[i]); + if (rc != SSH_OK) { + return -1; + } + } + + return 0; +} + +static int sftp_parse_names(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t name_buf_len = 0; + char *name = NULL; + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_unpack(buffer, "d", &name_buf_len); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_unpack(buffer, "s", &name); + if (rc != SSH_OK) { + return -1; + } + + name_buf_len -= strlen(name) + sizeof(uint32_t); + map->names[i] = name; + } + + if (name_buf_len != 0) { + return -1; + } + + return 0; +} + +int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map) { sftp_status_message status = NULL; sftp_message msg = NULL; - ssh_buffer buffer; + ssh_buffer buffer = NULL; uint32_t id; int rc; - if (file == NULL) { - return NULL; + if (sftp == NULL) { + return -1; + } + + /* check if the user has provided the correct arguments */ + if (users_map == NULL && groups_map == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Both users map and groups map cannot be NULL"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } buffer = ssh_buffer_new(); if (buffer == NULL) { - ssh_set_error_oom(file->sftp->session); - sftp_set_error(file->sftp, SSH_FX_FAILURE); - return NULL; + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - id = sftp_get_new_id(file->sftp); + id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, - "dS", - id, - file->handle); + rc = ssh_buffer_pack(buffer, "ds", id, "users-groups-by-id@openssh.com"); if (rc != SSH_OK) { - ssh_set_error_oom(file->sftp->session); + ssh_set_error_oom(sftp->session); SSH_BUFFER_FREE(buffer); - sftp_set_error(file->sftp, SSH_FX_FAILURE); - return NULL; + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; } - rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); + /* pack all uids */ + rc = sftp_buffer_add_ids(buffer, users_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all gids */ + rc = sftp_buffer_add_ids(buffer, groups_map); + 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 NULL; + return -1; } - while (msg == NULL) { - if (sftp_read_and_dispatch(file->sftp) < 0) { - return NULL; - } - msg = sftp_dequeue(file->sftp, id); + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; } - if (msg->packet_type == SSH_FXP_ATTRS){ - sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); - sftp_message_free(msg); + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + rc = sftp_parse_names(msg->payload, users_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse usernames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } - return attr; + rc = sftp_parse_names(msg->payload, groups_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse groupnames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + sftp_message_free(msg); + return 0; } else if (msg->packet_type == SSH_FXP_STATUS) { status = parse_status_msg(msg); sftp_message_free(msg); if (status == NULL) { - return NULL; + return -1; } - sftp_set_error(file->sftp, status->status); - ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, - "SFTP server: %s", status->errormsg); - status_msg_free(status); - return NULL; + 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 get user and " + "group names by id", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); } - ssh_set_error(file->sftp->session, SSH_FATAL, - "Received msg %d during fstat()", msg->packet_type); - sftp_message_free(msg); - sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); - return NULL; + return -1; } #endif /* WITH_SFTP */ diff --git a/src/sftp_aio.c b/src/sftp_aio.c new file mode 100644 index 00000000..20e9853a --- /dev/null +++ b/src/sftp_aio.c @@ -0,0 +1,494 @@ +/* + * 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 + * Copyright (c) 2023 by Eshan Kelkar + * + * 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, read_len; + 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 + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, len); + + 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, + read_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 = read_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 += read_len; + *aio = aio_handle; + return read_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 */ + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * 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, write_len; + 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 + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, len); + + 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, + write_len, /* len of datastring */ + (size_t)write_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 = write_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 += write_len; + *aio = aio_handle; + return write_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; + int rc; + + /* + * 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; + } + + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* Return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * 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..02970e4a --- /dev/null +++ b/src/sftp_common.c @@ -0,0 +1,1026 @@ +/* + * 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 + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/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 { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_FAILURE); + 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; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_FAILURE); + 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; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + 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--; + /* just ignore the extensions -- we can't interpret them */ + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + } + + 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 = NULL, *q = NULL; + size_t len, field = 0; + + if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || + longname_field > SFTP_LONGNAME_NAME) { + return NULL; + } + + p = longname; + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (*p != '\0' && field != longname_field) { + if (isspace(*p)) { + field++; + p++; + while (*p != '\0' && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + /* If we reached NULL before we got our field fail */ + if (field != longname_field) { + return NULL; + } + + q = p; + while (*q != '\0' && !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, 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, (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; +} + +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr) +{ + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return SSH_ERROR; + } + + if (msg_ptr == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Trying to receive response of request id %" PRIu32 " in %s mode", + id, + blocking ? "blocking" : "non-blocking"); + + /* + * We deliberately check the queue first for the response before + * polling/blocking on the channel. The reason for this approach is + * explained by the following example (And a similar scenario can occur when + * the async sftp aio API is used, because it provides the control of which + * responses to receive (and in what order) to the user via the + * sftp_aio_wait_*() functions) + * + * Its possible that while this function is trying to receive some + * specific response (based on request id), other responses have already + * arrived (or may arrive) before that specific response on the channel. In + * that case, this function would collect those other responses from the + * channel, add them to the sftp response queue (using + * sftp_read_and_dipatch()) and finally provide the caller with the + * required specific response. + * + * Now, whenever the caller will call this function again to get one of + * those other responses, it won't be on the channel, instead it would be + * present in the queue. + * + * Assuming that no new response ever comes on the channel, if we don't + * check the queue first and instead: + * - (In non blocking mode) poll on the channel, then we'd always get 0 + * bytes of data and return SSH_AGAIN. + * - (In blocking mode) wait on the channel, then + * sftp_read_and_dispatch() would block infinitely by default if the + * user has not set any timeout. + * + * Hence checking the queue for the response first and if not found there, + * polling/blocking on the channel is advised. + */ + while (msg == NULL) { + /* + * Before trying to poll/block on the channel for data, probe the queue + * to check whether the response is already present in it. + */ + msg = sftp_dequeue(sftp, id); + if (msg != NULL) { + break; + } + + if (!blocking) { + rc = ssh_channel_poll(sftp->channel, 0); + if (rc == SSH_ERROR) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (rc == 0) { + /* nothing available and we cannot block */ + return SSH_AGAIN; + } + } + + rc = sftp_read_and_dispatch(sftp); + if (rc == -1) { + /* something nasty has happened */ + return SSH_ERROR; + } + } + + *msg_ptr = msg; + return SSH_OK; +} + +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) { + SSH_LOG(SSH_LOG_WARN, + "Invalid SSH_FXP_STATUS message. Missing error message."); + } + + 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 7d8070b1..987d52a8 100644 --- a/src/sftpserver.c +++ b/src/sftpserver.c @@ -31,10 +31,19 @@ #include #include #endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ + #include #include #include #include +#include #include #include "libssh/libssh.h" @@ -51,6 +60,27 @@ #define MAX_ENTRIES_NUM_IN_PACKET 50 #define MAX_LONG_NAME_LEN 350 +/** + * @internal + * + * @brief Creates an SFTP client message from a received packet. + * + * Allocates and initializes a sftp_client_message structure from a raw + * SFTP packet. Copies the complete @p packet payload and parses common + * fields such as message type, request id, and message-specific data + * (handle, filename, attributes, offsets, etc.) depending on the SFTP + * message type. + * + * On success, the returned message owns its internal buffers and must + * be freed with sftp_client_message_free(). + * + * @param[in] sftp The SFTP session associated with the packet. + * @param[in] packet The received SFTP packet to decode. + * + * @return A newly allocated sftp_client_message on success, or NULL on + * error (memory allocation failure, malformed packet, or + * unsupported message type). + */ static sftp_client_message sftp_make_client_message(sftp_session sftp, sftp_packet packet) { @@ -247,15 +277,24 @@ sftp_make_client_message(sftp_session sftp, sftp_packet packet) return NULL; } +/** + * @brief Reads the next SFTP client message from the session. + * + * Reads a single SFTP packet from the given SFTP session and converts it + * into a parsed sftp_client_message structure. + * + * @param[in] sftp The SFTP session to read from. + * + * @return A newly allocated sftp_client_message on success; NULL if no packet + * is available or an error occurs. + */ sftp_client_message sftp_get_client_message(sftp_session sftp) { - ssh_session session = sftp->session; - sftp_packet packet; + sftp_packet packet = NULL; packet = sftp_packet_read(sftp); if (packet == NULL) { - ssh_set_error_oom(session); - return NULL; + return NULL; } return sftp_make_client_message(sftp, packet); } @@ -279,22 +318,62 @@ sftp_get_client_message_from_packet(sftp_session sftp) return sftp_make_client_message(sftp, packet); } -/* Send an sftp client message. Can be used in case of proxying */ +/** + * @brief Send an SFTP client message. + * + * Writes the given client message as a packet using the stored message + * type and complete_message buffer. Can be used in case of proxying. + * + * @param[in] sftp The SFTP session. + * @param[in] msg The client message to send. + * + * @return 0 on success; -1 on error from sftp_packet_write(). + */ int sftp_send_client_message(sftp_session sftp, sftp_client_message msg) { return sftp_packet_write(sftp, msg->type, msg->complete_message); } +/** + * @brief Get the SFTP client message type. + * + * Returns the SFTP packet type associated with the given client message + * (for example `SSH_FXP_READ`, `SSH_FXP_WRITE`, `SSH_FXP_OPEN`, ...). + * + * @param[in] msg The SFTP client message. + * + * @return The SFTP message type as an unsigned 8-bit value. + */ uint8_t sftp_client_message_get_type(sftp_client_message msg) { return msg->type; } +/** + * @brief Get the filename associated with an SFTP client message. + * + * Returns the filename carried by the given SFTP client message, if the + * message type includes a filename field (for example OPEN, REMOVE, RENAME). + * + * @param[in] msg The SFTP client message. + * + * @return Filename string, or NULL if no filename + * is associated with the message. + */ const char *sftp_client_message_get_filename(sftp_client_message msg) { return msg->filename; } +/** + * @brief Set the filename associated with an SFTP client message. + * + * Replaces the current filename stored in the client message with a copy + * of the given @p newname string. + * + * @param[in] msg The SFTP client message to modify. + * @param[in] newname The new filename to store in the message. + */ void sftp_client_message_set_filename(sftp_client_message msg, const char *newname) { @@ -302,6 +381,17 @@ sftp_client_message_set_filename(sftp_client_message msg, const char *newname) msg->filename = strdup(newname); } +/** + * @brief Get the data field of an SFTP client message as a string. + * + * Converts the internal ssh_string data field to a string + * on first use and caches the result in the message. Subsequent calls + * return the cached pointer. + * + * @param[in] msg The SFTP client message. + * + * @return The data as string, or NULL on error. + */ const char *sftp_client_message_get_data(sftp_client_message msg) { if (msg->str_data == NULL) @@ -309,16 +399,48 @@ const char *sftp_client_message_get_data(sftp_client_message msg) return msg->str_data; } +/** + * @brief Get the flags associated with an SFTP client message. + * + * Returns the flags field stored in the given SFTP client message. The exact + * meaning of the flags depends on the SFTP message type (for example, open + * or stat flags). + * + * @param[in] msg The SFTP client message. + * + * @return The flags value as an unsigned 32-bit integer. + */ uint32_t sftp_client_message_get_flags(sftp_client_message msg) { return msg->flags; } +/** + * @brief Get the submessage name associated with an SFTP client message. + * + * Returns the submessage string stored in the given SFTP client message. + * This is typically used for vendor-specific SFTP operations. + * + * @param[in] msg The SFTP client message. + * + * @return The submessage name as a string, or NULL if no + * submessage is associated with the message. + */ const char *sftp_client_message_get_submessage(sftp_client_message msg) { return msg->submessage; } +/** + * @brief Free an SFTP client message and its associated resources. + * + * Releases all dynamically allocated fields in the SFTP client message + * (such as filename, submessage, data, handle, attributes, and cached + * buffers) and then frees the message structure itself. The function + * does nothing if msg is NULL. + * + * @param[in] msg The SFTP client message to free, or NULL. + */ void sftp_client_message_free(sftp_client_message msg) { if (msg == NULL) { @@ -336,11 +458,25 @@ void sftp_client_message_free(sftp_client_message msg) SAFE_FREE(msg); } +/** + * @brief Send an SFTP NAME reply for a client message. + * + * Builds and sends an `SSH_FXP_NAME` packet in response to the given + * SFTP client message, containing a single filename and its attributes. + * The function encodes the message id, the count of returned names + * (always 1), the filename fields and the provided attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] name The filename to return to the client. + * @param[in] attr The file attributes associated with the filename. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ int sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) { - ssh_buffer out; - ssh_string file; + ssh_buffer out = NULL; + ssh_string file = NULL; out = ssh_buffer_new(); if (out == NULL) { @@ -371,6 +507,19 @@ sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) return 0; } +/** + * @brief Send an SFTP HANDLE reply for a client message. + * + * Builds and sends an `SSH_FXP_HANDLE` packet in response to the given + * SFTP client message, containing the provided file @p handle. The message + * id is taken from the client message and the handle is encoded as an + * SSH string. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] handle The file handle to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ int sftp_reply_handle(sftp_client_message msg, ssh_string handle) { ssh_buffer out; @@ -395,6 +544,18 @@ int sftp_reply_handle(sftp_client_message msg, ssh_string handle) return 0; } +/** + * @brief Send an SFTP ATTRS reply for a client message. + * + * Builds and sends an `SSH_FXP_ATTRS` packet in response to the given + * SFTP client message, encoding the message id and the provided file + * attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] attr The file attributes to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) { ssh_buffer out; @@ -417,11 +578,25 @@ int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) return 0; } +/** + * @brief Add one name entry to a multi-name SFTP reply. + * + * Appends a @p file name, @p longname and attributes to the buffered NAME reply + * stored in the client message. Can be called multiple times before the + * reply is sent. + * + * @param[in] msg The SFTP client message being prepared. + * @param[in] file The filename to add. + * @param[in] longname The long name to add. + * @param[in] attr The file attributes for this entry. + * + * @return 0 on success; -1 on memory allocation or buffer write error. + */ int sftp_reply_names_add(sftp_client_message msg, const char *file, const char *longname, sftp_attributes attr) { - ssh_string name; + ssh_string name = NULL; name = ssh_string_from_char(file); if (name == NULL) { @@ -457,6 +632,17 @@ sftp_reply_names_add(sftp_client_message msg, const char *file, return 0; } +/** + * @brief Send a multi-name SFTP reply. + * + * Sends an `SSH_FXP_NAME` packet for the given client message using the + * accumulated name entries stored in msg->attrbuf and msg->attr_num. + * After sending, the buffer and counter are reset. + * + * @param[in] msg The SFTP client message to reply to. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ int sftp_reply_names(sftp_client_message msg) { ssh_buffer out; @@ -488,11 +674,25 @@ int sftp_reply_names(sftp_client_message msg) return 0; } +/** + * @brief Send an SFTP STATUS reply. + * + * Sends an `SSH_FXP_STATUS` packet for the given client message, including + * the @p status code and an optional human readable @p message. The language + * tag is sent as an empty string. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] status The SFTP status code to send (e.g. `SSH_FX_OK`, + * `SSH_FX_FAILURE`). + * @param[in] message Optional text message describing the status, or NULL. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) { - ssh_buffer out; - ssh_string s; + ssh_buffer out = NULL; + ssh_string s = NULL; out = ssh_buffer_new(); if (out == NULL) { @@ -524,6 +724,18 @@ sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) return 0; } +/** + * @brief Send an SFTP DATA reply. + * + * Sends an `SSH_FXP_DATA` packet for the given client message, containing + * the provided data buffer and its length. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] data The data buffer to send. + * @param[in] len Number of bytes from data to send. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ int sftp_reply_data(sftp_client_message msg, const void *data, int len) { ssh_buffer out; @@ -639,16 +851,22 @@ int sftp_reply_version(sftp_client_message client_msg) 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). +/** + * @brief Allocate a new SFTP handle slot. + * + * Finds a free handle slot in the SFTP session, stores the given @p info + * there and returns a 4-byte ssh_string that encodes the handle + * index. + * + * @param[in] sftp The SFTP session. + * @param[in] info Info to be stored in the handle slot. + * + * @return A new handle as ssh_string on success; NULL if no slot is + * available or on memory allocation failure. */ ssh_string sftp_handle_alloc(sftp_session sftp, void *info) { - ssh_string ret; + ssh_string ret = NULL; uint32_t val; uint32_t i; @@ -681,6 +899,19 @@ ssh_string sftp_handle_alloc(sftp_session sftp, void *info) return ret; } +/** + * @brief Resolve an SFTP handle to its stored info. + * + * Decodes the 4-byte @p handle value, checks bounds and returns the pointer + * stored in the corresponding handle slot in the SFTP session. + * + * @param[in] sftp The SFTP session. + * @param[in] handle The handle value as ssh_string. + * + * @return The stored pointer on success, or NULL if the handle table is + * not initialized, the handle size is invalid, or the index is + * out of range. + */ void *sftp_handle(sftp_session sftp, ssh_string handle) { uint32_t val; @@ -695,7 +926,7 @@ void *sftp_handle(sftp_session sftp, ssh_string handle) memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); - if (val > SFTP_HANDLES) { + if (val >= SFTP_HANDLES) { return NULL; } @@ -780,8 +1011,8 @@ stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct * 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; + z_attr->atime = (uint32_t)z_st->st_atime; + z_attr->mtime = (uint32_t)z_st->st_mtime; } static void @@ -813,7 +1044,7 @@ struct sftp_handle char *name; }; -SSH_SFTP_CALLBACK(process_unsupposed); +SSH_SFTP_CALLBACK(process_unsupported); SSH_SFTP_CALLBACK(process_open); SSH_SFTP_CALLBACK(process_read); SSH_SFTP_CALLBACK(process_write); @@ -829,6 +1060,7 @@ SSH_SFTP_CALLBACK(process_readlink); SSH_SFTP_CALLBACK(process_symlink); SSH_SFTP_CALLBACK(process_remove); SSH_SFTP_CALLBACK(process_extended_statvfs); +SSH_SFTP_CALLBACK(process_setstat); const struct sftp_message_handler message_handlers[] = { {"open", NULL, SSH_FXP_OPEN, process_open}, @@ -836,9 +1068,9 @@ const struct sftp_message_handler message_handlers[] = { {"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}, + {"fstat", NULL, SSH_FXP_FSTAT, process_unsupported}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_setstat}, + {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupported}, {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, {"remove", NULL, SSH_FXP_REMOVE, process_remove}, @@ -846,7 +1078,7 @@ const struct sftp_message_handler message_handlers[] = { {"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}, + {"rename", NULL, SSH_FXP_RENAME, process_unsupported}, {"readlink", NULL, SSH_FXP_READLINK, process_readlink}, {"symlink", NULL, SSH_FXP_SYMLINK, process_symlink}, {"init", NULL, SSH_FXP_INIT, sftp_reply_version}, @@ -874,17 +1106,26 @@ process_open(sftp_client_message client_msg) 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) + if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE) { + if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + /* Both read and write */ + file_flag = O_RDWR; + } else { + /* Only 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) + } + + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) { file_flag |= O_CREAT; + } + + if ((msg_flag & (uint32_t)SSH_FXF_TRUNC) == SSH_FXF_TRUNC) { + file_flag |= O_TRUNC; + } } else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { file_flag = O_RDONLY; } else { @@ -918,6 +1159,7 @@ process_open(sftp_client_message client_msg) sftp_reply_handle(client_msg, handle_s); ssh_string_free(handle_s); } else { + free(h); close(fd); SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate handle"); sftp_reply_status(client_msg, SSH_FX_FAILURE, @@ -933,17 +1175,17 @@ 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; + ssize_t readn = 0; int fd = -1; char *buffer = NULL; - int rv; + off_t off; 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) { + if (h != NULL && h->type == SFTP_FILE_HANDLE) { fd = h->fd; } @@ -952,8 +1194,8 @@ process_read(sftp_client_message client_msg) 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) { + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file fd: %d at offset: %" PRIu64, @@ -968,22 +1210,14 @@ process_read(sftp_client_message client_msg) 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); + readn = ssh_readn(fd, buffer, client_msg->len); + 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) { + sftp_reply_data(client_msg, buffer, readn); } else { sftp_reply_status(client_msg, SSH_FX_EOF, NULL); } @@ -1002,14 +1236,14 @@ process_write(sftp_client_message client_msg) int fd = -1; const char *msg_data = NULL; uint32_t len; - int rv; + off_t off; 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) { + if (h != NULL && h->type == SFTP_FILE_HANDLE) { fd = h->fd; } if (fd < 0) { @@ -1021,21 +1255,20 @@ process_write(sftp_client_message client_msg) 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) { + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); - SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file at offset: %" PRIu64, + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file at offset: %" PRIu64, client_msg->offset); + return SSH_ERROR; + } + written = ssh_writen(fd, msg_data, len); + if (written != (ssize_t)len) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); + SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); + return SSH_ERROR; } - 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); @@ -1055,7 +1288,11 @@ process_close(sftp_client_message client_msg) ssh_string_len(handle)); h = sftp_handle(sftp, handle); - if (h->type == SFTP_FILE_HANDLE) { + if (h == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "invalid handle"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, "Invalid handle"); + return SSH_OK; + } else if (h->type == SFTP_FILE_HANDLE) { int fd = h->fd; close(fd); ret = SSH_OK; @@ -1113,6 +1350,7 @@ process_opendir(sftp_client_message client_msg) sftp_reply_handle(client_msg, handle_s); ssh_string_free(handle_s); } else { + free(h); closedir(dir); sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); } @@ -1222,7 +1460,7 @@ process_readdir(sftp_client_message client_msg) ssh_string_len(handle)); h = sftp_handle(sftp, client_msg->handle); - if (h->type == SFTP_DIR_HANDLE) { + if (h != NULL && h->type == SFTP_DIR_HANDLE) { dir = h->dirp; handle_name = h->name; } @@ -1437,6 +1675,103 @@ process_stat(sftp_client_message client_msg) return ret; } +static int +process_setstat(sftp_client_message client_msg) +{ + int rv; + int ret = SSH_OK; + int status = SSH_FX_OK; + uint32_t msg_flags = client_msg->attr->flags; + const char *filename = sftp_client_message_get_filename(client_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); + + if (filename == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + if (msg_flags & SSH_FILEXFER_ATTR_SIZE) { + rv = truncate(filename, client_msg->attr->size); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "changing size failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rv = chmod(filename, client_msg->attr->permissions); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chmod failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_UIDGID) { + rv = chown(filename, client_msg->attr->uid, client_msg->attr->gid); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chwon failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_ACMODTIME) { +#ifdef HAVE_SYS_TIME_H + struct timeval tv[2]; + + tv[0].tv_sec = client_msg->attr->atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = client_msg->attr->mtime; + tv[1].tv_usec = 0; + + rv = utimes(filename, tv); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } +#else + struct _utimbuf tf; + + tf.actime = client_msg->attr->atime; + tf.modtime = client_msg->attr->mtime; + + rv = _utime(filename, &tf); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return rv; + } +#endif + } + + sftp_reply_status(client_msg, status, NULL); + return ret; +} + static int process_readlink(sftp_client_message client_msg) { @@ -1444,7 +1779,7 @@ process_readlink(sftp_client_message client_msg) const char *filename = sftp_client_message_get_filename(client_msg); char buf[PATH_MAX]; int len = -1; - const char *err_msg; + const char *err_msg = NULL; int status = SSH_FX_OK; SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); @@ -1530,7 +1865,7 @@ process_remove(sftp_client_message client_msg) } static int -process_unsupposed(sftp_client_message client_msg) +process_unsupported(sftp_client_message client_msg) { sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, "Operation not supported"); @@ -1676,8 +2011,8 @@ process_client_message(sftp_client_message client_msg) * @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. + * @return `SSH_OK` when the SFTP server was successfully initialized, + * `SSH_ERROR` otherwise. */ int sftp_channel_default_subsystem_request(ssh_session session, @@ -1727,13 +2062,13 @@ sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), if (sftpp == NULL) { SSH_LOG(SSH_LOG_WARNING, "NULL userdata passed to callback"); - return -1; + return SSH_ERROR; } sftp = *sftpp; decode_len = sftp_decode_channel_data_to_packet(sftp, data, len); - if (decode_len == -1) - return -1; + if (decode_len == SSH_ERROR) + return SSH_ERROR; msg = sftp_get_client_message_from_packet(sftp); rc = process_client_message(msg); diff --git a/src/sk_common.c b/src/sk_common.c new file mode 100644 index 00000000..883d1568 --- /dev/null +++ b/src/sk_common.c @@ -0,0 +1,290 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/callbacks.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef HAVE_LIBFIDO2 +#include "libssh/sk_usbhid.h" +#endif + +const char *ssh_sk_err_to_string(int sk_err) +{ + switch (sk_err) { + case SSH_SK_ERR_UNSUPPORTED: + return "Unsupported operation"; + case SSH_SK_ERR_PIN_REQUIRED: + return "PIN required but is either missing or invalid"; + case SSH_SK_ERR_DEVICE_NOT_FOUND: + return "No suitable device found"; + case SSH_SK_ERR_CREDENTIAL_EXISTS: + return "Credential already exists"; + case SSH_SK_ERR_GENERAL: + return "General error"; + default: + return "Unknown error"; + } +} + +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response) +{ + if (enroll_response == NULL) { + return; + } + + BURN_FREE(enroll_response->public_key, enroll_response->public_key_len); + BURN_FREE(enroll_response->key_handle, enroll_response->key_handle_len); + BURN_FREE(enroll_response->signature, enroll_response->signature_len); + BURN_FREE(enroll_response->attestation_cert, + enroll_response->attestation_cert_len); + BURN_FREE(enroll_response->authdata, enroll_response->authdata_len); + + ssh_burn(enroll_response, sizeof(*enroll_response)); +} + +void sk_enroll_response_free(struct sk_enroll_response *enroll_response) +{ + sk_enroll_response_burn(enroll_response); + SAFE_FREE(enroll_response); +} + +void sk_sign_response_free(struct sk_sign_response *sign_response) +{ + if (sign_response == NULL) { + return; + } + + BURN_FREE(sign_response->sig_r, sign_response->sig_r_len); + BURN_FREE(sign_response->sig_s, sign_response->sig_s_len); + SAFE_FREE(sign_response); +} + +void sk_resident_key_free(struct sk_resident_key *resident_key) +{ + if (resident_key == NULL) { + return; + } + + SAFE_FREE(resident_key->application); + BURN_FREE(resident_key->user_id, resident_key->user_id_len); + sk_enroll_response_burn(&resident_key->key); + SAFE_FREE(resident_key); +} + +void sk_options_free(struct sk_option **options) +{ + size_t i; + + if (options == NULL) { + return; + } + + for (i = 0; options[i] != NULL; i++) { + SAFE_FREE(options[i]->name); + SAFE_FREE(options[i]->value); + SAFE_FREE(options[i]); + } + SAFE_FREE(options); +} + +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values) +{ + size_t i, j; + size_t key_count = 0; + int found; + + if (keys == NULL || values == NULL || options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameter(s) provided"); + return SSH_ERROR; + } + + while (keys[key_count] != NULL) { + key_count++; + } + + *values = calloc(key_count + 1, sizeof(char *)); + if (*values == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate values array"); + return SSH_ERROR; + } + + for (i = 0; options[i] != NULL; i++) { + const struct sk_option *option = options[i]; + + found = 0; + + /* Look for this option name in the supported keys */ + for (j = 0; j < key_count; j++) { + + if (strcmp(option->name, keys[j]) == 0) { + /* Copy the value string if it exists */ + if (option->value != NULL) { + (*values)[j] = strdup(option->value); + if ((*values)[j] == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + goto error; + } + + } else { + (*values)[j] = NULL; + } + found = 1; + break; + } + } + + /* If option is required but not supported, fail */ + if (!found && option->required) { + SSH_LOG(SSH_LOG_WARN, + "Required option '%s' is not supported", + option->name); + goto error; + } + } + + return SSH_OK; + +error: + for (j = 0; j < key_count; j++) { + SAFE_FREE((*values)[j]); + } + + SAFE_FREE(*values); + return SSH_ERROR; +} + +struct sk_option **sk_options_dup(const struct sk_option **options) +{ + struct sk_option **new_options = NULL; + size_t count = 0; + size_t i; + + if (options == NULL) { + return NULL; + } + + /* Count the number of options */ + while (options[count] != NULL) { + count++; + } + + new_options = calloc(count + 1, sizeof(struct sk_option *)); + if (new_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for options array"); + return NULL; + } + + /* Copy each option */ + for (i = 0; i < count; i++) { + const struct sk_option *option = options[i]; + struct sk_option *new_option = NULL; + + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option"); + goto error; + } + + /* Copy option name */ + if (option->name != NULL) { + new_option->name = strdup(option->name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option name"); + SAFE_FREE(new_option); + goto error; + } + } + + /* Copy option value */ + if (option->value != NULL) { + new_option->value = strdup(option->value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + goto error; + } + } + + new_option->required = option->required; + new_options[i] = new_option; + } + + new_options[count] = NULL; + return new_options; + +error: + SK_OPTIONS_FREE(new_options); + return NULL; +} + +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks) +{ + uint32_t callback_version; + uint32_t callback_version_major; + uint32_t libssh_version_major; + + if (callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks cannot be NULL"); + return false; + } + + /* Check if the api_version callback is provided */ + if (!ssh_callbacks_exists(callbacks, api_version)) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks missing api_version callback"); + return false; + } + + /* Extract major version from callback provider */ + callback_version = callbacks->api_version(); + callback_version_major = callback_version & SSH_SK_VERSION_MAJOR_MASK; + + libssh_version_major = SSH_SK_VERSION_MAJOR; + + /* Check if major versions are compatible */ + if (callback_version_major != libssh_version_major) { + SSH_LOG(SSH_LOG_WARN, + "SK API major version mismatch: callback provides 0x%08x, " + "libssh supports 0x%08x", + callback_version_major, + libssh_version_major); + return false; + } + + return true; +} + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void) +{ +#ifdef HAVE_LIBFIDO2 + return ssh_sk_get_usbhid_callbacks(); +#else + return NULL; +#endif /* HAVE_LIBFIDO2 */ +} diff --git a/src/sk_usbhid.c b/src/sk_usbhid.c new file mode 100644 index 00000000..782df0ae --- /dev/null +++ b/src/sk_usbhid.c @@ -0,0 +1,2239 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/sk_api.h" +#include "libssh/sk_common.h" +#include "libssh/sk_usbhid.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#endif + +#define SK_USBHID_API_VERSION 0x000a0000 + +#define ECDSA_P256_PUBKEY_LEN 64 + +/* Maximum number of FIDO2/U2F devices that can be connected */ +#define MAX_FIDO_DEVICES 8 + +/* Timeout for touch detection on single FIDO2/U2F device during each polling */ +#define FIDO_POLL_MS 50 + +/* Sleep between each consecutive polling */ +#define POLL_SLEEP_NS 200000000 + +/* The entire timeout for user to touch any of the connected devices */ +#define SELECT_MS 15000 + +/* DER encoding constants */ +#define DER_SEQUENCE_TAG 0x30 +#define DER_INTEGER_TAG 0x02 +#define DER_MAX_LEN_BYTES 2 + +struct sk_device { + char *path; + fido_dev_t *fido_device; +}; + +/** + * libfido2 log handler that prints libfido2 debug messages. + * + * @param msg The log message from libfido2 + */ +static void fido_log_handler(const char *msg) +{ + if (msg == NULL) { + return; + } + + SSH_LOG(SSH_LOG_TRACE, "libfido2: %s", msg); +} + +/** + * Initialize libfido2 with appropriate logging settings based on + * current libssh log level. + */ +static void sk_fido_init(void) +{ + int fido_flags = 0; + int log_level = ssh_get_log_level(); + + /* Enable libfido2 debug output if libssh is at TRACE level */ + if (log_level == SSH_LOG_TRACE) { + fido_flags |= FIDO_DEBUG; + fido_set_log_handler(fido_log_handler); + } + + fido_init(fido_flags); +} + +/** + * Convert a libfido2 error code to a libssh security key error code. + * + * @param fido_err The FIDO error code to convert + * + * @return The corresponding SSH_SK_ERR_* error code + */ +static int fido_err_to_ssh_sk_err(int fido_err) +{ + switch (fido_err) { + case FIDO_ERR_UNSUPPORTED_OPTION: + case FIDO_ERR_UNSUPPORTED_ALGORITHM: + case FIDO_ERR_UNSUPPORTED_EXTENSION: + return SSH_SK_ERR_UNSUPPORTED; + case FIDO_ERR_PIN_REQUIRED: + case FIDO_ERR_PIN_INVALID: + return SSH_SK_ERR_PIN_REQUIRED; + default: + return SSH_SK_ERR_GENERAL; + } +} + +static void sk_device_close(struct sk_device *device) +{ + int rc; + + if (device == NULL) { + return; + } + + if (device->fido_device != NULL) { + rc = fido_dev_cancel(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to cancel device operations: %s", + fido_strerr(rc)); + } + + rc = fido_dev_close(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to close device: %s", + fido_strerr(rc)); + } + + fido_dev_free(&device->fido_device); + } + + SAFE_FREE(device->path); + SAFE_FREE(device); +} + +static struct sk_device *sk_device_open(const char *device_path) +{ + int rc; + struct sk_device *device = NULL; + + if (device_path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Device path cannot be NULL"); + goto error; + } + + device = calloc(1, sizeof(struct sk_device)); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for sk_device"); + goto error; + } + + device->fido_device = fido_dev_new(); + if (device->fido_device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new fido device instance"); + goto error; + } + + device->path = strdup(device_path); + if (device->path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device path"); + goto error; + } + + rc = fido_dev_open(device->fido_device, device->path); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to open FIDO2/U2F device at %s: %s", + device->path, + fido_strerr(rc)); + goto error; + } + + return device; + +error: + sk_device_close(device); + return NULL; +} + +static void sk_device_close_list(struct sk_device **devices, size_t num_devices) +{ + size_t i; + + if (devices == NULL) { + return; + } + + for (i = 0; i < num_devices; i++) { + sk_device_close(devices[i]); + } + SAFE_FREE(devices); +} + +static struct sk_device ** +sk_device_open_list(const fido_dev_info_t *device_list, + size_t num_devices, + size_t *num_opened) +{ + size_t i; + const char *device_path = NULL; + struct sk_device **devices = NULL; + const fido_dev_info_t *device_info = NULL; + + *num_opened = 0; + + devices = calloc(num_devices, sizeof(struct sk_device *)); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device list"); + return NULL; + } + + for (i = 0; i < num_devices; i++) { + device_info = fido_dev_info_ptr(device_list, i); + if (device_info == NULL) { + SSH_LOG(SSH_LOG_INFO, "Failed to get device info for index %zu", i); + continue; + } + + device_path = fido_dev_info_path(device_info); + devices[*num_opened] = sk_device_open(device_path); + if (devices[*num_opened] == NULL) { + SSH_LOG(SSH_LOG_INFO, + "Failed to open device %zu at %s", + *num_opened, + device_path); + } else { + (*num_opened)++; + } + } + + if (*num_opened == 0) { + sk_device_close_list(devices, num_devices); + devices = NULL; + } + + return devices; +} + +/** + * Check if given device has the credentials corresponding to the given + * key_handle. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to check for + * @param key_handle_len The length of the key handle in bytes + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_key_handle(const struct sk_device *device, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int ret = FIDO_ERR_INTERNAL; + uint8_t dummy_data[32] = {0}; + fido_assert_t *assert = NULL; + bool is_dev_fido2 = false; + + /* + * We make use of the pre-flight checking as described in + * https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight + * to identify whether the device knows of the passed key_handle. + */ + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + return FIDO_ERR_INTERNAL; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_up(assert, FIDO_OPT_FALSE); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Allow assertions only from this particular key_handle */ + ret = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential for assertion: %s", + fido_strerr(ret)); + goto out; + } + + is_dev_fido2 = fido_dev_is_fido2(device->fido_device); + + ret = fido_dev_get_assert(device->fido_device, assert, NULL); + + if (!is_dev_fido2 && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) { + /* U2F devices might return this */ + ret = FIDO_OK; + } else if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Check if given device has the resident key with given user_id and + * application. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param user_id The binary user ID to search for in resident keys + * @param user_id_len Length of binary user_id + * @param pin The PIN for the device (can be NULL if not required) + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_resident_key(const struct sk_device *device, + const char *application, + const uint8_t *user_id, + size_t user_id_len, + const char *pin) +{ + int rc, ret = FIDO_ERR_INTERNAL; + bool supports_uv = false; + size_t i, num_asserts = 0, len; + const uint8_t *ptr = NULL; + uint8_t dummy_data[32] = {0}; + fido_opt_t user_verification = FIDO_OPT_OMIT; + fido_assert_t *assert = NULL; + + /* If no user_id or zero length provided, nothing to compare */ + if (user_id == NULL || user_id_len == 0) { + return FIDO_ERR_NO_CREDENTIALS; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + goto out; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Check if device supports internal user verification methods such as + * biometric */ + supports_uv = fido_dev_supports_uv(device->fido_device); + + /* + * Determine user verification strategy for resident key enumeration: + * - If PIN is provided, rely on PIN-based authentication (UV = OMIT) + * + * - If no PIN is provided but device supports internal UV (biometric/etc), + * enable UV to ensure we can access all resident keys regardless of their + * credential protection while minimising user friction. + * + * - If no PIN is provided and device does not support internal UV, we will + * only be able to access resident keys without user-verification + * protection. + * + * Read about credential protection and resident keys: + * (https://developers.yubico.com/WebAuthn/WebAuthn_Developer_Guide/Resident_Keys.html) + */ + user_verification = + (pin == NULL && supports_uv) ? FIDO_OPT_TRUE : FIDO_OPT_OMIT; + ret = fido_assert_set_uv(assert, user_verification); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_dev_get_assert(device->fido_device, assert, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + goto out; + } + + ret = FIDO_ERR_NO_CREDENTIALS; + + num_asserts = fido_assert_count(assert); + for (i = 0; i < num_asserts; i++) { + ptr = fido_assert_user_id_ptr(assert, i); + len = fido_assert_user_id_len(assert, i); + + if (len != user_id_len) { + continue; + } + + rc = memcmp(ptr, user_id, user_id_len); + if (rc == 0) { + SSH_LOG(SSH_LOG_INFO, "Resident key with given user ID exists"); + ret = FIDO_OK; + break; + } + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Begin touch detection on all devices in the provided list. + * + * @param devices Array of device pointers + * @param num_devices Number of devices in the array + * + * @return SSH_OK if at least one device started touch detection successfully, + * SSH_ERROR if all devices failed + */ +static int sk_device_touch_begin(struct sk_device **devices, size_t num_devices) +{ + int rc; + size_t i, num_success = 0; + + for (i = 0; i < num_devices; i++) { + rc = fido_dev_get_touch_begin(devices[i]->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to begin touch on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + } else { + num_success++; + } + } + + return (num_success > 0) ? SSH_OK : SSH_ERROR; +} + +/** + * Poll the touch status on all devices and return the index of the device + * on which touch was detected. + * + * @param devices Array of device pointers to poll for touch status + * @param num_devices Number of devices in the array + * @param touch_detected Pointer to store whether touch was detected (0 or 1) + * @param chosen_idx Pointer to store the index of the device that was touched + * + * @return SSH_OK on successful polling (regardless of whether touch was + * detected), SSH_ERROR if no devices left to poll. + * + * @warning Automatically closes the device if any error occurs + * while detecting if it was touched. + */ +static int sk_device_touch_poll(struct sk_device **devices, + size_t num_devices, + int *touch_detected, + size_t *chosen_idx) +{ + int rc; + size_t i, n_failed = 0; + + for (i = 0; i < num_devices; i++) { + if (devices[i] == NULL) { + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Polling touch status on device %s", + devices[i]->path); + + rc = fido_dev_get_touch_status(devices[i]->fido_device, + touch_detected, + FIDO_POLL_MS); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get touch status on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + sk_device_close(devices[i]); + devices[i] = NULL; + + n_failed++; + if (n_failed == num_devices) { + SSH_LOG(SSH_LOG_WARN, "No devices left to poll"); + return SSH_ERROR; + } + } else if (*touch_detected) { + *chosen_idx = i; + return SSH_OK; + } + } + + *touch_detected = 0; + return SSH_OK; +} + +/** + * Select a device from the list of devices which has the given + * application and key handle. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to look for + * @param key_handle_len The length of the key handle in bytes + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_credential(const fido_dev_info_t *device_list, + size_t num_devices, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int rc; + size_t num_opened = 0, i; + struct sk_device **devices = NULL, *selected_device = NULL; + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + selected_device = NULL; + for (i = 0; i < num_opened; i++) { + rc = sk_device_check_key_handle(devices[i], + application, + key_handle, + key_handle_len); + if (rc == FIDO_OK) { + selected_device = devices[i]; + devices[i] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Selected device %s for key handle", + selected_device->path); + break; + } + } + + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Select a device by touch, where the user touches the key they want to use. + * The function will block until a touch is detected or the timeout is reached. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_touch(const fido_dev_info_t *device_list, + size_t num_devices) +{ + int rc, touch = 0; + size_t num_opened = 0, chosen_idx; + struct sk_device **devices = NULL, *selected_device = NULL; + struct ssh_timestamp ts; + +#ifndef _WIN32 + struct timespec poll_sleep = {.tv_sec = 0, .tv_nsec = POLL_SLEEP_NS}; +#endif + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + if (num_opened == 1) { + selected_device = devices[0]; + devices[0] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Only one device opened, automatically selected %s", + selected_device->path); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) opened", num_opened); + + rc = sk_device_touch_begin(devices, num_opened); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to begin touch on any device"); + goto out; + } + + ssh_timestamp_init(&ts); + do { + rc = sk_device_touch_poll(devices, num_opened, &touch, &chosen_idx); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to poll touch status"); + goto out; + } else if (touch) { + selected_device = devices[chosen_idx]; + devices[chosen_idx] = NULL; + goto out; + } + + if (ssh_timeout_elapsed(&ts, SELECT_MS)) { + SSH_LOG(SSH_LOG_WARN, "Touch selection timed out"); + break; + } + +#ifdef _WIN32 + /* Sleep expects milliseconds; convert nanoseconds (round down). */ + Sleep((DWORD)(POLL_SLEEP_NS / 1000000)); +#else + nanosleep(&poll_sleep, NULL); +#endif /* _WIN32 */ + + } while (true); + +out: + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Probe for FIDO2/U2F devices and choose one based on the provided application + * and key handle. If application or key handle are NULL, the user will be + * prompted to touch the key they want to use. + * + * @param application The application identifier (relying party ID), can be NULL + * @param key_handle The key handle to look for, can be NULL + * @param key_handle_len The length of the key handle in bytes + * @param probe_resident Whether to probe for resident keys + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device *sk_device_probe(const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + bool probe_resident) +{ + int rc; + size_t num_devices = 0; + struct sk_device *device = NULL; + fido_dev_info_t *device_list = NULL; + +#ifdef _WIN32 + if (!probe_resident) { + device = sk_device_open("windows://hello"); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open Windows Hello device"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using Windows Hello device"); + return device; + } +#endif /* _WIN32 */ + + device_list = fido_dev_info_new(MAX_FIDO_DEVICES); + if (device_list == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create device info list"); + return NULL; + } + + rc = fido_dev_info_manifest(device_list, MAX_FIDO_DEVICES, &num_devices); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device info manifest: %s", + fido_strerr(rc)); + goto out; + } + if (num_devices == 0) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices found"); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) detected", num_devices); + + /* + * If key_handle and application are specified, then we find the key which + * has the corresponding credentials, otherwise, we rely on the user to + * touch the key that they want to use. + */ + if (application != NULL && key_handle != NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by credential"); + device = sk_device_select_by_credential(device_list, + num_devices, + application, + key_handle, + key_handle_len); + } else { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by touch"); + device = sk_device_select_by_touch(device_list, num_devices); + } + +out: + fido_dev_info_free(&device_list, MAX_FIDO_DEVICES); + return device; +} + +/** + * Export an ECDSA public key from a FIDO2/U2F credential. + * + * The format returned by libfido2 is different from the expected SEC1 octet + * string representation, so this function performs the necessary conversion. + * + * @param credential The FIDO2/U2F credential containing the ECDSA public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ecdsa(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2/U2F credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ECDSA_P256_PUBKEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2/U2F credential public key length %zu" + "(expected ecdsa public key length %d)", + len, + ECDSA_P256_PUBKEY_LEN); + return SSH_ERROR; + } + + /* + * Convert from libfido2's raw coordinate format to SEC1 octet string + * format. + * + * libfido2 returns: x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + * + * SEC1 format expects: 0x04 + x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + */ + response->public_key_len = 1 + ECDSA_P256_PUBKEY_LEN; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + /* SEC1 uncompressed point format: 0x04 prefix + raw coordinates */ + response->public_key[0] = 0x04; + memcpy(response->public_key + 1, ptr, ECDSA_P256_PUBKEY_LEN); + + return SSH_OK; +} + +/** + * Export an Ed25519 public key from a FIDO2 credential. + * + * @param credential The FIDO2 credential containing the Ed25519 public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ed25519(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2 credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2 credential public key length %zu" + " (expected ed25519 public key length %d)", + len, + ED25519_KEY_LEN); + return SSH_ERROR; + } + + response->public_key_len = len; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + memcpy(response->public_key, ptr, len); + return SSH_OK; +} + +/** + * Export a public key from a FIDO2/U2F credential based on the specified + * algorithm. + * + * @param algorithm The key algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key(int algorithm, + const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_public_key_ecdsa(credential, response); + break; + case SSH_SK_ED25519: + ret = export_public_key_ed25519(credential, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +/** + * Parse DER length encoding. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param length Pointer to store the parsed length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +parse_der_length(const uint8_t **p, const uint8_t *end, size_t *length) +{ + int len_bytes = 0; + + if (*p >= end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for DER length"); + return SSH_ERROR; + } + + /* If the MSB is set, it indicates a long form length + * where the lower 7 bits indicate the number of + * subsequent bytes that represent the length. + * + * If the MSB is not set, it indicates a short form + * length where the length is directly represented + * in the the byte itself. + */ + if (**p & 0x80) { + /* Long form length */ + len_bytes = **p & 0x7f; + (*p)++; + + if (len_bytes > DER_MAX_LEN_BYTES) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid DER length bytes: %d. Should not be greater than %d", + len_bytes, + DER_MAX_LEN_BYTES); + return SSH_ERROR; + } + + if (*p + len_bytes > end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for length bytes"); + return SSH_ERROR; + } + + *length = 0; + while (len_bytes--) { + *length = (*length << 8) | **p; + (*p)++; + } + } else { + /* Short form length */ + *length = **p; + (*p)++; + } + + return SSH_OK; +} + +/** + * Parse a single DER-encoded INTEGER. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param component_name Name of the component for error messages + * @param int_ptr Pointer to store the integer data + * @param int_len Pointer to store the integer length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_der_integer(const uint8_t **p, + const uint8_t *end, + const char *component_name, + uint8_t **int_ptr, + size_t *int_len) +{ + size_t length = 0; + const uint8_t *data_ptr = NULL; + int rc = SSH_ERROR; + + if (int_ptr == NULL || int_len == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid arguments provided for %s component", + component_name); + return SSH_ERROR; + } + + *int_ptr = NULL; + *int_len = 0; + + /* Check for INTEGER tag */ + if (*p >= end || **p != DER_INTEGER_TAG) { + SSH_LOG(SSH_LOG_WARN, + "Expected INTEGER tag for %s component", + component_name); + return SSH_ERROR; + } + (*p)++; + + /* Parse length */ + rc = parse_der_length(p, end, &length); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid %s component length", component_name); + return SSH_ERROR; + } + + /* Verify we have enough data */ + if (*p + length > end) { + SSH_LOG(SSH_LOG_WARN, + "%s component extends beyond signature", + component_name); + return SSH_ERROR; + } + + /* Skip leading zero if present (The leading zero is placed when the MSB of + * the actual number is 1, so that it is not confused as a negative number + * in 2's complement) */ + data_ptr = *p; + if (length > 0 && **p == 0x00) { + data_ptr++; + length--; + } + + /* Allocate memory for the integer data */ + if (length > 0) { + *int_ptr = calloc(length, sizeof(uint8_t)); + if (*int_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for %s component", + component_name); + return SSH_ERROR; + } + memcpy(*int_ptr, data_ptr, length); + } + + *int_len = length; + *p = data_ptr + length; + + return SSH_OK; +} + +/** + * Parse DER-encoded ECDSA signature and extract r and s components. + * + * @param der_sig DER-encoded signature data + * @param der_len Length of DER data + * @param r_ptr Pointer to store r component + * @param r_len Pointer to store r length + * @param s_ptr Pointer to store s component + * @param s_len Pointer to store s length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_ecdsa_der_signature(const uint8_t *der_sig, + size_t der_len, + uint8_t **r_ptr, + size_t *r_len, + uint8_t **s_ptr, + size_t *s_len) +{ + const uint8_t *p = der_sig; + const uint8_t *end = der_sig + der_len; + size_t seq_len = 0; + int rc; + + if (r_ptr == NULL || r_len == NULL || s_ptr == NULL || s_len == NULL || + der_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid arguments provided"); + return SSH_ERROR; + } + + *r_ptr = NULL; + *r_len = 0; + *s_ptr = NULL; + *s_len = 0; + + /* Parse SEQUENCE tag */ + if (p >= end || *(p++) != DER_SEQUENCE_TAG) { + SSH_LOG(SSH_LOG_WARN, "Expected SEQUENCE tag in DER signature"); + return SSH_ERROR; + } + + /* Parse sequence length */ + rc = parse_der_length(&p, end, &seq_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid DER sequence length"); + return SSH_ERROR; + } + + /* Verify sequence length matches remaining data */ + if (p + seq_len != end) { + SSH_LOG(SSH_LOG_WARN, "DER sequence length mismatch"); + return SSH_ERROR; + } + + /* Parse first INTEGER (r component) */ + rc = parse_der_integer(&p, end, "r", r_ptr, r_len); + if (rc != SSH_OK) { + goto error; + } + + /* Parse second INTEGER (s component) */ + rc = parse_der_integer(&p, end, "s", s_ptr, s_len); + if (rc != SSH_OK) { + goto error; + } + + /* Verify we consumed all data */ + if (p != end) { + SSH_LOG(SSH_LOG_WARN, "Unexpected data after s component"); + goto error; + } + + return SSH_OK; + +error: + SAFE_FREE(*r_ptr); + *r_len = 0; + SAFE_FREE(*s_ptr); + *s_len = 0; + return SSH_ERROR; +} + +/** + * Export an ECDSA signature from a FIDO2/U2F assertion. + * + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ecdsa(fido_assert_t *assert, + struct sk_sign_response *response) +{ + size_t len = 0; + const uint8_t *ptr = NULL; + int rc; + + len = fido_assert_sig_len(assert, 0); + ptr = fido_assert_sig_ptr(assert, 0); + + if (ptr == NULL || len == 0) { + SSH_LOG(SSH_LOG_WARN, + "Invalid signature data from FIDO2/U2F assertion"); + return SSH_ERROR; + } + + /* This will allocate and populate response->sig_r/_s (+ lengths) */ + rc = parse_ecdsa_der_signature(ptr, + len, + &response->sig_r, + &response->sig_r_len, + &response->sig_s, + &response->sig_s_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to parse DER ECDSA signature"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * Export an Ed25519 signature from a FIDO2 assertion. + * + * @param assert The FIDO2 assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ed25519(fido_assert_t *assert, + struct sk_sign_response *response) +{ + const uint8_t *ptr = NULL; + size_t len; + + ptr = fido_assert_sig_ptr(assert, 0); + len = fido_assert_sig_len(assert, 0); + if (len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Bad ED25519 signature length %zu", len); + return SSH_ERROR; + } + + response->sig_r_len = len; + response->sig_r = calloc(response->sig_r_len, sizeof(uint8_t)); + if (response->sig_r == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + return SSH_ERROR; + } + + memcpy(response->sig_r, ptr, len); + response->sig_s = NULL; + response->sig_s_len = 0; + return SSH_OK; +} + +/** + * Export a signature from a FIDO2/U2F assertion based on the specified + * algorithm. + * + * @param algorithm The signature algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature(int algorithm, + fido_assert_t *assert, + struct sk_sign_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_signature_ecdsa(assert, response); + break; + case SSH_SK_ED25519: + ret = export_signature_ed25519(assert, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +static uint32_t ssh_sk_usbhid_api_version(void) +{ + return SK_USBHID_API_VERSION; +} + +/** + * Create and configure a new FIDO2/U2F credential for enrollment. + * + * @param device The FIDO2/U2F device to use + * @param alg The algorithm to use (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param challenge The challenge data + * @param challenge_len The length of the challenge data + * @param application The application identifier (relying party ID) + * @param flags The enrollment flags + * @param pin The PIN for the device (can be NULL) + * @param user_id The binary user ID buffer (can be NULL) + * @param user_id_len Length of user_id buffer in bytes (0 if none) + * @param credential_ptr Pointer to store the created credential + * + * @return FIDO_OK on success, FIDO_ERR_* codes on failure + */ +static int create_new_fido_credential(struct sk_device *device, + uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + const uint8_t *user_id, + size_t user_id_len, + fido_cred_t **credential_ptr) +{ + int ret = FIDO_ERR_INTERNAL; + int cose_algorithm, cred_protection; + bool cred_prot_support = false; + fido_opt_t set_resident_key = FIDO_OPT_OMIT; + fido_cred_t *credential = NULL; + + /* Set the COSE algorithm based on the requested algorithm */ + switch (alg) { + case SSH_SK_ECDSA: + cose_algorithm = COSE_ES256; + break; + case SSH_SK_ED25519: + cose_algorithm = COSE_EDDSA; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + return FIDO_ERR_UNSUPPORTED_ALGORITHM; + } + + credential = fido_cred_new(); + if (credential == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + goto error; + } + + ret = fido_cred_set_type(credential, cose_algorithm); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential type: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_clientdata(credential, challenge, challenge_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & SSH_SK_RESIDENT_KEY) { + set_resident_key = FIDO_OPT_TRUE; + } + ret = fido_cred_set_rk(credential, set_resident_key); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set resident key option: %s", + fido_strerr(ret)); + goto error; + } + + /* TODO: Add an additional option to set display_name, icon ..etc */ + ret = + fido_cred_set_user(credential, user_id, user_id_len, NULL, NULL, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user information: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_rp(credential, application, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & (SSH_SK_USER_VERIFICATION_REQD | SSH_SK_RESIDENT_KEY)) { + cred_prot_support = fido_dev_supports_cred_prot(device->fido_device); + if (!cred_prot_support) { + SSH_LOG(SSH_LOG_WARN, + "Device does not support credential protection"); + ret = FIDO_ERR_UNSUPPORTED_EXTENSION; + goto error; + } + + if (flags & SSH_SK_USER_VERIFICATION_REQD) { + cred_protection = FIDO_CRED_PROT_UV_REQUIRED; + } else { + cred_protection = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID; + } + + ret = fido_cred_set_prot(credential, cred_protection); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential protection: %s", + fido_strerr(ret)); + goto error; + } + } + + ret = fido_dev_make_cred(device->fido_device, credential, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to make credential: %s", + fido_strerr(ret)); + goto error; + } + + *credential_ptr = credential; + return FIDO_OK; + +error: + fido_cred_free(&credential); + return ret; +} + +/** + * Construct an enrollment response from a FIDO2/U2F credential. + * This function extracts and copies all necessary data from the fido_cred_t + * into the response structure. + * + * @param alg The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the enrollment data + * @param flags The enrollment flags + * @param response_ptr Pointer to store the constructed enrollment response + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +fido_cred_export_sk_enroll_response(uint32_t alg, + const fido_cred_t *credential, + uint8_t flags, + struct sk_enroll_response **response_ptr) +{ + const uint8_t *ptr = NULL; + const char *fmt = NULL; + struct sk_enroll_response *response = NULL; + int rc; + + response = calloc(1, sizeof(struct sk_enroll_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for enroll response"); + return SSH_ERROR; + } + + response->flags = flags; + + /* Export public key */ + rc = export_public_key(alg, credential, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto error; + } + + /* Export the key handle */ + ptr = fido_cred_id_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get key handle"); + goto error; + } + + response->key_handle_len = fido_cred_id_len(credential); + response->key_handle = calloc(response->key_handle_len, sizeof(uint8_t)); + if (response->key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(response->key_handle, ptr, response->key_handle_len); + + /* Export challenge signature */ + fmt = fido_cred_fmt(credential); + if (fmt == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto error; + } + + ptr = fido_cred_sig_ptr(credential); + if (ptr != NULL) { + response->signature_len = fido_cred_sig_len(credential); + response->signature = calloc(response->signature_len, sizeof(uint8_t)); + if (response->signature == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + goto error; + } + memcpy(response->signature, ptr, response->signature_len); + } else if (strcmp(fmt, "none") == 0) { + /* No signature for "none" attestation format */ + response->signature = NULL; + response->signature_len = 0; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to get signature"); + goto error; + } + + /* Export attestation information if available */ + ptr = fido_cred_x5c_ptr(credential); + if (ptr != NULL) { + response->attestation_cert_len = fido_cred_x5c_len(credential); + response->attestation_cert = + calloc(response->attestation_cert_len, sizeof(uint8_t)); + if (response->attestation_cert == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for attestation cert"); + goto error; + } + memcpy(response->attestation_cert, ptr, response->attestation_cert_len); + } + + /* Export authdata */ + ptr = fido_cred_authdata_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get authdata"); + goto error; + } + + response->authdata_len = fido_cred_authdata_len(credential); + response->authdata = calloc(response->authdata_len, sizeof(uint8_t)); + if (response->authdata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for authdata"); + goto error; + } + memcpy(response->authdata, ptr, response->authdata_len); + + *response_ptr = response; + response = NULL; + return SSH_OK; + +error: + sk_enroll_response_free(response); + return SSH_ERROR; +} + +static int ssh_sk_usbhid_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + const uint8_t *ptr = NULL; + const char *attestation_format = NULL; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, + SSH_SK_OPTION_NAME_USER_ID, + NULL}; + char **option_values = NULL; + const char *device_path = NULL; + uint8_t user_id[SK_MAX_USER_ID_LEN] = {0}; + size_t user_id_len = 0; + size_t j; + + struct sk_device *device = NULL; + struct sk_enroll_response *response = NULL; + + fido_cred_t *credential = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "enroll_response cannot be NULL"); + goto out; + } + *enroll_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (challenge == NULL || challenge_len == 0) { + SSH_LOG(SSH_LOG_WARN, "challenge cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; /* device path is first in the array */ + + /* + * The user id is actually binary data according to the FIDO2 + * specification, but since we want to remain compatible with OpenSSH + * sk-api, so we are restricted to only obtain the user_id as a char * + * from the sk_option struct. + */ + if (option_values[1] != NULL) { + user_id_len = strlen(option_values[1]); + + if (user_id_len > SK_MAX_USER_ID_LEN) { + SSH_LOG(SSH_LOG_WARN, + "user_id length exceeds maximum of %d characters", + SK_MAX_USER_ID_LEN); + goto out; + } + + memcpy((char *)user_id, option_values[1], user_id_len); + } + } + + sk_fido_init(); + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using FIDO2/U2F device: %s", device->path); + + /* + * Check whether a resident key with same user_id exists to avoid + * overwriting, unless operation is marked as forceful. + */ + if ((flags & SSH_SK_RESIDENT_KEY) != 0 && + (flags & SSH_SK_FORCE_OPERATION) == 0) { + + rc = sk_device_check_resident_key(device, + application, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + pin); + if (rc == FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, "Resident key already exists"); + ret = SSH_SK_ERR_CREDENTIAL_EXISTS; + goto out; + } else if (rc != FIDO_ERR_NO_CREDENTIALS) { + SSH_LOG(SSH_LOG_WARN, + "Failed to check for resident key: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + /* Create and configure the FIDO2/U2F credential */ + ret = create_new_fido_credential(device, + alg, + challenge, + challenge_len, + application, + flags, + pin, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + &credential); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + ret = fido_err_to_ssh_sk_err(ret); + goto out; + } + + ptr = fido_cred_x5c_ptr(credential); + attestation_format = fido_cred_fmt(credential); + + if (attestation_format == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto out; + } + + rc = strcmp(attestation_format, "none"); + + /* + * If the x509 certificate is available, we can assume attestation type to + * be Basic Attestation and verify the attestation using the + * fido_cred_verify function, which checks the attestation signature using + * the attestation key mentioned in the x509 certificate. + * + * If the x509 certificate is not available, we check the attestation format + * to see whether it's type is Self attestation or None. If it + * is Self Attestation, we use fido_cred_verify_self to verify the + * credential, which checks the attestation signature against the public key + * of the credential itself. + * + * For more details, refer: + * https://developers.yubico.com/libfido2/Manuals/fido_cred_verify.html + * https://www.w3.org/TR/webauthn-2/#sctn-attestation + */ + if (ptr != NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Basic Attestation)"); + rc = fido_cred_verify(credential); + } else if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Self attestation)"); + rc = fido_cred_verify_self(credential); + } else { + SSH_LOG(SSH_LOG_DEBUG, "No attestation data available"); + rc = FIDO_OK; + } + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to verify credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* Construct the enrollment response from the credential data */ + rc = fido_cred_export_sk_enroll_response(alg, credential, flags, &response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto out; + } + + *enroll_response = response; + response = NULL; + ret = SSH_OK; + +out: + + /* Clean up extracted values */ + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + sk_enroll_response_free(response); + sk_device_close(device); + fido_cred_free(&credential); + + return ret; +} + +static int ssh_sk_usbhid_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i; + + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_device *device = NULL; + struct sk_sign_response *response = NULL; + + bool has_internal_uv = false, is_winhello = false; + fido_opt_t user_presence = FIDO_OPT_FALSE; + fido_assert_t *assert = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "sign_response cannot be NULL"); + goto out; + } + *sign_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "data to sign cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + if (key_handle == NULL || key_handle_len == 0) { + SSH_LOG(SSH_LOG_WARN, "key_handle cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + sk_fido_init(); + + /* + * We directly open the device if path is given. + * + * Otherwise, If PIN supplied or UV required, we avoid credential probing + * across multiple devices (which could trigger multiple UV prompts). + * Instead, we select by user touch first. + * + * For presence-only (UP) cases, credential-based probing is silent (see the + * comment in the sk_device_check_key_handle function about pre-flight + * checking), so we keep it to reduce touches. + */ + if (device_path != NULL) { + device = sk_device_open(device_path); + } else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD)) { + /* Touch based selection */ + device = sk_device_probe(NULL, NULL, 0, false); + } else { + /* Credential based selection */ + device = + sk_device_probe(application, key_handle, key_handle_len, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F assertion"); + goto out; + } + + rc = fido_assert_set_clientdata(assert, data, data_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to set client data: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_set_rp(assert, application); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + user_presence = + (flags & SSH_SK_USER_PRESENCE_REQD) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + rc = fido_assert_set_up(assert, user_presence); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* + * WinHello always requests the pin, unless we explicitly specify that we + * don't expect user verification. + */ + is_winhello = fido_dev_is_winhello(device->fido_device); + if (pin == NULL && is_winhello) { + rc = fido_assert_set_uv(assert, FIDO_OPT_FALSE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + } + } + + /* + * pin can be NULL if device internally has user verification capabilities + * such as biometric. + */ + if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD)) { + has_internal_uv = fido_dev_has_uv(device->fido_device); + if (!has_internal_uv) { + SSH_LOG(SSH_LOG_WARN, + "User Verification requirement cannot be satisfied as " + "device lacks internal user verification and PIN is also " + "not provided"); + ret = SSH_SK_ERR_PIN_REQUIRED; + goto out; + } + + rc = fido_assert_set_uv(assert, FIDO_OPT_TRUE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + rc = fido_dev_get_assert(device->fido_device, assert, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to get assertion: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + response = calloc(1, sizeof(struct sk_sign_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate sign response"); + goto out; + } + + response->flags = fido_assert_flags(assert, 0); + response->counter = fido_assert_sigcount(assert, 0); + + rc = export_signature(alg, assert, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export signature"); + goto out; + } + + *sign_response = response; + response = NULL; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (i = 0; supported_options[i] != NULL; i++) { + SAFE_FREE(option_values[i]); + } + SAFE_FREE(option_values); + } + + fido_assert_free(&assert); + sk_device_close(device); + sk_sign_response_free(response); + + return ret; +} + +/** + * Export a single resident credential into an allocated sk_resident_key. + */ +static int fido_cred_export_sk_resident_key(const fido_cred_t *credential, + const char *relying_party_id, + bool has_internal_uv, + struct sk_resident_key **out_key) +{ + struct sk_resident_key *resident_key = NULL; + const uint8_t *ptr = NULL; + size_t len; + int algorithm; + int rc; + + resident_key = calloc(1, sizeof(struct sk_resident_key)); + if (resident_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for resident key"); + goto error; + } + + /* application */ + resident_key->application = strdup(relying_party_id); + if (resident_key->application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for application"); + goto error; + } + + /* key handle */ + len = fido_cred_id_len(credential); + ptr = fido_cred_id_ptr(credential); + resident_key->key.key_handle_len = len; + resident_key->key.key_handle = calloc(len, sizeof(uint8_t)); + if (resident_key->key.key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(resident_key->key.key_handle, ptr, len); + + /* user id */ + len = fido_cred_user_id_len(credential); + ptr = fido_cred_user_id_ptr(credential); + resident_key->user_id_len = len; + resident_key->user_id = calloc(len, sizeof(uint8_t)); + if (resident_key->user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for user ID"); + goto error; + } + memcpy(resident_key->user_id, ptr, len); + + /* algorithm */ + algorithm = fido_cred_type(credential); + switch (algorithm) { + case COSE_ES256: + resident_key->alg = SSH_SK_ECDSA; + break; + case COSE_EDDSA: + resident_key->alg = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm %d", algorithm); + goto error; + } + + rc = fido_cred_prot(credential); + if (rc == FIDO_CRED_PROT_UV_REQUIRED && !has_internal_uv) { + resident_key->flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + rc = export_public_key(resident_key->alg, credential, &resident_key->key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to export public key for credential: %d", + rc); + goto error; + } + + *out_key = resident_key; + return SSH_OK; + +error: + SK_RESIDENT_KEY_FREE(resident_key); + return SSH_ERROR; +} + +/** + * Load resident keys from a specific security key device. + * + * @param device The security key device to load keys from + * @param pin The PIN for the device (required for loading resident keys) + * @param resident_keys_ptr Pointer to store the array of loaded resident keys + * @param num_keys_found_ptr Pointer to store the number of keys found + * + * @return SSH_SK_ERR_* error code (SSH_OK on success) + * + * @note This function only considers resident keys that belong to + * relying parties starting with "ssh:". + */ +static int +sk_device_load_resident_keys(struct sk_device *device, + const char *pin, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int ret = SSH_SK_ERR_GENERAL, rc; + bool has_internal_uv = false; + size_t i, j, keys_count, num_relying_parties; + const char *relying_party_id = NULL; + + struct sk_resident_key *cur_resident_key = NULL, **temp_ptr = NULL; + fido_credman_metadata_t *metadata = NULL; + fido_credman_rp_t *relying_parties = NULL; + fido_credman_rk_t *resident_keys = NULL; + const fido_cred_t *credential = NULL; + + has_internal_uv = fido_dev_has_uv(device->fido_device); + + metadata = fido_credman_metadata_new(); + if (metadata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2/U2F metadata"); + goto out; + } + + rc = fido_credman_get_dev_metadata(device->fido_device, metadata, pin); + if (rc != FIDO_OK) { + if (rc == FIDO_ERR_INVALID_COMMAND) { + SSH_LOG(SSH_LOG_WARN, "Device does not support resident keys"); + ret = SSH_SK_ERR_UNSUPPORTED; + } else { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device metadata: %s for device at %s", + fido_strerr(rc), + device->path); + ret = fido_err_to_ssh_sk_err(rc); + } + goto out; + } + + relying_parties = fido_credman_rp_new(); + if (relying_parties == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create relying parties list"); + goto out; + } + + rc = fido_credman_get_dev_rp(device->fido_device, relying_parties, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + num_relying_parties = fido_credman_rp_count(relying_parties); + + SSH_LOG(SSH_LOG_DEBUG, + "Device %s has key(s) for %zu relying party(ies).", + device->path, + num_relying_parties); + + /* + * Check all resident keys belonging to relying parties starting with "ssh:" + */ + for (i = 0; i < num_relying_parties; i++) { + relying_party_id = fido_credman_rp_id(relying_parties, i); + if (relying_party_id != NULL) { + rc = strncasecmp(relying_party_id, "ssh:", 4); + if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH relying party: %s", + relying_party_id); + continue; + } + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Relying party ID is NULL, skipping RP %zu", + i); + continue; + } + + fido_credman_rk_free(&resident_keys); + resident_keys = fido_credman_rk_new(); + if (resident_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2 resident key"); + goto out; + } + + rc = fido_credman_get_dev_rk(device->fido_device, + relying_party_id, + resident_keys, + pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get resident key for RP %s: %s", + relying_party_id, + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + continue; + } + + keys_count = fido_credman_rk_count(resident_keys); + if (keys_count == 0) { + SSH_LOG(SSH_LOG_INFO, + "No resident keys found for RP %s", + relying_party_id); + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Found %zu resident key(s) for RP %s", + keys_count, + relying_party_id); + + for (j = 0; j < keys_count; j++) { + credential = fido_credman_rk(resident_keys, j); + if (credential == NULL) { + SSH_LOG(SSH_LOG_INFO, "No resident key in slot %zu", j); + continue; + } + + rc = fido_cred_export_sk_resident_key(credential, + relying_party_id, + has_internal_uv, + &cur_resident_key); + if (rc != SSH_OK) { + goto out; + } + + temp_ptr = realloc(*resident_keys_ptr, + sizeof(struct sk_resident_key *) * + (*num_keys_found_ptr + 1)); + if (temp_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for resident keys list"); + goto out; + } + + *resident_keys_ptr = temp_ptr; + (*resident_keys_ptr)[*num_keys_found_ptr] = cur_resident_key; + (*num_keys_found_ptr)++; + cur_resident_key = NULL; + } + } + + ret = SSH_OK; + +out: + SK_RESIDENT_KEY_FREE(cur_resident_key); + fido_credman_rp_free(&relying_parties); + fido_credman_rk_free(&resident_keys); + fido_credman_metadata_free(&metadata); + return ret; +} + +static int +ssh_sk_usbhid_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i, j, keys_count = 0; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_resident_key **resident_keys = NULL; + struct sk_device *device = NULL; + + if (resident_keys_ptr == NULL || num_keys_found_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "resident_keys_ptr and num_keys_found_ptr cannot be NULL"); + return SSH_SK_ERR_GENERAL; + } + + /* + * To load device metadata and resident keys, a valid pin must be provided + * regardless of internal uv support. + */ + if (pin == NULL) { + SSH_LOG(SSH_LOG_WARN, "PIN cannot be NULL for loading resident keys"); + return SSH_SK_ERR_PIN_REQUIRED; + } + + *resident_keys_ptr = NULL; + *num_keys_found_ptr = 0; + + sk_fido_init(); + + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, 1); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2 device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + rc = sk_device_load_resident_keys(device, pin, &resident_keys, &keys_count); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to load resident keys: %d", rc); + ret = rc; + goto out; + } + + *resident_keys_ptr = resident_keys; + *num_keys_found_ptr = keys_count; + resident_keys = NULL; + keys_count = 0; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + + sk_device_close(device); + + for (i = 0; i < keys_count; i++) { + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + return ret; +} + +static struct ssh_sk_callbacks_struct sk_usbhid_callbacks = { + .api_version = ssh_sk_usbhid_api_version, + .enroll = ssh_sk_usbhid_enroll, + .sign = ssh_sk_usbhid_sign, + .load_resident_keys = ssh_sk_usbhid_load_resident_keys, +}; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void) +{ + ssh_callbacks_init(&sk_usbhid_callbacks); + return &sk_usbhid_callbacks; +} diff --git a/src/sntrup761.c b/src/sntrup761.c new file mode 100644 index 00000000..a67de437 --- /dev/null +++ b/src/sntrup761.c @@ -0,0 +1,524 @@ +/* + * sntrup761.c - SNTRUP761x25519 ECDH functions for key exchange + * sntrup761x25519-sha512@openssh.com - based on curve25519.c. + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 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, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sntrup761.h" +#ifdef HAVE_SNTRUP761 + +#include "libssh/bignum.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +#ifndef HAVE_LIBGCRYPT +static void crypto_random(void *ctx, size_t length, uint8_t *dst) +{ + int *err = ctx; + *err = ssh_get_random(dst, length, 1); +} +#endif /* HAVE_LIBGCRYPT */ + +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_sntrup761x25519_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_client_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static int ssh_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + if (!session->server) { +#ifdef HAVE_LIBGCRYPT + gcry_error_t err; + + err = gcry_kem_keypair(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: %s", + gpg_strerror(err)); + return SSH_ERROR; + } +#else + sntrup761_keypair(session->next_crypto->sntrup761_client_pubkey, + session->next_crypto->sntrup761_privkey, + &rc, + crypto_random); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: PRNG failure"); + return SSH_ERROR; + } +#endif /* HAVE_LIBGCRYPT */ + } + + return SSH_OK; +} + +/** @internal + * @brief Starts sntrup761x25519-sha512@openssh.com key exchange + */ +int ssh_client_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE + SNTRUP761_PUBLICKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_client_callbacks); +} + +static int ssh_sntrup761x25519_build_k(ssh_session session) +{ + unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE]; + unsigned char *k = ssk + SNTRUP761_SIZE; + unsigned char hss[SHA512_DIGEST_LEN]; + int rc; + + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 shared secret", k, CURVE25519_PUBKEY_SIZE); +#endif + +#ifdef HAVE_LIBGCRYPT + if (session->server) { + gcry_error_t err; + err = gcry_kem_encap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } else { + gcry_error_t err; + err = gcry_kem_decap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } +#else + if (session->server) { + sntrup761_enc(session->next_crypto->sntrup761_ciphertext, + ssk, + session->next_crypto->sntrup761_client_pubkey, + &rc, + crypto_random); + if (rc != 1) { + rc = SSH_ERROR; + goto cleanup; + } + } else { + sntrup761_dec(ssk, + session->next_crypto->sntrup761_ciphertext, + session->next_crypto->sntrup761_privkey); + } +#endif /* HAVE_LIBGCRYPT */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server cipher text", + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE); +#endif + + sha512(ssk, sizeof ssk, hss); + + bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + return 0; +cleanup: + ssh_burn(ssk, sizeof ssk); + ssh_burn(hss, sizeof hss); + + return rc; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_sntrup761x25519_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"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_S in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != (SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 ciphertext+key: %d", + (int)ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->sntrup761_ciphertext, + ssh_string_data(q_s_string), + SNTRUP761_CIPHERTEXT_SIZE); + memcpy(session->next_crypto->curve25519_server_pubkey, + (char *)ssh_string_data(q_s_string) + SNTRUP761_CIPHERTEXT_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_sntrup761x25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_sntrup761x25519_init, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_server_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the sntrup761x25519-sha512@openssh.com kex callbacks + */ +void ssh_server_sntrup761x25519_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init) +{ + /* ECDH/SNTRUP761 keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_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 sntrup761x25519 Q_C in packet"); + goto error; + } + if (ssh_string_len(q_c_string) != (SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 public key: %zu", + ssh_string_len(q_c_string)); + goto error; + } + + memcpy(session->next_crypto->sntrup761_client_pubkey, + ssh_string_data(q_c_string), + SNTRUP761_PUBLICKEY_SIZE); + memcpy(session->next_crypto->curve25519_client_pubkey, + ((char *)ssh_string_data(q_c_string)) + SNTRUP761_PUBLICKEY_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_c_string); + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("client public key sntrup761", + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE); + ssh_log_hexdump("client public key c25519", + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* Build server's key pair */ + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate sntrup761 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_sntrup761x25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + rc = ssh_buffer_add_u32(session->out_buffer, + ntohl(SNTRUP761_CIPHERTEXT_SIZE + + CURVE25519_PUBKEY_SIZE)); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server public key c25519", + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH_REPLY:", + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); +#endif + + 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); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_SNTRUP761 */ diff --git a/src/socket.c b/src/socket.c index 9dc4cbdd..09bc71ef 100644 --- a/src/socket.c +++ b/src/socket.c @@ -44,6 +44,9 @@ struct sockaddr_un { #include #include #include +#ifdef HAVE_PTHREAD +#include +#endif #endif /* _WIN32 */ #include "libssh/priv.h" @@ -90,6 +93,19 @@ struct ssh_socket_struct { #endif }; +#ifdef HAVE_PTHREAD +struct jump_thread_data_struct { + ssh_session session; + socket_t fd; + char *next_hostname; + uint16_t next_port; + struct ssh_jump_info_struct *next_jump; + struct ssh_jump_callbacks_struct *next_cb; +}; + +int proxy_disconnect = 0; +#endif /* HAVE_PTHREAD */ + static int sockets_initialized = 0; static ssize_t ssh_socket_unbuffered_read(ssh_socket s, @@ -100,8 +116,14 @@ static ssize_t ssh_socket_unbuffered_write(ssh_socket s, uint32_t len); /** - * \internal - * \brief inits the socket system (windows specific) + * @internal + * + * @brief Initialize socket support for libssh. + * + * Initializes the socket subsystem, calling WSAStartup() on Windows and + * ssh_poll_init() on all platforms. Can be called multiple times. + * + * @return 0 on success; -1 on Windows socket initialization failure. */ int ssh_socket_init(void) { @@ -123,7 +145,12 @@ int ssh_socket_init(void) } /** - * @brief Cleanup the socket system. + * @internal + * + * @brief Cleanup socket support for libssh. + * + * Cleans up the socket subsystem, calling ssh_poll_cleanup() on all platforms + * and WSACleanup() on Windows. Can be called multiple times. */ void ssh_socket_cleanup(void) { @@ -136,10 +163,17 @@ void ssh_socket_cleanup(void) } } - /** - * \internal - * \brief creates a new Socket object + * @internal + * + * @brief Allocate and initialize a new SSH socket structure. + * + * Creates a new ssh_socket structure associated with the given session, + * initializes input/output buffers and sets default socket state. + * + * @param[in] session The SSH session to associate with the socket. + * + * @return A new ssh_socket on success; NULL on memory allocation failure. */ ssh_socket ssh_socket_new(ssh_session session) { @@ -177,8 +211,13 @@ ssh_socket ssh_socket_new(ssh_session session) /** * @internal - * @brief Reset the state of a socket so it looks brand-new - * @param[in] s socket to rest + * + * @brief Reset the state of a socket, so it looks brand new. + * + * Clears the file descriptor, reinitializes input/output buffers, frees + * the poll handle if present, and resets all socket state flags. + * + * @param[in] s The SSH socket to reset. */ void ssh_socket_reset(ssh_socket s) { @@ -190,7 +229,10 @@ void ssh_socket_reset(ssh_socket s) s->read_wontblock = 0; s->write_wontblock = 0; s->data_except = 0; - s->poll_handle = NULL; + if (s->poll_handle != NULL) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } s->state=SSH_SOCKET_NONE; #ifndef _WIN32 s->proxy_pid = 0; @@ -204,24 +246,36 @@ void ssh_socket_reset(ssh_socket s) * @param s socket to set callbacks on. * @param callbacks a ssh_socket_callback object reference. */ - void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) { s->callbacks = callbacks; } +/** + * @internal + * + * @brief Mark an SSH socket as connected. + * + * Sets the socket state to connected and configures the poll handle + * to wait for `POLLIN` and `POLLOUT` events (needed for non-blocking connect). + * + * @param[in] s The SSH socket. + * @param[in] p The poll handle to configure, or NULL. + */ 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 */ + /* `POLLOUT` is the event to wait for in a non-blocking 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. + * @internal + * + * @brief SSH poll callback. This callback will be used when an + * event caught on the socket. * * @param p Poll object this callback belongs to. * @param fd The raw socket. @@ -300,7 +354,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, } /* Rollback the unused space */ - ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE - nread); + ssh_buffer_pass_bytes_end(s->in_buffer, + (uint32_t)(MAX_BUF_SIZE - nread)); if (nread == 0) { if (p != NULL) { @@ -325,7 +380,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, 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); + ssh_buffer_pass_bytes(s->in_buffer, (uint32_t)processed); } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); /* p may have been freed, so don't use it @@ -400,8 +455,15 @@ ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) return s->poll_handle; } -/** \internal - * \brief Deletes a socket object +/** + * @internal + * + * @brief Deletes a socket object. + * + * Closes the socket connection, frees input/output buffers and + * releases the socket structure memory. + * + * @param[in] s The SSH socket to free, or NULL. */ void ssh_socket_free(ssh_socket s) { @@ -414,6 +476,20 @@ void ssh_socket_free(ssh_socket s) SAFE_FREE(s); } +/** + * @internal + * + * @brief Connect an SSH socket to a Unix domain socket. + * + * Creates a Unix domain socket connection to the given @p path and associates + * it with the SSH socket. + * + * @param[in] s The SSH socket to connect. + * @param[in] path Path to the Unix domain socket. + * + * @return `SSH_OK` on success; `SSH_ERROR` on socket creation, connect, or fd + * setup failure. + */ int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; @@ -427,7 +503,7 @@ int ssh_socket_unix(ssh_socket s, const char *path) ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); - return -1; + return SSH_ERROR; } #ifndef _WIN32 @@ -436,7 +512,7 @@ int ssh_socket_unix(ssh_socket s, const char *path) "Error from fcntl(fd, F_SETFD, 1): %s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); CLOSE_SOCKET(fd); - return -1; + return SSH_ERROR; } #endif @@ -445,14 +521,22 @@ int ssh_socket_unix(ssh_socket s, const char *path) path, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); CLOSE_SOCKET(fd); - return -1; + return SSH_ERROR; } - ssh_socket_set_fd(s,fd); - return 0; + return ssh_socket_set_fd(s, fd); } -/** \internal - * \brief closes a socket +/** + * @internal + * + * @brief Close an SSH socket. + * + * Closes the socket file descriptor if open, saves the last error code, + * frees the poll handle if unlocked, and marks the socket state as closed. + * On Unix, attempts to terminate and wait for any running proxy command + * process. + * + * @param[in] s The SSH socket to close. */ void ssh_socket_close(ssh_socket s) { @@ -466,7 +550,7 @@ void ssh_socket_close(ssh_socket s) #endif } - if (s->poll_handle != NULL) { + if (s->poll_handle != NULL && !ssh_poll_is_locked(s->poll_handle)) { ssh_poll_free(s->poll_handle); s->poll_handle = NULL; } @@ -506,7 +590,7 @@ void ssh_socket_close(ssh_socket s) * @warning this function updates both the input and output * file descriptors */ -void ssh_socket_set_fd(ssh_socket s, socket_t fd) +int ssh_socket_set_fd(ssh_socket s, socket_t fd) { ssh_poll_handle h = NULL; @@ -518,7 +602,7 @@ void ssh_socket_set_fd(ssh_socket s, socket_t fd) s->state = SSH_SOCKET_CONNECTING; h = ssh_socket_get_poll_handle(s); if (h == NULL) { - return; + return SSH_ERROR; } /* POLLOUT is the event to wait for in a nonblocking connect */ @@ -527,26 +611,51 @@ void ssh_socket_set_fd(ssh_socket s, socket_t fd) ssh_poll_add_events(h, POLLWRNORM); #endif } + return SSH_OK; } -/** \internal - * \brief returns the input file descriptor of the socket +/** + * @internal + * + * @brief Returns the input file descriptor of a socket. + * + * @param[in] s The SSH socket. + * + * @return The socket file descriptor (socket_t). */ socket_t ssh_socket_get_fd(ssh_socket s) { return s->fd; } -/** \internal - * \brief returns nonzero if the socket is open +/** + * @internal + * + * @brief Check if an SSH socket is open. + * + * @param[in] s The SSH socket. + * + * @return Non-zero if socket is open, 0 if closed or invalid. */ int ssh_socket_is_open(ssh_socket s) { return s->fd != SSH_INVALID_SOCKET; } -/** \internal - * \brief read len bytes from socket into buffer +/** + * @internal + * + * @brief Perform an unbuffered read from an SSH socket. + * + * Reads @p len bytes from the socket file descriptor directly into @p buffer, + * using `recv()` if the descriptor is a socket, or `read()` otherwise. + * Updates internal error and state flags based on the result. + * + * @param[in] s The SSH socket. + * @param[out] buffer Buffer to read data into. + * @param[in] len Maximum number of bytes to read. + * + * @return Number of bytes read on success, or -1 on error. */ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, void *buffer, @@ -578,8 +687,21 @@ static ssize_t ssh_socket_unbuffered_read(ssh_socket s, return rc; } -/** \internal - * \brief writes len bytes from buffer to socket +/** + * @internal + * + * @brief Perform an unbuffered write to an SSH socket. + * + * Writes @p len bytes from @p buffer to the socket file descriptor, + * using `send()` if the descriptor is a socket or `write()` otherwise. + * Updates internal error and state flags, and re-enables POLLOUT + * polling if a poll handle exists. + * + * @param[in] s The SSH socket. + * @param[in] buffer Buffer containing data to write. + * @param[in] len Number of bytes to write. + * + * @return Number of bytes written on success, or -1 on error. */ static ssize_t ssh_socket_unbuffered_write(ssh_socket s, const void *buffer, @@ -620,8 +742,18 @@ static ssize_t ssh_socket_unbuffered_write(ssh_socket s, return w; } -/** \internal - * \brief returns nonzero if the current socket is in the fd_set +/** + * @internal + * + * @brief Check if SSH socket file descriptor is set in an fd_set. + * + * Tests if the socket's file descriptor is present in the + * given @p set (fd_set) . Returns 0 if the socket has no valid file descriptor. + * + * @param[in] s The SSH socket. + * @param[in] set The fd_set to test against. + * + * @return Non-zero if the socket fd is set in the fd_set, 0 otherwise. */ int ssh_socket_fd_isset(ssh_socket s, fd_set *set) { @@ -631,10 +763,17 @@ int ssh_socket_fd_isset(ssh_socket s, fd_set *set) return FD_ISSET(s->fd,set); } -/** \internal - * \brief sets the current fd in a fd_set and updates the max_fd +/** + * @internal + * + * @brief Add SSH socket file descriptor to an fd_set. + * + * Adds the socket's file descriptor to the given @p set (fd_set) + * and updates @p max_fd if this socket has the highest file descriptor number. + * @param[in] s The SSH socket. + * @param[in,out] set The fd_set to add the socket to. + * @param[in,out] max_fd the maximum fd value. */ - void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) { if (s->fd == SSH_INVALID_SOCKET) { @@ -650,10 +789,21 @@ void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) } } -/** \internal - * \brief buffered write of data - * \returns SSH_OK, or SSH_ERROR - * \warning has no effect on socket before a flush +/** + * @internal + * + * @brief Write data to an SSH socket output buffer. + * + * Adds the data to the socket's output @p buffer and calls a nonblocking + * flush attempt to send buffered data. + * + * @param[in] s The SSH socket. + * @param[in] buffer Data to write. + * @param[in] len Number of bytes to write. + * + * @return `SSH_OK` on success; `SSH_ERROR` on buffer allocation failure. + * + * @warning It has no effect on socket before a flush. */ int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) { @@ -668,10 +818,22 @@ int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) return SSH_OK; } - -/** \internal - * \brief starts a nonblocking flush of the output buffer +/** + * @internal * + * @brief Starts a nonblocking flush of the output buffer. + * + * Sends all buffered data from the socket's output buffer. + * If the socket is not open, marks the session as dead and calls an + * exception callback or sets a fatal error. If the socket cannot currently + * accept data, polls for writable events and returns `SSH_AGAIN`. + * On write errors, closes the socket and signals the error. Updates + * byte counters on successful writes. + * + * @param[in] s The SSH socket. + * + * @return `SSH_OK` if all data was sent; `SSH_AGAIN` if the operation should + * be retried later; `SSH_ERROR` on fatal socket error. */ int ssh_socket_nonblocking_flush(ssh_socket s) { @@ -730,7 +892,7 @@ int ssh_socket_nonblocking_flush(ssh_socket s) return SSH_ERROR; } - ssh_buffer_pass_bytes(s->out_buffer, bwritten); + ssh_buffer_pass_bytes(s->out_buffer, (uint32_t)bwritten); if (s->session->socket_counter != NULL) { s->session->socket_counter->out_bytes += bwritten; } @@ -751,26 +913,79 @@ int ssh_socket_nonblocking_flush(ssh_socket s) return SSH_OK; } +/** + * @internal + * + * @brief Set the SSH socket write_wontblock flag. + * + * Marks the socket as ready for nonblocking writes (`write_wontblock = 1`). + * Used by the poll system when POLLOUT becomes available. + * + * @param[in] s The SSH socket. + */ void ssh_socket_set_write_wontblock(ssh_socket s) { s->write_wontblock = 1; } +/** + * @internal + * + * @brief Set the SSH socket read_wontblock flag. + * + * Marks the socket as ready for nonblocking reads (`read_wontblock = 1`). + * Used by the poll system when POLLIN becomes available. + * + * @param[in] s The SSH socket. + */ void ssh_socket_set_read_wontblock(ssh_socket s) { s->read_wontblock = 1; } +/** + * @internal + * + * @brief Set the SSH socket exception flag. + * + * Marks the socket as having an exception condition (`data_except = 1`). + * + * @param[in] s The SSH socket. + */ void ssh_socket_set_except(ssh_socket s) { s->data_except = 1; } +/** + * @internal + * + * @brief Check if SSH socket data is available for reading. + * + * Returns true if the socket is ready for nonblocking reads + * (`read_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if data is available, 0 otherwise. + */ int ssh_socket_data_available(ssh_socket s) { return s->read_wontblock; } +/** + * @internal + * + * @brief Check if SSH socket is writable. + * + * Returns true if the socket is ready for nonblocking writes + * (`write_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if socket is writable, 0 otherwise. + */ int ssh_socket_data_writable(ssh_socket s) { return s->write_wontblock; @@ -790,7 +1005,19 @@ int ssh_socket_buffered_write_bytes(ssh_socket s) return ssh_buffer_get_len(s->out_buffer); } - +/** + * @internal + * + * @brief Get the current status of an SSH socket. + * + * Checks the input/output buffers and exception flag to determine socket + * status: `SSH_READ_PENDING` if input data available, `SSH_WRITE_PENDING` + * if output data pending, `SSH_CLOSED_ERROR` if exception occurred. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags. + */ int ssh_socket_get_status(ssh_socket s) { int r = 0; @@ -810,6 +1037,18 @@ int ssh_socket_get_status(ssh_socket s) return r; } +/** + * @internal + * + * @brief Get SSH socket poll flags from the poll handle. + * + * Checks the poll handle events and returns `SSH_READ_PENDING` if POLLIN + * is set, `SSH_WRITE_PENDING` if POLLOUT is set. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags based on poll events. + */ int ssh_socket_get_poll_flags(ssh_socket s) { int r = 0; @@ -856,8 +1095,8 @@ int ssh_socket_set_blocking(socket_t fd) * @param host hostname or ip address to connect to. * @param port port number to connect to. * @param bind_addr address to bind to, or NULL for default. - * @returns SSH_OK socket is being connected. - * @returns SSH_ERROR error while connecting to remote host. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while connecting to remote host. */ int ssh_socket_connect(ssh_socket s, const char *host, @@ -876,12 +1115,10 @@ int ssh_socket_connect(ssh_socket s, if (fd == SSH_INVALID_SOCKET) { return SSH_ERROR; } - ssh_socket_set_fd(s,fd); - - return SSH_OK; + return ssh_socket_set_fd(s, fd); } -#ifndef _WIN32 +#ifdef WITH_EXEC /** * @internal * @brief executes a command and redirect input and outputs @@ -910,7 +1147,7 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) */ shell = getenv("SHELL"); if (shell == NULL || shell[0] == '\0') { - /* Fall back to the /bin/sh only if the bash is not available. But there are + /* 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) { @@ -944,10 +1181,9 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) * This call will always be nonblocking. * @param s socket to connect. * @param command Command to execute. - * @returns SSH_OK socket is being connected. - * @returns SSH_ERROR error while executing the command. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while executing the command. */ - int ssh_socket_connect_proxycommand(ssh_socket s, const char *command) { @@ -973,8 +1209,16 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) } s->proxy_pid = pid; close(pair[0]); - SSH_LOG(SSH_LOG_DEBUG, "ProxyCommand connection pipe: [%d,%d]",pair[0],pair[1]); - ssh_socket_set_fd(s, pair[1]); + SSH_LOG(SSH_LOG_DEBUG, + "ProxyCommand connection pipe: [%d,%d]", + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + s->fd_is_socket = 0; h = ssh_socket_get_poll_handle(s); if (h == NULL) { @@ -987,6 +1231,355 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) return SSH_OK; } +#endif /* WITH_EXEC */ + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +static int +verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + + state = ssh_session_is_known_server(session); + + switch (state) { + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + default: + SSH_LOG(SSH_LOG_WARN, "Couldn't verify knownhost during proxyjump."); + return SSH_ERROR; + } + + return SSH_OK; +} + +static void free_jump_thread_data(struct jump_thread_data_struct *data) +{ + if (data == NULL) { + return; + } + + ssh_free(data->session); + SAFE_FREE(data->next_hostname); + if (data->next_jump != NULL) { + SAFE_FREE(data->next_jump->hostname); + SAFE_FREE(data->next_jump->username); + } + SAFE_FREE(data->next_jump); + SAFE_FREE(data); +} + +static void * +jump_thread_func(void *arg) +{ + struct jump_thread_data_struct *jump_thread_data = NULL; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + ssh_session jump_session = NULL; + ssh_channel caa = NULL; + int rc; + ssh_event event = NULL; + ssh_connector connector_in = NULL, connector_out = NULL; + uint16_t next_port; + char *next_hostname = NULL; + + jump_thread_data = (struct jump_thread_data_struct *)arg; + jump_session = jump_thread_data->session; + + /* First thing we need to do is to set the right level as its kept in + * thread local variable, therefore reset to 0 after spawning new thread. + */ + ssh_set_log_level(jump_session->common.log_verbosity); + + cb = jump_thread_data->next_cb; + jis = jump_thread_data->next_jump; + + /* This is the calling thread target where we will eventually initialize + * forwarding */ + next_port = jump_thread_data->next_port; + next_hostname = jump_thread_data->next_hostname; + + ssh_options_set(jump_session, SSH_OPTIONS_HOST, jis->hostname); + ssh_options_set(jump_session, SSH_OPTIONS_USER, jis->username); + ssh_options_set(jump_session, SSH_OPTIONS_PORT, &jis->port); + + if (cb != NULL && cb->before_connection != NULL) { + rc = cb->before_connection(jump_session, cb->userdata); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + } + + SSH_LOG(SSH_LOG_PACKET, + "Proxy connecting to host %s port %d user %s, callbacks=%p", + jis->hostname, + jis->port, + jis->username, + (void *)cb); + + /* If there are more jumps then this will make a new thread and call the + * current function again, until there are no jumps. When there are no jumps + * it connects normally. */ + rc = ssh_connect(jump_session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + /* Use the callback or default implementation for verifying knownhost */ + if (cb != NULL && cb->verify_knownhost != NULL) { + rc = cb->verify_knownhost(jump_session, cb->userdata); + } else { + rc = verify_knownhost(jump_session); + } + if (rc != SSH_OK) { + goto exit; + } + + /* Use the callback or publickey method to authenticate */ + if (cb != NULL && cb->authenticate != NULL) { + rc = cb->authenticate(jump_session, cb->userdata); + } else { + rc = ssh_userauth_publickey_auto(jump_session, NULL, NULL); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + caa = ssh_channel_new(jump_session); + if (caa == NULL) { + goto exit; + } + /* The origin hostname and port are set to match OpenSSH implementation + * they are only used for logging on the server */ + rc = ssh_channel_open_forward(caa, + next_hostname, + next_port, + "127.0.0.1", + 65535); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Error opening port forwarding channel: %s", + ssh_get_error(jump_session)); + goto exit; + } + + event = ssh_event_new(); + if (event == NULL) { + goto exit; + } + + connector_in = ssh_connector_new(jump_session); + if (connector_in == NULL) { + goto exit; + } + ssh_connector_set_out_channel(connector_in, caa, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, jump_thread_data->fd); + ssh_event_add_connector(event, connector_in); + + connector_out = ssh_connector_new(jump_session); + if (connector_out == NULL) { + goto exit; + } + ssh_connector_set_out_fd(connector_out, jump_thread_data->fd); + ssh_connector_set_in_channel(connector_out, caa, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + while (ssh_channel_is_open(caa)) { + if (proxy_disconnect == 1) { + break; + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, + "Error in ssh_event_dopoll() during proxy jump"); + break; + } + } + +exit: + if (connector_in != NULL) { + ssh_event_remove_connector(event, connector_in); + ssh_connector_free(connector_in); + } + if (connector_out != NULL) { + ssh_event_remove_connector(event, connector_out); + ssh_connector_free(connector_out); + } + + ssh_disconnect(jump_session); + ssh_event_free(event); + + shutdown(jump_thread_data->fd, SHUT_RDWR); + close(jump_thread_data->fd); + + free_jump_thread_data(jump_thread_data); + pthread_exit(NULL); +} + +int +ssh_socket_connect_proxyjump(ssh_socket s) +{ + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_poll_handle h = NULL; + int rc; + pthread_t jump_thread; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + struct jump_thread_data_struct *jump_thread_data = NULL; + ssh_session jump_session = NULL, session = NULL; + struct ssh_list *empty_list = NULL; + socket_t pair[2] = {SSH_INVALID_SOCKET, SSH_INVALID_SOCKET}; + + session = s->session; + + SSH_LOG(SSH_LOG_INFO, + "Connecting to host %s port %d user %s through ProxyJump", + session->opts.host, + session->opts.port, + session->opts.username); + + if (s->state != SSH_SOCKET_NONE) { + ssh_set_error( + session, + SSH_FATAL, + "ssh_socket_connect_proxyjump called on socket not unconnected"); + return SSH_ERROR; + } + + jump_thread_data = calloc(1, sizeof(struct jump_thread_data_struct)); + if (jump_thread_data == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc == -1) { + ssh_set_error(session, + SSH_FATAL, + "Creating socket pair failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + jump_session = ssh_new(); + if (jump_session == NULL) { + ssh_set_error_oom(session); + goto fail; + } + + jump_session->proxy_root = false; + /* Reset the global variable if it was previously 1 */ + if (session->proxy_root) { + proxy_disconnect = 0; + } + + /* Pop first jump that will be used by the following thread */ + jis = ssh_list_pop_head(struct ssh_jump_info_struct *, + session->opts.proxy_jumps); + if (jis == NULL) { + SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received"); + ssh_free(jump_session); + goto fail; + } + jump_thread_data->next_jump = jis; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps; + jump_session->opts.proxy_jumps = session->opts.proxy_jumps; + session->opts.proxy_jumps = empty_list; + + /* Pop the callbacks for the first jump */ + cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *, + session->opts.proxy_jumps_user_cb); + /* empty is ok */ + jump_thread_data->next_cb = cb; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps_user_cb; + jump_session->opts.proxy_jumps_user_cb = session->opts.proxy_jumps_user_cb; + session->opts.proxy_jumps_user_cb = empty_list; + + ssh_options_set(jump_session, + SSH_OPTIONS_LOG_VERBOSITY, + &session->common.log_verbosity); + + jump_thread_data->next_port = session->opts.port; + jump_thread_data->next_hostname = strdup(session->opts.host); + + jump_thread_data->fd = pair[0]; + pair[0] = SSH_INVALID_SOCKET; + jump_thread_data->session = jump_session; + /* transferred to the jump_thread_data */ + jump_session = NULL; + + SSH_LOG(SSH_LOG_INFO, + "Starting proxy thread to host %s port %d user %s, callbacks=%p", + jump_thread_data->next_jump->hostname, + jump_thread_data->next_jump->port, + jump_thread_data->next_jump->username, + (void *)jump_thread_data->next_cb); + + rc = pthread_create(&jump_thread, NULL, jump_thread_func, jump_thread_data); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Creating new thread failed: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + /* ownership passed to the thread */ + jump_thread_data = NULL; + + rc = pthread_detach(jump_thread); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to detach thread: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + SSH_LOG(SSH_LOG_DEBUG, + "ProxyJump connection thread %lu started pipe: [%d,%d]", + (unsigned long)jump_thread, + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + pair[1] = SSH_INVALID_SOCKET; + + s->fd_is_socket = 1; + 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); + } + + return SSH_OK; + +fail: + if (pair[0] != SSH_INVALID_SOCKET) { + close(pair[0]); + } + if (pair[1] != SSH_INVALID_SOCKET) { + close(pair[1]); + } + free_jump_thread_data(jump_thread_data); + return SSH_ERROR; +} + +#endif /* HAVE_PTHREAD */ #endif /* _WIN32 */ /** @} */ diff --git a/src/string.c b/src/string.c index 44403487..e78737b3 100644 --- a/src/string.c +++ b/src/string.c @@ -27,8 +27,8 @@ #include #ifndef _WIN32 -#include #include +#include #endif #include "libssh/priv.h" @@ -67,7 +67,7 @@ struct ssh_string_struct *ssh_string_new(size_t size) return NULL; } - str->size = htonl(size); + str->size = htonl((uint32_t)size); str->data[0] = 0; return str; @@ -84,15 +84,16 @@ struct ssh_string_struct *ssh_string_new(size_t size) * * @return 0 on success, < 0 on error. */ -int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { - if ((s == NULL) || (data == NULL) || - (len == 0) || (len > ssh_string_len(s))) { - return -1; - } +int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) +{ + if ((s == NULL) || (data == NULL) || (len == 0) || + (len > ssh_string_len(s))) { + return -1; + } - memcpy(s->data, data, len); + memcpy(s->data, data, len); - return 0; + return 0; } /** @@ -105,25 +106,65 @@ int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { * * @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; - size_t len; +struct ssh_string_struct *ssh_string_from_char(const char *what) +{ + struct ssh_string_struct *ptr = NULL; + size_t len; + + if (what == NULL) { + errno = EINVAL; + return NULL; + } - if(what == NULL) { - errno = EINVAL; - return NULL; - } + len = strlen(what); + + ptr = ssh_string_new(len); + if (ptr == NULL) { + return NULL; + } + + memcpy(ptr->data, what, len); + + return ptr; +} + +/** + * @brief Create a ssh string from an arbitrary data buffer. + * + * Allocates a new SSH string of length `len` and copies the provided data + * into it. If len is 0, returns an empty SSH string. When len > 0, data + * must not be NULL. + * + * @param[in] data Pointer to the data buffer to copy from. May be NULL + * only when len == 0. + * @param[in] len Length of the data buffer to copy. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_from_data(const void *data, size_t len) +{ + struct ssh_string_struct *s = NULL; + int rc; - len = strlen(what); + if (len > 0 && data == NULL) { + errno = EINVAL; + return NULL; + } - ptr = ssh_string_new(len); - if (ptr == NULL) { - return NULL; - } + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } - memcpy(ptr->data, what, len); + if (len > 0) { + rc = ssh_string_fill(s, data, len); + if (rc != 0) { + ssh_string_free(s); + return NULL; + } + } - return ptr; + return s; } /** @@ -133,7 +174,8 @@ struct ssh_string_struct *ssh_string_from_char(const char *what) { * * @return The size of the content of the string, 0 on error. */ -size_t ssh_string_len(struct ssh_string_struct *s) { +size_t ssh_string_len(struct ssh_string_struct *s) +{ size_t size; if (s == NULL) { @@ -164,7 +206,7 @@ const char *ssh_string_get_char(struct ssh_string_struct *s) } s->data[ssh_string_len(s)] = '\0'; - return (const char *) s->data; + return (const char *)s->data; } /** @@ -178,27 +220,28 @@ const char *ssh_string_get_char(struct ssh_string_struct *s) * @note If the input SSH string contains zeroes, some parts of the output * string may not be readable with regular libc functions. */ -char *ssh_string_to_char(struct ssh_string_struct *s) { - size_t len; - char *new; - - if (s == NULL) { - return NULL; - } - - len = ssh_string_len(s); - if (len + 1 < len) { - return NULL; - } - - new = malloc(len + 1); - if (new == NULL) { - return NULL; - } - memcpy(new, s->data, len); - new[len] = '\0'; - - return new; +char *ssh_string_to_char(struct ssh_string_struct *s) +{ + size_t len; + char *new = NULL; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len + 1 < len) { + return NULL; + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + memcpy(new, s->data, len); + new[len] = '\0'; + + return new; } /** @@ -206,7 +249,8 @@ char *ssh_string_to_char(struct ssh_string_struct *s) { * * @param[in] s The string to delete. */ -void ssh_string_free_char(char *s) { +void ssh_string_free_char(char *s) +{ SAFE_FREE(s); } @@ -218,27 +262,75 @@ void ssh_string_free_char(char *s) { * * @return Newly allocated copy of the string, NULL on error. */ -struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) { - struct ssh_string_struct *new; - size_t len; +struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) +{ + struct ssh_string_struct *new = NULL; + size_t len; - if (s == NULL) { - return NULL; - } + if (s == NULL) { + return NULL; + } - len = ssh_string_len(s); - if (len == 0) { - return NULL; - } + len = ssh_string_len(s); - new = ssh_string_new(len); - if (new == NULL) { - return NULL; - } + new = ssh_string_new(len); + if (new == NULL) { + return NULL; + } - memcpy(new->data, s->data, len); + memcpy(new->data, s->data, len); - return new; + return new; +} + +/** + * @brief Compare two SSH strings. + * + * @param[in] s1 The first SSH string to compare. + * @param[in] s2 The second SSH string to compare. + * + * @return 0 if the strings are equal, + * < 0 if s1 is less than s2, + * > 0 if s1 is greater than s2. + */ +int ssh_string_cmp(struct ssh_string_struct *s1, struct ssh_string_struct *s2) +{ + size_t len1, len2, min_len; + int cmp; + + /* Both are NULL */ + if (s1 == NULL && s2 == NULL) { + return 0; + } + + /* Only one is NULL - NULL is considered "less than" non-NULL */ + if (s1 == NULL) { + return -1; + } else if (s2 == NULL) { + return 1; + } + + /* Get lengths */ + len1 = ssh_string_len(s1); + len2 = ssh_string_len(s2); + min_len = MIN(len1, len2); + + /* Compare data up to the shorter length */ + if (min_len > 0) { + cmp = memcmp(s1->data, s2->data, min_len); + if (cmp != 0) { + return cmp; + } + } + + /* If common prefix is equal, compare lengths */ + if (len1 < len2) { + return -1; + } else if (len1 > len2) { + return 1; + } + + return 0; } /** @@ -246,12 +338,13 @@ struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) { * * @param[in] s The string to burn. */ -void ssh_string_burn(struct ssh_string_struct *s) { +void ssh_string_burn(struct ssh_string_struct *s) +{ if (s == NULL || s->size == 0) { return; } - explicit_bzero(s->data, ssh_string_len(s)); + ssh_burn(s->data, ssh_string_len(s)); } /** @@ -261,12 +354,13 @@ void ssh_string_burn(struct ssh_string_struct *s) { * * @return Return the data of the string or NULL on error. */ -void *ssh_string_data(struct ssh_string_struct *s) { - if (s == NULL) { - return NULL; - } +void *ssh_string_data(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } - return s->data; + return s->data; } /** @@ -274,8 +368,9 @@ void *ssh_string_data(struct ssh_string_struct *s) { * * \param[in] s The SSH string to delete. */ -void ssh_string_free(struct ssh_string_struct *s) { - SAFE_FREE(s); +void ssh_string_free(struct ssh_string_struct *s) +{ + SAFE_FREE(s); } /** @} */ diff --git a/src/threads/mbedtls.c b/src/threads/mbedtls.c index 6cc3fa53..62e1db87 100644 --- a/src/threads/mbedtls.c +++ b/src/threads/mbedtls.c @@ -52,6 +52,12 @@ int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) #elif defined MBEDTLS_THREADING_PTHREAD return SSH_OK; #else + fprintf(stderr, + "MbedTLS needs to have threading enabled with " + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT " + "in mbedtls_config.h\n"); +#warn "MbedTLS needs to have threading enabled with " \ + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT in mbedtls_config.h" return SSH_ERROR; #endif } diff --git a/src/threads/winlocks.c b/src/threads/winlocks.c index da600418..e63635e7 100644 --- a/src/threads/winlocks.c +++ b/src/threads/winlocks.c @@ -82,7 +82,7 @@ static struct ssh_threads_callbacks_struct ssh_threads_winlock = void ssh_mutex_lock(SSH_MUTEX *mutex) { - void *rc; + void *rc = NULL; CRITICAL_SECTION *mutex_tmp = NULL; diff --git a/src/token.c b/src/token.c index 6ce2b64c..3fe1745c 100644 --- a/src/token.c +++ b/src/token.c @@ -48,7 +48,7 @@ void ssh_tokens_free(struct ssh_tokens_st *tokens) if (tokens->tokens != NULL) { for (i = 0; tokens->tokens[i] != NULL; i++) { - explicit_bzero(tokens->tokens[i], strlen(tokens->tokens[i])); + ssh_burn(tokens->tokens[i], strlen(tokens->tokens[i])); } } diff --git a/src/ttyopts.c b/src/ttyopts.c new file mode 100644 index 00000000..251a988b --- /dev/null +++ b/src/ttyopts.c @@ -0,0 +1,476 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH + * Author: Daniel Evers + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#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; +#ifdef B57600 + case B57600: + return 57600; +#endif +#ifdef B115200 + case B115200: + return 115200; +#endif +#ifdef B230400 + case B230400: + return 230400; +#endif + } +} + +/** + * 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) +#ifdef IMAXBEL + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#endif +#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) +#ifdef ECHOCTL + SSH_ENCODE_LOCAL_OPT(ECHOCTL) +#endif +#ifdef ECHOKE + SSH_ENCODE_LOCAL_OPT(ECHOKE) +#endif +#ifdef PENDIN + SSH_ENCODE_LOCAL_OPT(PENDIN) +#endif +#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) +#ifdef VEOL2 + SSH_ENCODE_CC_OPT(VEOL2) +#endif + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif +#ifdef VREPRINT + SSH_ENCODE_CC_OPT(VREPRINT) +#endif +#ifdef VWERASE + SSH_ENCODE_CC_OPT(VWERASE) +#endif +#ifdef VLNEXT + SSH_ENCODE_CC_OPT(VLNEXT) +#endif +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif +#ifdef VDISCARD + SSH_ENCODE_CC_OPT(VDISCARD) +#endif +#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. + * + * The "sane" default set is derived from the `stty sane`, but iutf8 support is + * added on top of that. + * + * @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, 004) + 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, 1) + 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, 1) + 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, 1) + 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 bf949ea9..a172f901 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -38,10 +38,6 @@ #include #include -#ifdef WITH_ZLIB -#include -#endif - #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" @@ -52,8 +48,11 @@ #ifdef WITH_GEX #include "libssh/dh-gex.h" #endif /* WITH_GEX */ -#include "libssh/ecdh.h" #include "libssh/curve25519.h" +#include "libssh/kex-gss.h" +#include "libssh/ecdh.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/sntrup761.h" static struct ssh_hmac_struct ssh_hmac_tab[] = { { "hmac-sha1", SSH_HMAC_SHA1, false }, @@ -152,13 +151,12 @@ static void cipher_free(struct ssh_cipher_struct *cipher) { struct ssh_crypto_struct *crypto_new(void) { - struct ssh_crypto_struct *crypto; + struct ssh_crypto_struct *crypto = NULL; - crypto = malloc(sizeof(struct ssh_crypto_struct)); + crypto = calloc(1, sizeof(struct ssh_crypto_struct)); if (crypto == NULL) { return NULL; } - ZERO_STRUCTP(crypto); return crypto; } @@ -186,42 +184,38 @@ void crypto_free(struct ssh_crypto_struct *crypto) #endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_release(crypto->ecdh_privkey); -#endif +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair_free(crypto->ecdh_privkey); + SAFE_FREE(crypto->ecdh_privkey); +#endif /* HAVE_LIBGCRYPT */ crypto->ecdh_privkey = NULL; } +#endif +#ifdef HAVE_LIBCRYPTO + EVP_PKEY_free(crypto->curve25519_privkey); +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_release(crypto->curve25519_privkey); #endif SAFE_FREE(crypto->dh_server_signature); if (crypto->session_id != NULL) { - explicit_bzero(crypto->session_id, crypto->session_id_len); + ssh_burn(crypto->session_id, crypto->session_id_len); SAFE_FREE(crypto->session_id); } if (crypto->secret_hash != NULL) { - explicit_bzero(crypto->secret_hash, crypto->digest_len); + ssh_burn(crypto->secret_hash, crypto->digest_len); SAFE_FREE(crypto->secret_hash); } -#ifdef WITH_ZLIB - if (crypto->compress_out_ctx && - (deflateEnd(crypto->compress_out_ctx) != 0)) { - inflateEnd(crypto->compress_out_ctx); - } - SAFE_FREE(crypto->compress_out_ctx); - - if (crypto->compress_in_ctx && - (deflateEnd(crypto->compress_in_ctx) != 0)) { - inflateEnd(crypto->compress_in_ctx); - } - SAFE_FREE(crypto->compress_in_ctx); -#endif /* WITH_ZLIB */ + compress_cleanup(crypto); SAFE_FREE(crypto->encryptIV); SAFE_FREE(crypto->decryptIV); SAFE_FREE(crypto->encryptMAC); SAFE_FREE(crypto->decryptMAC); if (crypto->encryptkey != NULL) { - explicit_bzero(crypto->encryptkey, crypto->out_cipher->keysize / 8); + ssh_burn(crypto->encryptkey, crypto->out_cipher->keysize / 8); SAFE_FREE(crypto->encryptkey); } if (crypto->decryptkey != NULL) { - explicit_bzero(crypto->decryptkey, crypto->in_cipher->keysize / 8); + ssh_burn(crypto->decryptkey, crypto->in_cipher->keysize / 8); SAFE_FREE(crypto->decryptkey); } @@ -234,7 +228,23 @@ void crypto_free(struct ssh_crypto_struct *crypto) SAFE_FREE(crypto->kex_methods[i]); } - explicit_bzero(crypto, sizeof(struct ssh_crypto_struct)); +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY_free(crypto->mlkem_privkey); +#else + if (crypto->mlkem_privkey != NULL) { + ssh_burn(crypto->mlkem_privkey, crypto->mlkem_privkey_len); + SAFE_FREE(crypto->mlkem_privkey); + crypto->mlkem_privkey_len = 0; + } +#endif + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->mlkem_client_pubkey); + ssh_string_free(crypto->mlkem_ciphertext); + ssh_string_free(crypto->hybrid_client_init); + ssh_string_free(crypto->hybrid_server_reply); + ssh_string_free(crypto->hybrid_shared_secret); + + ssh_burn(crypto, sizeof(struct ssh_crypto_struct)); SAFE_FREE(crypto); } @@ -582,6 +592,14 @@ int crypt_set_algorithms_server(ssh_session session){ case SSH_KEX_DH_GROUP18_SHA512: ssh_server_dh_init(session); break; +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + ssh_server_gss_kex_init(session); + break; +#endif /* WITH_GSSAPI */ #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: case SSH_KEX_DH_GEX_SHA256: @@ -601,6 +619,19 @@ int crypt_set_algorithms_server(ssh_session session){ ssh_server_curve25519_init(session); break; #endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_server_sntrup761x25519_init(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_server_hybrid_mlkem_init(session); + break; default: ssh_set_error(session, SSH_FATAL, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d91c2211..cf1adf56 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 @@ -20,6 +18,27 @@ set(TORTURE_LINK_LIBRARIES ${CMOCKA_LIBRARY} ssh::static) +if (NOT WIN32) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + pthread) +endif(NOT WIN32) +if (WITH_GSSAPI AND GSSAPI_FOUND) + find_package(OpenSSL 1.1.1 REQUIRED) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + OpenSSL::Crypto) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +# Check for sk-dummy library if FIDO2 support is enabled +if (WITH_FIDO2) + find_file(SK_DUMMY_LIBRARY + NAMES sk-dummy.so + PATHS /usr/lib64/sshtest /usr/lib/sshtest + NO_DEFAULT_PATH + ) +endif (WITH_FIDO2) + # create test library add_library(${TORTURE_LIBRARY} STATIC @@ -27,12 +46,26 @@ add_library(${TORTURE_LIBRARY} torture.c torture_key.c torture_pki.c + torture_sk.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" ) +# Check for sk-dummy and add HAVE_SK_DUMMY definition if available +if (SK_DUMMY_LIBRARY) + add_library(sk-dummy SHARED IMPORTED) + set_target_properties(sk-dummy PROPERTIES IMPORTED_LOCATION "${SK_DUMMY_LIBRARY}") + target_link_libraries(${TORTURE_LIBRARY} PRIVATE sk-dummy) + set(HAVE_SK_DUMMY 1) + set(SK_DUMMY_LIBRARY_PATH ${SK_DUMMY_LIBRARY}) +endif() + +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 if (CLIENT_TESTING) @@ -53,6 +86,15 @@ if (CLIENT_TESTING) list(APPEND WRAP_SYMBOLS "-Wl,--wrap=crypto_scalarmult_base" "-Wl,--wrap=crypto_scalarmult") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=sntrup761_keypair" + "-Wl,--wrap=sntrup761_enc" + "-Wl,--wrap=sntrup761_dec") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_generate_key_pair" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_validate_public_key" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_encapsulate" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_decapsulate") add_library(${TORTURE_SHARED_LIBRARY} SHARED @@ -60,22 +102,32 @@ if (CLIENT_TESTING) torture.c torture_key.c torture_pki.c + torture_sk.c torture_cmocka.c ) - target_link_libraries(${TORTURE_SHARED_LIBRARY} + target_link_libraries(${TORTURE_SHARED_LIBRARY} PUBLIC ${CMOCKA_LIBRARY} ssh::static ${WRAP_SYMBOLS} ) + + # Link sk-dummy to torture_shared library if available + if (SK_DUMMY_LIBRARY) + target_link_libraries(${TORTURE_SHARED_LIBRARY} PRIVATE sk-dummy) + endif (SK_DUMMY_LIBRARY) + target_compile_options(${TORTURE_SHARED_LIBRARY} PRIVATE -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" -DTORTURE_SHARED ) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_SHARED_LIBRARY}) + endif (WITH_COVERAGE) endif () if (ARGP_LIBRARIES) target_link_libraries(${TORTURE_LIBRARY} - ${ARGP_LIBRARIES} + PUBLIC ${ARGP_LIBRARIES} ) endif() @@ -88,11 +140,21 @@ add_subdirectory(unittests) # OpenSSH Capabilities are required for all unit tests find_program(SSH_EXECUTABLE NAMES ssh) +find_program(SSH_KEYGEN_EXECUTABLE NAMES ssh-keygen) if (SSH_EXECUTABLE) + file(SIZE ${SSH_EXECUTABLE} SSH_EXECUTABLE_SIZE) execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) - string(REGEX REPLACE "^.*OpenSSH_([0-9]).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") - string(REGEX REPLACE "^.*OpenSSH_[0-9].([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") + string(REGEX REPLACE "^.*OpenSSH_([0-9]+).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") + string(REGEX REPLACE "^.*OpenSSH_[0-9]+.([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") set(OPENSSH_VERSION "${OPENSSH_VERSION_MAJOR}.${OPENSSH_VERSION_MINOR}") + add_definitions(-DOPENSSH_VERSION_MAJOR=${OPENSSH_VERSION_MAJOR} -DOPENSSH_VERSION_MINOR=${OPENSSH_VERSION_MINOR}) + if("${OPENSSH_VERSION}" VERSION_GREATER_EQUAL "8.1" AND SSH_KEYGEN_EXECUTABLE) + set(OPENSSH_SUPPORTS_SSHSIG 1) + message(STATUS "OpenSSH ${OPENSSH_VERSION} supports SSH signatures") + else() + set(OPENSSH_SUPPORTS_SSHSIG 0) + message(STATUS "OpenSSH ${OPENSSH_VERSION} does not support SSH signatures (requires 8.1+)") + endif() if("${OPENSSH_VERSION}" VERSION_LESS "6.3") # ssh - Q was introduced in 6.3 message("Version less than 6.3, hardcoding cipher list") @@ -125,12 +187,16 @@ if (SSH_EXECUTABLE) diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 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 + sntrup761x25519-sha512@openssh.com sntrup761x25519-sha512 + mlkem768x25519-sha256 mlkem768nistp256-sha256 mlkem1024nistp384-sha384 curve25519-sha256 curve25519-sha256@libssh.org 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 ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com ecdsa-sha2-nistp521-cert-v01@openssh.com + sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com + sk-ssh-ed25519-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com ) foreach(ALGORITHM ${SSH_ALGORITHMS}) string(TOUPPER ${ALGORITHM} VARNAME) @@ -152,6 +218,47 @@ if (SSH_EXECUTABLE) endif() +find_program(DROPBEAR_EXECUTABLE NAMES dbclient) +if (DROPBEAR_EXECUTABLE) + execute_process(COMMAND ${DROPBEAR_EXECUTABLE} -V ERROR_VARIABLE DROPBEAR_VERSION_STR) + string(REGEX REPLACE "^.*Dropbear v([0-9]+)\\.([0-9]+).*$" "\\1.\\2" DROPBEAR_VERSION "${DROPBEAR_VERSION_STR}") + set(DROPBEAR_VERSION "${DROPBEAR_VERSION}") + + # HMAC-SHA1 support was removed in version 2025.87 + if("${DROPBEAR_VERSION}" VERSION_LESS "2025.87") + message("Dropbear Version less than 2025.87, enabling dropbear HMAC-SHA1 tests") + add_definitions(-DDROPBEAR_SUPPORTS_HMAC_SHA1) + endif() +else() + message(STATUS "Could NOT find Dropbear (missing: dbclient executable)") + set(DROPBEAR_EXECUTABLE "/bin/false") +endif() + +find_program(PUTTY_EXECUTABLE + NAMES + plink + plink.exe + putty # Fallback for systems where plink isn't separate + DOC "Path to PuTTY plink executable for automated tests") + +if (PUTTY_EXECUTABLE) + message(STATUS "Found PuTTY client: ${PUTTY_EXECUTABLE}") +else() + set(PUTTY_EXECUTABLE "/bin/putty-not-found") +endif() + +find_program(PUTTYGEN_EXECUTABLE + NAMES + puttygen + puttygen.exe + DOC "Path to PuTTYgen executable for key conversion") + +if (PUTTYGEN_EXECUTABLE) + message(STATUS "Found PuTTY keygen: ${PUTTYGEN_EXECUTABLE}") +else() + set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found") +endif() + find_program(SSHD_EXECUTABLE NAME sshd @@ -160,6 +267,20 @@ find_program(SSHD_EXECUTABLE /usr/sbin /usr/local/sbin) +if (WITH_PKCS11_URI) + find_package(softhsm) + if (NOT SOFTHSM_FOUND) + message(SEND_ERROR "Could not find softhsm module!") + endif (NOT SOFTHSM_FOUND) + find_library(PKCS11SPY + NAMES + pkcs11-spy.so + ) + + #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) + if (CLIENT_TESTING OR SERVER_TESTING) find_package(socket_wrapper 1.1.5 REQUIRED) find_package(nss_wrapper 1.1.2 REQUIRED) @@ -178,31 +299,6 @@ if (CLIENT_TESTING OR SERVER_TESTING) /usr/bin /usr/local/bin) - if (WITH_PKCS11_URI) - find_package(softhsm) - if (NOT SOFTHSM_FOUND) - message(SEND_ERROR "Could not find softhsm module!") - endif (NOT SOFTHSM_FOUND) - 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) - if (SSH_EXECUTABLE) - execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) - string(REGEX REPLACE "^.*OpenSSH_([0-9]).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") - string(REGEX REPLACE "^.*OpenSSH_[0-9].([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") - add_definitions(-DOPENSSH_VERSION_MAJOR=${OPENSSH_VERSION_MAJOR} -DOPENSSH_VERSION_MINOR=${OPENSSH_VERSION_MINOR}) - endif() - set(LOCAL_USER "nobody") set(LOCAL_UID "65533") find_program(ID_EXECUTABLE NAMES id) @@ -236,10 +332,20 @@ if (CLIENT_TESTING OR SERVER_TESTING) set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") endif() + # fs wrapper + add_library(fs_wrapper SHARED fs_wrapper.c) + set(FS_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}fs_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + fs_wrapper + ) + set(FS_WRAPPER "${FS_WRAPPER_LIBRARY}") + # ssh_ping add_executable(ssh_ping ssh_ping.c) target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(ssh_ping ssh::static) + target_link_libraries(ssh_ping ssh::static pthread) # homedir will be used in passwd set(HOMEDIR ${CMAKE_CURRENT_BINARY_DIR}/home) @@ -255,7 +361,8 @@ 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}") + set(TORTURE_ENVIRONMENT + "LD_PRELOAD=${FS_WRAPPER}:${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_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") @@ -278,9 +385,16 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(COPY keys/id_ecdsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ed25519 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ed25519.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Security key support + file(COPY keys/id_ecdsa_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) - # Allow to auth with bob's public keys on alice account + # Allow to auth with bob's public keys on alice and doe account configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY) + configure_file(keys/id_ecdsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/frank/.ssh/authorized_keys @ONLY) # append ECDSA public key file(READ keys/id_ecdsa.pub CONTENTS) @@ -290,6 +404,14 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(READ keys/id_ed25519.pub CONTENTS) file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + # append sk-ecdsa public key + file(READ keys/id_ecdsa_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append sk-ed25519 public key + file(READ keys/id_ed25519_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + # Allow to auth with bob his public keys on charlie account configure_file(keys/pkcs11/id_pkcs11_rsa_openssh.pub ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys @ONLY) @@ -303,16 +425,19 @@ 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) + file(READ keys/pkcs11/id_pkcs11_ed25519_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + # 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) +file(COPY gss/kdcsetup.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gss FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") @@ -339,10 +464,10 @@ if (FUZZ_TESTING) endif() add_custom_target(test_memcheck - # FIXME: The threads_pki_rsa test is skipped under valgrind as it times out + # FIXME: The pkd_hello_i1 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 + COMMAND ${CMAKE_CTEST_COMMAND} -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 ca4f0006..c7b245a5 100644 --- a/tests/benchmarks/CMakeLists.txt +++ b/tests/benchmarks/CMakeLists.txt @@ -14,4 +14,4 @@ include_directories(${libssh_BINARY_DIR}) add_executable(benchmarks ${benchmarks_SRCS}) -target_link_libraries(benchmarks ssh::ssh) +target_link_libraries(benchmarks ssh::static pthread) diff --git a/tests/benchmarks/bench1.sh b/tests/benchmarks/bench1.sh index 21b7e6ec..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 version : `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 aa42689d..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 version : `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 0fd1446d..05a6ceb0 100644 --- a/tests/benchmarks/bench_raw.c +++ b/tests/benchmarks/bench_raw.c @@ -135,8 +135,13 @@ int benchmarks_raw_up (ssh_session session, struct argument_s *args, snprintf(cmd,sizeof(cmd),"%s /tmp/eater.py", PYTHON_PATH); if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) goto error; - if((err=ssh_channel_read(channel,buffer,sizeof(buffer)-1,0))==SSH_ERROR) - goto error; + err = ssh_channel_read(channel, buffer, sizeof(buffer) - 1, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } buffer[err]=0; if(!strstr(buffer,"go")){ fprintf(stderr,"parse error : %s\n",buffer); @@ -160,9 +165,13 @@ int benchmarks_raw_up (ssh_session session, struct argument_s *args, if(args->verbose>0) fprintf(stdout,"Finished upload, now waiting the ack\n"); - - if((err=ssh_channel_read(channel,buffer,5,0))==SSH_ERROR) + err = ssh_channel_read(channel, buffer, 5, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); goto error; + } buffer[err]=0; if(!strstr(buffer,"done")){ fprintf(stderr,"parse error : %s\n",buffer); @@ -272,8 +281,12 @@ int benchmarks_raw_down (ssh_session session, struct argument_s *args, if(toread > args->chunksize) toread = args->chunksize; r=ssh_channel_read(channel,buffer,toread,0); - if(r == SSH_ERROR) - goto error; + if (r == SSH_ERROR) + goto error; + if (r == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } total += r; } diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index d5766879..e6c68248 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -23,6 +23,7 @@ #include "benchmarks.h" #include #include +#include #include #include #include @@ -240,3 +241,396 @@ int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, 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 58f3ac37..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 @@ -27,42 +29,52 @@ #include #include -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 */ }; @@ -73,7 +85,7 @@ struct benchmark benchmarks[]= { const char *argp_program_version = "libssh benchmarks 2011-08-28"; const char *argp_program_bug_address = "Aris Adamantiadis "; -static char **cmdline; +static char **cmdline = NULL; /* Program documentation. */ static char doc[] = "libssh benchmarks"; @@ -81,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': @@ -210,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. */ @@ -253,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 ; ienabled){ - 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 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 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/chmodtest.c b/tests/chmodtest.c index 1e6f5112..60ac6a5b 100644 --- a/tests/chmodtest.c +++ b/tests/chmodtest.c @@ -10,7 +10,7 @@ int main(void) { char buffer[1024*1024]; int rc; - session = connect_ssh("localhost", NULL, 0); + session = connect_ssh("localhost", NULL, NULL, 0); if (session == NULL) { return 1; } diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 6bb1762a..52e8d8a5 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -4,11 +4,13 @@ find_package(socket_wrapper) set(LIBSSH_CLIENT_TESTS torture_algorithms + torture_auth + torture_auth_cert + torture_auth_agent_forwarding torture_client_callbacks torture_client_config torture_connect torture_hostkey - torture_auth torture_rekey torture_forward torture_knownhosts @@ -16,7 +18,10 @@ set(LIBSSH_CLIENT_TESTS torture_proxycommand torture_session torture_request_env - torture_client_global_requests) + torture_request_pty_modes + torture_client_global_requests + torture_get_kex_algo + ) find_program(SCP_EXECUTABLE NAMES scp) if (SCP_EXECUTABLE) @@ -31,6 +36,20 @@ if (WITH_PKCS11_URI) torture_auth_pkcs11) endif() +if (HAVE_PTHREAD) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_proxyjump) +endif() + +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_gssapi_auth + torture_gssapi_key_exchange + torture_gssapi_key_exchange_null) +endif() + if (DEFAULT_C_NO_DEPRECATION_FLAGS) set_source_files_properties(torture_knownhosts.c PROPERTIES @@ -51,7 +70,15 @@ if (WITH_SFTP) torture_sftp_read torture_sftp_fsync torture_sftp_hardlink + torture_sftp_limits torture_sftp_rename + torture_sftp_expand_path + torture_sftp_aio + torture_sftp_home_directory + torture_sftp_setstat + torture_sftp_packet_read + torture_sftp_recv_response_msg + torture_sftp_get_users_groups_by_id ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) @@ -62,7 +89,7 @@ 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) diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c index 60354f9b..06d7f7de 100644 --- a/tests/client/torture_algorithms.c +++ b/tests/client/torture_algorithms.c @@ -496,7 +496,7 @@ static void torture_algorithms_3des_cbc_hmac_sha2_512_etm(void **state) { test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512-etm@openssh.com"); } -#if defined(WITH_BLOWFISH_CIPHER) && defined(OPENSSH_BLOWFISH_CBC) +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) static void torture_algorithms_blowfish_cbc_hmac_sha1(void **state) { struct torture_state *s = *state; @@ -556,7 +556,7 @@ static void torture_algorithms_blowfish_cbc_hmac_sha2_512_etm(void **state) { test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512-etm@openssh.com"); } -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH && defined(OPENSSH_BLOWFISH_CBC) */ #ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM static void torture_algorithms_chacha20_poly1305(void **state) @@ -718,6 +718,88 @@ static void torture_algorithms_ecdh_curve25519_sha256_libssh_org(void **state) { } #endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_algorithms_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +static void torture_algorithms_ecdh_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +static void torture_algorithms_ecdh_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +static void torture_algorithms_ecdh_mlkem1024nistp384_sha384(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem1024nistp384-sha384", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* HAVE_MLKEM1024 && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ + static void torture_algorithms_dh_group1(void **state) { struct torture_state *s = *state; @@ -921,7 +1003,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512_etm, session_setup, session_teardown), -#if defined(WITH_BLOWFISH_CIPHER) && defined(OPENSSH_BLOWFISH_CBC) +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1, session_setup, session_teardown), @@ -940,7 +1022,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512_etm, session_setup, session_teardown), -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH_CIPHER && defined(OPENSSH_BLOWFISH_CBC) */ #ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305, session_setup, @@ -985,6 +1067,31 @@ int torture_run_tests(void) { session_setup, session_teardown), #endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#if defined(OPENSSH_MLKEM768X25519_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem1024nistp384_sha384, + session_setup, + session_teardown), +#endif /* defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ #if defined(HAVE_ECC) cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256, session_setup, diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 5a9bc6fa..99d59b94 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -32,8 +32,11 @@ #include #include -/* agent_is_running */ -#include "agent.c" +#include "torture_auth_common.c" + +#ifdef WITH_FIDO2 +#include "torture_sk.h" +#endif static int sshd_setup(void **state) { @@ -73,7 +76,7 @@ static int session_setup(void **state) assert_ssh_return_code(s->ssh.session, rc); /* Enable all hostkeys */ - all_keytypes = ssh_kex_get_supported_method(SSH_HOSTKEYS); + all_keytypes = ssh_get_supported_methods(SSH_HOSTKEYS); rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); assert_ssh_return_code(s->ssh.session, rc); @@ -109,11 +112,8 @@ static int pubkey_setup(void **state) static int agent_setup(void **state) { struct torture_state *s = *state; - char ssh_agent_cmd[4096]; - char ssh_agent_sock[1024]; - char ssh_agent_pidfile[1024]; - char bob_ssh_key[1024]; struct passwd *pwd; + char ssh_key_path[1024]; int rc; rc = pubkey_setup(state); @@ -124,71 +124,18 @@ static int agent_setup(void **state) pwd = getpwnam("bob"); assert_non_null(pwd); - snprintf(ssh_agent_sock, - sizeof(ssh_agent_sock), - "%s/agent.sock", - s->socket_dir); - - snprintf(ssh_agent_pidfile, - sizeof(ssh_agent_pidfile), - "%s/agent.pid", - s->socket_dir); - - /* Production ready code!!! */ - snprintf(ssh_agent_cmd, - sizeof(ssh_agent_cmd), - "eval `ssh-agent -a %s`; echo $SSH_AGENT_PID > %s", - ssh_agent_sock, ssh_agent_pidfile); - - /* run ssh-agent and ssh-add as the normal user */ - unsetenv("UID_WRAPPER_ROOT"); - - rc = system(ssh_agent_cmd); - assert_return_code(rc, errno); - - setenv("SSH_AUTH_SOCK", ssh_agent_sock, 1); - setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile, 1); - - snprintf(bob_ssh_key, - sizeof(bob_ssh_key), - "ssh-add %s/.ssh/id_rsa", - pwd->pw_dir); - - rc = system(bob_ssh_key); - assert_return_code(rc, errno); - - return 0; -} - -static int agent_cert_setup(void **state) -{ - char bob_alt_ssh_key[1024]; - struct passwd *pwd; - int rc; - - rc = agent_setup(state); + /* Use the common function to set up the SSH agent with Bob's key */ + snprintf(ssh_key_path, sizeof(ssh_key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + rc = torture_setup_ssh_agent(s, ssh_key_path); if (rc != 0) { return rc; } - pwd = getpwnam("bob"); - assert_non_null(pwd); - - /* remove all keys, load alternative key + cert */ - snprintf(bob_alt_ssh_key, - sizeof(bob_alt_ssh_key), - "ssh-add -D && ssh-add %s/.ssh_cert/id_rsa", - pwd->pw_dir); - - rc = system(bob_alt_ssh_key); - assert_return_code(rc, errno); - return 0; } static int agent_teardown(void **state) { - const char *ssh_agent_pidfile; int rc; rc = session_teardown(state); @@ -196,17 +143,11 @@ static int agent_teardown(void **state) 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"); + /* Use the common function to clean up the SSH agent */ + rc = torture_cleanup_ssh_agent(); + if (rc != 0) { + return rc; + } return 0; } @@ -323,9 +264,21 @@ static void torture_auth_pubkey(void **state) { 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); @@ -423,7 +376,7 @@ torture_auth_autopubkey_protected_auth_function (const char *prompt, char *buf, assert_int_equal(echo, 0); assert_int_equal(verify, 0); - expected_id = ssh_path_expand_escape(data->session, "%d/id_rsa_protected"); + expected_id = ssh_path_expand_escape(data->session, "%d/.ssh/id_rsa_protected"); assert_true(expected_id != NULL); rc = ssh_userauth_publickey_auto_get_current_identity(data->session, &id); @@ -476,7 +429,7 @@ static void torture_auth_autopubkey_protected(void **state) { /* Try id_rsa_protected first. */ - rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/id_rsa_protected"); + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/.ssh/id_rsa_protected"); assert_int_equal(rc, SSH_OK); rc = ssh_connect(session); @@ -526,7 +479,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; @@ -549,19 +506,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_nonblocking(void **state) { +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, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -572,9 +545,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 */ @@ -589,23 +562,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; @@ -624,11 +615,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; @@ -641,7 +650,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 */ @@ -653,79 +662,43 @@ 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_agent(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - if (!ssh_agent_is_running(session)){ - print_message("*** Agent not running. Test ignored\n"); - return; - } - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - - rc = ssh_connect(session); - assert_int_equal(rc, SSH_OK); - - rc = ssh_userauth_none(session,NULL); - /* This request should return a SSH_REQUEST_DENIED error */ - if (rc == SSH_ERROR) { - assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); - } - rc = ssh_userauth_list(session, NULL); - assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - - rc = ssh_userauth_agent(session, NULL); - assert_ssh_return_code(session, rc); +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_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); +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; + struct passwd *pwd = NULL; int rc; - int identities_only = 1; - char *id; + bool identities_only = true; + char *id = NULL; pwd = getpwnam("bob"); assert_non_null(pwd); @@ -769,7 +742,7 @@ static void torture_auth_agent_identities_only(void **state) rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); assert_int_equal(rc, SSH_OK); - /* Should succeed as key now in config */ + /* Should succeed as key now in config/options */ rc = ssh_userauth_agent(session, NULL); assert_ssh_return_code(session, rc); } @@ -781,8 +754,8 @@ static void torture_auth_agent_identities_only_protected(void **state) char bob_ssh_key[1024]; struct passwd *pwd; int rc; - int identities_only = 1; - char *id; + bool identities_only = true; + char *id = NULL; pwd = getpwnam("bob"); assert_non_null(pwd); @@ -831,109 +804,6 @@ static void torture_auth_agent_identities_only_protected(void **state) assert_ssh_return_code(session, rc); } -static void torture_auth_cert(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - ssh_key privkey = NULL; - ssh_key cert = NULL; - char bob_ssh_key[1024]; - char bob_ssh_cert[2048]; - struct passwd *pwd; - int rc; - - pwd = getpwnam("bob"); - assert_non_null(pwd); - - snprintf(bob_ssh_key, - sizeof(bob_ssh_key), - "%s/.ssh_cert/id_rsa", - pwd->pw_dir); - snprintf(bob_ssh_cert, - sizeof(bob_ssh_cert), - "%s-cert.pub", - bob_ssh_key); - - /* cert has been signed for login as alice */ - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - - rc = ssh_connect(session); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_import_cert_file(bob_ssh_cert, &cert); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_copy_cert_to_privkey(cert, privkey); - assert_int_equal(rc, SSH_OK); - - rc = ssh_userauth_try_publickey(session, NULL, cert); - assert_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_agent_cert(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); - } -#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ - - /* Setup loads a different key, tests are exactly the same. */ - torture_auth_agent(state); -} - -static void torture_auth_agent_cert_nonblocking(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); - } -#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ - - torture_auth_agent_nonblocking(state); -} - static void torture_auth_pubkey_types(void **state) { struct torture_state *s = *state; @@ -1128,7 +998,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 */ @@ -1228,6 +1098,123 @@ static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) SSH_KEY_FREE(privkey); } +#ifdef WITH_FIDO2 + +static void torture_auth_pubkey_types_sk_key(void **state, + enum ssh_keytypes_e key_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *privkey_file_name = NULL; + const char *key_type_str = NULL; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + const struct ssh_sk_callbacks_struct *sk_dummy_callbacks = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + /* Conditions to skip the test */ + sk_dummy_callbacks = torture_get_sk_dummy_callbacks(); + if (sk_dummy_callbacks == NULL) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Key type specific setup */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_file_name = "id_ecdsa_sk"; + key_type_str = "sk-ecdsa-sha2-nistp256@openssh.com"; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_file_name = "id_ed25519_sk"; + key_type_str = "sk-ssh-ed25519@openssh.com"; + break; + + default: + /* should never reach here */ + assert_true(0); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/%s", + pwd->pw_dir, + privkey_file_name); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + pki_context = ssh_pki_ctx_new(); + assert_non_null(pki_context); + + rc = ssh_pki_ctx_options_set(pki_context, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_dummy_callbacks); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_context); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use the SK key */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + key_type_str); + 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_PKI_CTX_FREE(pki_context); +} + +static void torture_auth_pubkey_types_sk_ecdsa(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_auth_pubkey_types_sk_ed25519(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ED25519); +} + +#endif /* WITH_FIDO2 */ + static void torture_auth_pubkey_rsa_key_size(void **state) { struct torture_state *s = *state; @@ -1345,6 +1332,38 @@ static void torture_auth_pubkey_rsa_key_size_nonblocking(void **state) SSH_KEY_FREE(privkey); } +static void torture_auth_pubkey_skip_none(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); + + /* Skip the ssh_userauth_none() here */ + + 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_SUCCESS); + + SSH_KEY_FREE(privkey); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -1357,16 +1376,28 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_none_max_tries, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password, + cmocka_unit_test_setup_teardown(torture_auth_password_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint, + cmocka_unit_test_setup_teardown(torture_auth_password_bad, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_good, + session_setup, + session_teardown), + 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, @@ -1396,15 +1427,6 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected, agent_setup, agent_teardown), - cmocka_unit_test_setup_teardown(torture_auth_cert, - pubkey_setup, - session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_agent_cert, - agent_cert_setup, - agent_teardown), - cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, - agent_cert_setup, - agent_teardown), cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, pubkey_setup, session_teardown), @@ -1423,12 +1445,23 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, pubkey_setup, session_teardown), +#ifdef WITH_FIDO2 + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ed25519, + pubkey_setup, + session_teardown), +#endif /* WITH_FIDO2 */ 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), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_skip_none, + pubkey_setup, + session_teardown), }; ssh_init(); diff --git a/tests/client/torture_auth_agent_forwarding.c b/tests/client/torture_auth_agent_forwarding.c new file mode 100644 index 00000000..cdde5328 --- /dev/null +++ b/tests/client/torture_auth_agent_forwarding.c @@ -0,0 +1,369 @@ +#include "config.h" + +#if !defined(_WIN32) || (defined(WITH_SERVER) && defined(HAVE_PTHREAD)) + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include /* For calloc/free */ + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include +#include +#include +#include +#include /* usleep */ + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +/* struct to store the state of the test */ +struct agent_callback_state { + int called; + ssh_session expected_session; + ssh_channel created_channel; +}; + +/* Agent callback function that will be triggered when a channel open request is + * received */ +static ssh_channel agent_callback(ssh_session session, void *userdata) +{ + struct agent_callback_state *state = + (struct agent_callback_state *)userdata; + ssh_channel channel = NULL; /* Initialize to NULL */ + + /* Increment call counter */ + state->called++; + + /* Verify session matches what we expect */ + assert_ptr_equal(session, state->expected_session); + + /* Create a new channel for agent forwarding */ + channel = ssh_channel_new(session); + if (channel == NULL) { + return NULL; + } + + /* Make the channel non-blocking */ + ssh_channel_set_blocking(channel, 0); + + /* Store the channel for verification and later cleanup */ + state->created_channel = channel; + + return channel; +} + +static int sshd_setup_agent_forwarding(void **state) +{ + int rc; + + /* Use the standard server setup function */ + torture_setup_sshd_server(state, false); + + /* Override the default configuration with our own, adding agent forwarding + * support */ + rc = torture_update_sshd_config(state, "AllowAgentForwarding yes\n"); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* Only free the session - nothing else */ +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + if (s != NULL && s->ssh.ssh.session != NULL) { + /* Clean up callback resources first */ + if (s->ssh.ssh.cb_state != NULL) { + struct agent_callback_state *cb_state = s->ssh.ssh.cb_state; + + /* Close and free any open channel from the callback */ + if (cb_state->created_channel != NULL) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + } + + free(cb_state); + s->ssh.ssh.cb_state = NULL; + } + + if (s->ssh.ssh.callbacks != NULL) { + free(s->ssh.ssh.callbacks); + s->ssh.ssh.callbacks = NULL; + } + + /* Disconnect and free the session */ + ssh_disconnect(s->ssh.ssh.session); + ssh_free(s->ssh.ssh.session); + s->ssh.ssh.session = NULL; + } + + return 0; +} + +static int torture_teardown_ssh_agent(void **state) +{ + struct torture_state *s = *state; + int rc; + + if (s == NULL) { + return 0; + } + + /* Kill the SSH agent */ + rc = torture_cleanup_ssh_agent(); + assert_return_code(rc, errno); + + /* Use the standard teardown function which will properly clean up */ + torture_teardown_sshd_server(state); + + return 0; +} + +/* Test function to verify if agent forwarding callback works */ +static void torture_auth_agent_forwarding(void **state) +{ + struct torture_state *s = *state; + struct agent_callback_state *cb_state; + ssh_session session = NULL; + ssh_channel channel = NULL; /* Initialize to NULL */ + int rc; + int port = torture_server_port(); + char buffer[4096] = {0}; + int nbytes; + int max_read_attempts = 10; /* Limit the number of read attempts */ + int read_count = 0; + bool agent_available = false; + bool agent_not_available_found = false; + size_t exp_socket_len; + + /* The forwarded agent socket is created under the home directory, which + * might easily extend the maximum unix domain socket path length. + * If we see this, just skip the test as it will not work */ + exp_socket_len = strlen(BINARYDIR) + + strlen("/home/bob/.ssh/agent.1234567890.sshd.XXXXXXXXXX"); + if (exp_socket_len > UNIX_PATH_MAX) { + SSH_LOG(SSH_LOG_WARNING, + "The working directory is too long for agent forwarding to work" + ": Skipping the test"); + skip(); + } + + assert_non_null(s); + session = s->ssh.ssh.session; + assert_non_null(session); + + /* Get our callback state */ + cb_state = (struct agent_callback_state *)s->ssh.ssh.cb_state; + assert_non_null(cb_state); + + /* Set username */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + /* Set server address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + /* Set port */ + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_ssh_return_code(session, rc); + + /* Connect to server */ + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Authenticate */ + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Create a single channel that we'll use for all tests */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request agent forwarding */ + rc = ssh_channel_request_auth_agent(channel); + assert_ssh_return_code(session, rc); + + /* Running a command that will try to use the SSH agent */ + rc = ssh_channel_request_exec( + channel, + "echo 'Simple command'; " + "echo 'ENV SSH_AUTH_SOCK=>['$SSH_AUTH_SOCK']<'; " /* Use boundary + markers */ + "ssh-add -l || echo 'Agent not available'; " + "echo 'Done'"); /* Marker for command completion */ + assert_ssh_return_code(session, rc); + + /* Set to non-blocking mode with manual timeout implementation + * This prevents the test from hanging indefinitely if there's an issue with + * the channel communication. We implement our own timeout logic using a + * counter and sleep, which gives the server time to process our request + * while still ensuring the test will eventually terminate even if no EOF is + * received. + */ + ssh_channel_set_blocking(channel, 0); + + /* Read with safety counter to prevent infinite loops */ + while (!ssh_channel_is_eof(channel) && read_count < max_read_attempts) { + nbytes = ssh_channel_read_nonblocking(channel, + buffer, + sizeof(buffer) - 1, + 0); + + if (nbytes > 0) { + buffer[nbytes] = 0; + ssh_log_hexdump("Read bytes:", (unsigned char *)buffer, nbytes); + + /* Process the command output to check for three key conditions: + * 1. If SSH_AUTH_SOCK is properly set (meaning agent forwarding + * works) + * 2. If "Agent not available" message appears (indicating failure) + * 3. If we've seen the "Done" marker (to know when to stop reading) + */ + /* Check if SSH_AUTH_SOCK has a non-empty value by looking for + * boundary markers with content between them */ + if (strstr(buffer, "ENV SSH_AUTH_SOCK=>[") != NULL && + strstr(buffer, "]<") != NULL && + strstr(buffer, "ENV SSH_AUTH_SOCK=>[]<") == NULL) { + agent_available = true; + } + + if (strstr(buffer, "Agent not available") != NULL) { + agent_not_available_found = true; + } + + if (strstr(buffer, "Done") != NULL) { + break; + } + } else if (nbytes == SSH_ERROR) { + break; + } else if (nbytes == SSH_EOF) { + break; + } + + /* Short sleep between reads to avoid spinning */ + usleep(100000); /* 100ms */ + read_count++; + } + + /* Trying to read from stderr as well */ + ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer) - 1, 1); + + /* Close the channel */ + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + /* Verify agent forwarding worked correctly */ + + /* Verify callback was called exactly once */ + assert_int_equal(cb_state->called, 1); + + /* Verify "Agent not available" was not found + * The agent should be available - we should never see "Agent not available" + * output + */ + assert_false(agent_not_available_found); + + /* Verify SSH_AUTH_SOCK is set */ + assert_true(agent_available); + + /* Any channel created in the callback is freed */ + if (cb_state->created_channel) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + cb_state->created_channel = NULL; + } +} + +/* Session setup function that configures SSH agent */ +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct agent_callback_state *cb_state = NULL; + struct ssh_callbacks_struct *callbacks = NULL; + char key_path[1024]; + struct passwd *pw = NULL; + int rc; + + /* Create a new session */ + s->ssh.ssh.session = ssh_new(); + assert_non_null(s->ssh.ssh.session); + + rc = ssh_options_set(s->ssh.ssh.session, + SSH_OPTIONS_LOG_VERBOSITY, + &verbosity); + assert_int_equal(rc, SSH_OK); + + /* Create and initialize the callback state */ + cb_state = calloc(1, sizeof(struct agent_callback_state)); + assert_non_null(cb_state); + + cb_state->expected_session = s->ssh.ssh.session; + cb_state->created_channel = NULL; + + /* Set up the callbacks */ + callbacks = calloc(1, sizeof(struct ssh_callbacks_struct)); + assert_non_null(callbacks); + + callbacks->userdata = cb_state; + callbacks->channel_open_request_auth_agent_function = agent_callback; + + ssh_callbacks_init(callbacks); + rc = ssh_set_callbacks(s->ssh.ssh.session, callbacks); + assert_int_equal(rc, SSH_OK); + + /* Store callback state and callbacks */ + s->ssh.ssh.cb_state = cb_state; + s->ssh.ssh.callbacks = callbacks; + + /* Set up SSH agent with Bob's key */ + pw = getpwnam("bob"); + assert_non_null(pw); + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pw->pw_dir); + rc = torture_setup_ssh_agent(s, key_path); + assert_return_code(rc, errno); + + return 0; +} + +/* Main test function */ +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding, + session_setup, + session_teardown), + }; + + ssh_init(); + + /* Simplify the CMocka test filter handling */ +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_message_output(CM_OUTPUT_STDOUT); +#endif + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, + sshd_setup_agent_forwarding, + torture_teardown_ssh_agent); + + ssh_finalize(); + + return rc; +} + +#endif diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c new file mode 100644 index 00000000..1d808cfe --- /dev/null +++ b/tests/client/torture_auth_cert.c @@ -0,0 +1,1011 @@ +/* + * 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 "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "torture.h" + +#include +#include +#include +#include + +#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_get_supported_methods(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; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + 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; + struct passwd *pwd; + char key_path[1024]; + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* run ssh-agent and add the key */ + rc = torture_setup_ssh_agent(s, key_path); + assert_int_equal(rc, 0); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char ssh_key_cmd[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(ssh_key_cmd, + sizeof(ssh_key_cmd), + "ssh-add -D && ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(ssh_key_cmd); + assert_return_code(rc, errno); + + return 0; +} + +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; + + rc = agent_cert_setup(state); + if (rc != 0) { + return rc; + } + + 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 int agent_teardown(void **state) +{ + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + rc = torture_cleanup_ssh_agent(); + assert_int_equal(rc, 0); + + 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; + bool identities_only = true; + 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; + bool identities_only = true; + 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 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; + bool identities_only = true; + 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; + bool identities_only = true; + 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; + bool identities_only = true; + 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; + bool identities_only = true; + 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 f0658484..d33a400b 100644 --- a/tests/client/torture_auth_pkcs11.c +++ b/tests/client/torture_auth_pkcs11.c @@ -35,12 +35,13 @@ /* agent_is_running */ #include "agent.c" -#define LIBSSH_RSA_TESTKEY "id_pkcs11_rsa" +#define LIBSSH_RSA_TESTKEY "id_pkcs11_rsa" #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 LIBSSH_ED25519_TESTKEY "id_pkcs11_ed25519" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; struct pki_st { char *temp_dir; @@ -58,17 +59,22 @@ static int setup_tokens(void **state, const char *type, const char *obj_name) cwd = test_state->temp_dir; assert_non_null(cwd); - snprintf(priv_filename, sizeof(priv_filename), "%s%s", test_state->keys_dir, type); + snprintf(priv_filename, + sizeof(priv_filename), + "%s%s", + test_state->keys_dir, + type); torture_setup_tokens(cwd, priv_filename, obj_name, "1"); return 0; } + static int session_setup(void **state) { int verbosity = torture_libssh_verbosity(); struct torture_state *s = *state; - struct passwd *pwd; + struct passwd *pwd = NULL; bool b = false; int rc; @@ -103,20 +109,21 @@ static int session_teardown(void **state) return 0; } -static int setup_session(void **state) + +static int setup_pkcs11(void **state) { struct torture_state *s = *state; struct pki_st *test_state = NULL; int rc; char keys_dir[1024] = {0}; - char *temp_dir; + char *temp_dir = NULL; test_state = malloc(sizeof(struct pki_st)); assert_non_null(test_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); @@ -125,7 +132,7 @@ static int setup_session(void **state) rc = torture_change_dir(temp_dir); assert_int_equal(rc, 0); - test_state->temp_dir = strdup(torture_get_current_working_dir()); + test_state->temp_dir = torture_get_current_working_dir(); assert_non_null(test_state->temp_dir); snprintf(keys_dir, sizeof(keys_dir), "%s/tests/keys/pkcs11/", SOURCEDIR); @@ -136,6 +143,9 @@ static int setup_session(void **state) setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); setup_tokens(state, LIBSSH_ECDSA_521_TESTKEY, "ecdsa521"); + if (!ssh_fips_mode()) { + setup_tokens(state, LIBSSH_ED25519_TESTKEY, "ed25519"); + } return 0; } @@ -144,50 +154,55 @@ static int sshd_setup(void **state) { torture_setup_sshd_server(state, true); - setup_session(state); + setup_pkcs11(state); return 0; } -static int sshd_teardown(void **state) { - +static int sshd_teardown(void **state) +{ struct torture_state *s = *state; struct pki_st *test_state = s->private_data; int rc; - torture_cleanup_tokens(test_state->temp_dir); + if (test_state != NULL) { + torture_cleanup_tokens(test_state->temp_dir); - rc = torture_change_dir(test_state->orig_dir); - assert_int_equal(rc, 0); + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); - rc = torture_rmdirs(test_state->temp_dir); - assert_int_equal(rc, 0); + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); - SAFE_FREE(test_state->temp_dir); - SAFE_FREE(test_state->orig_dir); - SAFE_FREE(test_state->keys_dir); - SAFE_FREE(test_state); + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->keys_dir); + SAFE_FREE(test_state); + } torture_teardown_sshd_server(state); return 0; } -static void torture_auth_autopubkey(void **state, const char *obj_name, const char *pin) { +static void +torture_auth_autopubkey(void **state, const char *obj_name, const char *pin) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; - int verbosity = 4; char priv_uri[1042]; + /* Authenticate as charlie with bob his pubkey */ rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_CHARLIE); assert_int_equal(rc, SSH_OK); - rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); - assert_int_equal(rc, SSH_OK); - - snprintf(priv_uri, sizeof(priv_uri), "pkcs11:token=%s;object=%s;type=private?pin-value=%s", - obj_name, obj_name, pin); + snprintf(priv_uri, + sizeof(priv_uri), + "pkcs11:token=%s;object=%s;type=private?pin-value=%s", + obj_name, + obj_name, + pin); rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); assert_int_equal(rc, SSH_OK); @@ -207,23 +222,40 @@ static void torture_auth_autopubkey(void **state, const char *obj_name, const ch assert_int_equal(rc, SSH_AUTH_SUCCESS); } -static void torture_auth_autopubkey_rsa(void **state) { +static void torture_auth_autopubkey_rsa(void **state) +{ torture_auth_autopubkey(state, "rsa", "1234"); } -static void torture_auth_autopubkey_ecdsa_key_256(void **state) { +static void torture_auth_autopubkey_ecdsa_key_256(void **state) +{ torture_auth_autopubkey(state, "ecdsa256", "1234"); } -static void torture_auth_autopubkey_ecdsa_key_384(void **state) { +static void torture_auth_autopubkey_ecdsa_key_384(void **state) +{ torture_auth_autopubkey(state, "ecdsa384", "1234"); } -static void torture_auth_autopubkey_ecdsa_key_521(void **state) { +static void torture_auth_autopubkey_ecdsa_key_521(void **state) +{ torture_auth_autopubkey(state, "ecdsa521", "1234"); } -int torture_run_tests(void) { +#ifdef WITH_PKCS11_PROVIDER +static void torture_auth_autopubkey_ed25519(void **state) +{ + /* The Ed25519 keys are not supported in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + torture_auth_autopubkey(state, "ed25519", "1234"); +} +#endif /* WITH_PKCS11_PROVIDER */ + +int torture_run_tests(void) +{ int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_auth_autopubkey_rsa, @@ -238,11 +270,21 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_521, session_setup, session_teardown), +#ifdef WITH_PKCS11_PROVIDER + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ed25519, + session_setup, + session_teardown), +#endif /* WITH_PKCS11_PROVIDER */ }; - ssh_session session = ssh_new(); - int verbosity = SSH_LOG_FUNCTIONS; - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); diff --git a/tests/client/torture_client_config.c b/tests/client/torture_client_config.c index 5ac22a4c..61b4bedb 100644 --- a/tests/client/torture_client_config.c +++ b/tests/client/torture_client_config.c @@ -6,6 +6,7 @@ #include #include "torture.h" #include "libssh/session.h" +#include "libssh/options.h" #include "libssh/misc.h" #define LIBSSH_SSH_CONFIG "libssh_config" @@ -59,6 +60,23 @@ static int setup_config_files(void **state) return 0; } +static int setup_session(void **state) +{ + struct torture_state *s = *state; + int verbosity; + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + setenv("NSS_WRAPPER_HOSTNAME", "client.libssh.site", 1); + + return 0; +} + static int teardown(void **state) { struct torture_state *s = *state; @@ -80,6 +98,16 @@ static int teardown(void **state) return 0; } +static int teardown_session(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + /* This tests makes sure that parsing both system-wide and per-user * configuration files retains OpenSSH semantics (the per-user overrides * the system-wide values). @@ -210,10 +238,233 @@ static void torture_client_config_suppress(void **state) assert_string_equal(s->ssh.session->opts.username, "bob"); } +static void torture_client_config_expand_bad(void **state) +{ + ssh_session session = ssh_new(); + int ret = 0; + + (void)state; + + assert_non_null(session); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_OK); + + ssh_free(session); +} + +static void torture_client_config_expand(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + /* TEST: user home directory */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%d"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + BINARYDIR "/tests/home"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: target host name */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%h"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, TORTURE_SSH_SERVER); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%u"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "root"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local hostname */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "client.libssh.site"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "alice"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%r"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "alice"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote port */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "2222"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%p"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "2222"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: empty proxyjump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* No proxyjump string should not explode */ + assert_string_equal(s->ssh.session->opts.knownhosts, ""); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: proxyjump string present */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + "user@" TORTURE_SSH_SERVER ":22"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYJUMP, "none"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-"); + + + /* TEST: hash of %l%h%p%r%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "133e3957ff9d01fdcf1f6c7f83325a8ce49bf850"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-user@" + TORTURE_SSH_SERVER ":22"); + + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: hash of %l%h%p%r%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "adf0b7c4e71a0fee85fd97506507ba8591f3663b"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { + /* Keep these first -- following setup is changing user to bob, which we + * do not want */ + cmocka_unit_test(torture_client_config_expand_bad), + cmocka_unit_test_setup_teardown(torture_client_config_expand, + setup_session, + teardown_session), cmocka_unit_test_setup_teardown(torture_client_config_system, setup_config_files, teardown), @@ -228,7 +479,6 @@ int torture_run_tests(void) { teardown), }; - ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); diff --git a/tests/client/torture_connect.c b/tests/client/torture_connect.c index f086488d..b34e5bfe 100644 --- a/tests/client/torture_connect.c +++ b/tests/client/torture_connect.c @@ -20,6 +20,7 @@ */ #include "config.h" +#include "torture_cmocka.h" #define LIBSSH_STATIC @@ -143,6 +144,48 @@ static void torture_connect_ipv6(void **state) { assert_ssh_return_code(session, rc); } +static void torture_connect_addrfamily(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + struct aftest { + enum ssh_address_family_options_e family; + char const *host; + int return_code; + }; + static struct aftest aftests[] = { + {SSH_ADDRESS_FAMILY_ANY, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_ANY, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afinet", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_ANY, "afinet6", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet6", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_INET6, "afinet6", SSH_OK}, + }; + + int aftest_count = sizeof(aftests) / sizeof(aftests[0]); + for (int i = 0; i < aftest_count; ++i) { + struct aftest const *t = &aftests[i]; + + rc = ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &t->family); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, t->host); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code_equal(session, rc, t->return_code); + ssh_disconnect(session); + } +} + #if 0 /* This does not work with socket_wrapper */ static void torture_connect_timeout(void **state) { struct torture_state *s = *state; @@ -258,18 +301,93 @@ static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) ssh_free(session); } +static void +internal_log(ssh_session session, + int priority, + const char *message, + void *userdata) +{ + (void)session; + (void)priority; + (void)message; + (void)userdata; + + return; +} + +static void +torture_legacy_callback(void **state) +{ + struct ssh_callbacks_struct cb[2] = {0}; + int rc, verbosity = SSH_LOG_WARNING; + ssh_session session = NULL; + + /* unused. */ + (void)state; + + /* + * Legacy code in 'ssh_set_callbacks' used to + * create the conditions for a use-after-free + * issue, in multi-session programs, by failing + * to update a pointer with the new session. + * + * To verify it won't happen again, this test + * creates two consecutive sessions and frees + * them; if any fault occurs then the pointer + * remained at the previous session, failing + * to be updated. + */ + for (int i = 0; i < 2; i++) { + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(session, rc); + + cb[i].log_function = internal_log; + + ssh_callbacks_init(&cb[i]); + ssh_set_callbacks(session, &cb[i]); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + ssh_free(session); + } +} + 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), + 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_addrfamily, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_double, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_failure, + session_setup, + session_teardown), #if 0 cmocka_unit_test_setup_teardown(torture_connect_timeout, session_setup, session_teardown), #endif - cmocka_unit_test_setup_teardown(torture_connect_socket, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_socket, + session_setup, + session_teardown), + cmocka_unit_test(torture_legacy_callback), cmocka_unit_test(torture_connect_uninitialized), }; diff --git a/tests/client/torture_get_kex_algo.c b/tests/client/torture_get_kex_algo.c new file mode 100644 index 00000000..0c44dfdb --- /dev/null +++ b/tests/client/torture_get_kex_algo.c @@ -0,0 +1,251 @@ +#include "config.h" +#include "libssh/libcrypto.h" +#include +#define LIBSSH_STATIC +#include "torture.h" +#include +#include + +#define ECDH_SHA2_NISTP256 "ecdh-sha2-nistp256" +#define CURVE25519_SHA256 "curve25519-sha256" +#define DIFFIE_HELLMAN_GROUP_14_SHA_1 "diffie-hellman-group14-sha1" +#define KEX_DH_GEX_SHA1 "diffie-hellman-group-exchange-sha1" +#define KEX_DH_GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define SNTRUP761X25519 "sntrup761x25519-sha512" +#define SNTRUP761X25519_OPENSSH "sntrup761x25519-sha512@openssh.com" +#define MLKEM768X25519 "mlkem768x25519-sha256" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_kex_basic_functionality(void **state) +{ + struct torture_state *s = *state; + ssh_session session = NULL; + const char *kex_algo = NULL; + const char *valid_algorithms[] = { + SNTRUP761X25519, + SNTRUP761X25519_OPENSSH, + MLKEM768X25519, + CURVE25519_SHA256, + ECDH_SHA2_NISTP256, + DIFFIE_HELLMAN_GROUP_14_SHA_1, + }; + size_t valid_algorithms_count, i; + int rc; + bool is_valid_algo; + + session = s->ssh.session; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + kex_algo = ssh_get_kex_algo(session); + assert_non_null(kex_algo); + + is_valid_algo = false; + valid_algorithms_count = + sizeof(valid_algorithms) / sizeof(valid_algorithms[0]); + for (i = 0; i < valid_algorithms_count; i++) { + if (strcmp(kex_algo, valid_algorithms[i]) == 0) { + is_valid_algo = true; + break; + } + } + assert_true(is_valid_algo); +} + +static void torture_kex_algo_preference(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *expected_kex = NULL; + const char *actual_kex = NULL; + int rc; + + if (ssh_fips_mode()) { + expected_kex = ECDH_SHA2_NISTP256; + } else { + expected_kex = CURVE25519_SHA256; + } + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, expected_kex); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + actual_kex = ssh_get_kex_algo(session); + assert_non_null(actual_kex); + assert_string_equal(actual_kex, expected_kex); +} + +static void torture_kex_algo_negotiation(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = + "non-existent-algo,not-supported-kex," CURVE25519_SHA256 + "," ECDH_SHA2_NISTP256 "," DIFFIE_HELLMAN_GROUP_14_SHA_1; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + assert_string_not_equal(negotiated_kex, "non-existent-algo"); + assert_string_not_equal(negotiated_kex, "not-supported-kex"); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} + +static void torture_kex_algo_before_connect(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_algo = NULL; + + kex_algo = ssh_get_kex_algo(session); + assert_null(kex_algo); +} + +#ifdef WITH_GEX +static void torture_dgex_algo(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = KEX_DH_GEX_SHA1 "," KEX_DH_GEX_SHA256; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_kex_basic_functionality, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_preference, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_negotiation, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_before_connect, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_dgex_algo, + session_setup, + session_teardown), +#endif /* WITH_GEX */ + }; + + 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_gssapi_auth.c b/tests/client/torture_gssapi_auth.c new file mode 100644 index 00000000..20f35419 --- /dev/null +++ b/tests/client/torture_gssapi_auth.c @@ -0,0 +1,276 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPICleanupCredentials yes\n" + "GSSAPIStrictAcceptorCheck yes\n"); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_gssapi_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* No client credential */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Invalid host principal */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_client_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid client identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "bob"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + + /* Valid client identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server(state); + + /* Valid server identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_delegate_creds(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server(state); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_client_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_delegate_creds, + 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_gssapi_key_exchange.c b/tests/client/torture_gssapi_key_exchange.c new file mode 100644 index 00000000..a1fcaa1d --- /dev/null +++ b/tests/client/torture_gssapi_key_exchange.c @@ -0,0 +1,295 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/crypto.h" +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_tgt(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Don't run kinit */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_kex_is_gss(session->current_crypto)); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_key_exchange_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool f = false; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_auth, + 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_gssapi_key_exchange_null.c b/tests/client/torture_gssapi_key_exchange_null.c new file mode 100644 index 00000000..c1096b3c --- /dev/null +++ b/tests/client/torture_gssapi_key_exchange_null.c @@ -0,0 +1,169 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + struct torture_state *s = NULL; + torture_setup_sshd_server(state, false); + + s = *state; + s->disable_hostkeys = true; + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + torture_teardown_kdc_server(state); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange_null(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_null_pubkey_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null_pubkey_auth, + 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_proxycommand.c b/tests/client/torture_proxycommand.c index 9b8019ca..232080eb 100644 --- a/tests/client/torture_proxycommand.c +++ b/tests/client/torture_proxycommand.c @@ -69,7 +69,9 @@ static void torture_options_set_proxycommand(void **state) char command[255] = {0}; struct stat sb; int rc; +#ifdef WITH_EXEC socket_t fd; +#endif rc = stat(NCAT_EXECUTABLE, &sb); if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { @@ -88,11 +90,15 @@ static void torture_options_set_proxycommand(void **state) rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); assert_int_equal(rc, 0); rc = ssh_connect(session); +#ifdef WITH_EXEC assert_ssh_return_code(session, rc); fd = ssh_get_fd(session); - assert_true(fd != SSH_INVALID_SOCKET); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); rc = fcntl(fd, F_GETFL); assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ } #else /* NCAT_EXECUTABLE */ @@ -124,7 +130,9 @@ static void torture_options_set_proxycommand_ssh(void **state) const char *address = torture_server_address(AF_INET); char command[255] = {0}; int rc; +#ifdef WITH_EXEC socket_t fd; +#endif rc = snprintf(command, sizeof(command), "ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", @@ -134,11 +142,15 @@ static void torture_options_set_proxycommand_ssh(void **state) rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); assert_int_equal(rc, 0); rc = ssh_connect(session); +#ifdef WITH_EXEC assert_ssh_return_code(session, rc); fd = ssh_get_fd(session); - assert_true(fd != SSH_INVALID_SOCKET); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); rc = fcntl(fd, F_GETFL); assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ } static void torture_options_set_proxycommand_ssh_stderr(void **state) @@ -148,7 +160,9 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) const char *address = torture_server_address(AF_INET); char command[255] = {0}; int rc; +#ifdef WITH_EXEC socket_t fd; +#endif /* The -vvv switches produce the desired output on the standard error */ rc = snprintf(command, sizeof(command), @@ -159,11 +173,65 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); assert_int_equal(rc, 0); rc = ssh_connect(session); +#ifdef WITH_EXEC assert_ssh_return_code(session, rc); fd = ssh_get_fd(session); - assert_true(fd != SSH_INVALID_SOCKET); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); rc = fcntl(fd, F_GETFL); assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +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) { @@ -181,6 +249,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_proxyjump.c b/tests/client/torture_proxyjump.c new file mode 100644 index 00000000..cc2d8d64 --- /dev/null +++ b/tests/client/torture_proxyjump.c @@ -0,0 +1,362 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + /* 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); + + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + 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_proxyjump_single_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + + rc = snprintf(proxyjump_buf, sizeof(proxyjump_buf), "alice@%s:22", address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)user; + + return ssh_userauth_publickey_auto(jump_session, NULL, NULL); +} + +static int authenticate_doe(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static int authenticate_frank(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_ecdsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static void torture_proxyjump_multiple_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "alice@%s:22,alice@%s:22", + address, + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_proxyjump_multiple_sshd_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + const char *address1 = torture_server1_address(AF_INET6); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,doe@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_multiple_sshd_users_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET6); + const char *address1 = torture_server1_address(AF_INET); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c1 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + struct ssh_jump_callbacks_struct c2 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_frank, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,frank@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c1); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c2); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_invalid_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doesnotexist@%s:54", + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_proxyjump_single_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_sshd_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_proxyjump_multiple_sshd_users_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_invalid_jump, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c index 0fc13b8b..6a0f439d 100644 --- a/tests/client/torture_rekey.c +++ b/tests/client/torture_rekey.c @@ -31,6 +31,7 @@ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" +#include "libssh/token.h" #include #include @@ -96,6 +97,7 @@ static int session_teardown(void **state) struct torture_state *s = *state; ssh_free(s->ssh.session); + s->ssh.session = NULL; return 0; } @@ -148,6 +150,33 @@ static void torture_rekey_default(void **state) ssh_disconnect(s->ssh.session); } +static void sanity_check_session_size(void **state, uint64_t rekey_limit) +{ + 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, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / 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); + } +} +static void sanity_check_session(void **state) +{ + sanity_check_session_size(state, bytes); +} + /* We lower the rekey limits manually and check that the rekey * really happens when sending data */ @@ -166,16 +195,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 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); + 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); @@ -258,7 +281,7 @@ static int session_setup_sftp_client(void **state) /* To trigger rekey by receiving data, the easiest thing is probably to * use sftp */ -static void torture_rekey_recv(void **state) +static void torture_rekey_recv_size(void **state, uint64_t rekey_limit) { struct torture_state *s = *state; struct ssh_crypto_struct *c = NULL; @@ -273,15 +296,10 @@ static void torture_rekey_recv(void **state) mode_t mask; int rc; - /* The blocks limit is set correctly */ + sanity_check_session_size(state, rekey_limit); + /* Copy the initial secret hash = session_id so we know we changed keys later */ 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); - /* We should 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); - /* Copy the initial secret hash = session_id so we know we changed keys later */ secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); @@ -312,8 +330,10 @@ static void torture_rekey_recv(void **state) /* 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); + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); /* Check that the secret hash is different than initially */ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); free(secret_hash); @@ -321,6 +341,11 @@ static void torture_rekey_recv(void **state) torture_sftp_close(s->ssh.tsftp); ssh_disconnect(s->ssh.session); } + +static void torture_rekey_recv(void **state) +{ + torture_rekey_recv_size(state, bytes); +} #endif /* WITH_SFTP */ /* Rekey time requires rekey after specified time and is off by default. @@ -468,15 +493,10 @@ static void torture_rekey_different_kex(void **state) 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 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); + 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); @@ -829,6 +849,86 @@ static void torture_rekey_guess_wrong_recv(void **state) torture_rekey_recv(state); } + +static void torture_rekey_guess_all_combinations(void **state) +{ + struct torture_state *s = *state; + char sshd_config[256] = ""; + char client_kex[256] = ""; + const char *supported = NULL; + struct ssh_tokens_st *s_tok = NULL; + uint64_t rekey_limit = 0; + char *p = NULL; + int rc, i, j; + + /* The rekey limit is 1/2 of the transferred file size so we will likely get + * 2 rekeys per test, which still runs for acceptable time */ + rekey_limit = atoll(SSH_EXECUTABLE_SIZE); + rekey_limit /= 2; + + if (ssh_fips_mode()) { + supported = ssh_kex_get_fips_methods(SSH_KEX); + } else { + supported = ssh_kex_get_supported_method(SSH_KEX); + } + assert_non_null(supported); + + s_tok = ssh_tokenize(supported, ','); + assert_non_null(s_tok); + for (i = 0; s_tok->tokens[i]; i++) { + /* Skip algorithms not supported by the OpenSSH server. + * Check also for prefix matches to distinguish vendor-specific names + * such as sntrup761x25519-sha512 and the @openssh.com alias */ + if ((p = strstr(OPENSSH_KEX, s_tok->tokens[i])) == NULL || + (*(p + strlen(s_tok->tokens[i])) != ',' && + *(p + strlen(s_tok->tokens[i])) != '\0')) { + SSH_LOG(SSH_LOG_INFO, "Server: %s [skipping]", s_tok->tokens[i]); + continue; + } + SSH_LOG(SSH_LOG_INFO, "Server: %s", s_tok->tokens[i]); + snprintf(sshd_config, + sizeof(sshd_config), + "KexAlgorithms %s", + s_tok->tokens[i]); + /* This sets an only supported kex algorithm that we do not have as + * a first option in the client */ + torture_update_sshd_config(state, sshd_config); + + for (j = 0; s_tok->tokens[j]; j++) { + if (i == j) { + continue; + } + + session_setup(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, + &rekey_limit); + assert_ssh_return_code(s->ssh.session, rc); + + /* Client kex preference will have the second of the pair and the + * server one as a second to negotiate on the second attempt */ + snprintf(client_kex, + sizeof(client_kex), + "%s,%s", + s_tok->tokens[j], + s_tok->tokens[i]); + SSH_LOG(SSH_LOG_INFO, "Client: %s", client_kex); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_KEY_EXCHANGE, + client_kex); + assert_ssh_return_code(s->ssh.session, rc); + session_setup_sftp(state); + torture_rekey_recv_size(state, rekey_limit); + session_teardown(state); + } + } + + ssh_tokens_free(s_tok); +} #endif /* WITH_SFTP */ int torture_run_tests(void) { @@ -898,6 +998,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, session_setup, session_teardown), + cmocka_unit_test(torture_rekey_guess_all_combinations), #endif /* WITH_SFTP */ }; diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c new file mode 100755 index 00000000..16d9b19c --- /dev/null +++ b/tests/client/torture_request_pty_modes.c @@ -0,0 +1,264 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + 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, offset = 0; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[offset + nbytes] = '\0'; + ssh_log_hexdump("Read bytes:", + (unsigned char *)buffer, + offset + nbytes); + if (strstr(buffer, expected) != NULL) + { + return 1; + } + /* read on */ + offset = nbytes; + nbytes = ssh_channel_read(c, + buffer + offset, + sizeof(buffer) - offset - 1, + 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 CRLF translated to newline */ + string_found = check_channel_output(c, ">TEST\r\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_session.c b/tests/client/torture_session.c index 37ed573a..f3d3ead2 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -97,7 +97,7 @@ static void torture_channel_read_error(void **state) { rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom"); assert_ssh_return_code(session, rc); - /* send crap and for server to send us a disconnect */ + /* send crap and wait for server to send us a disconnect */ fd = ssh_get_fd(session); assert_true(fd > 2); rc = write(fd, "AAAA", 4); @@ -321,14 +321,14 @@ static void torture_freed_channel_poll(void **state) 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) +/* 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; - bool channel_freed = false; + char request[256]; char buff[256] = {0}; int rc; @@ -346,40 +346,23 @@ static void torture_freed_channel_poll_timeout(void **state) 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); - } + rc = ssh_channel_read_nonblocking(channel, buff, 256, 0); + assert_ssh_return_code_equal(session, 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) +static void torture_channel_exit_status(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; - ssh_channel channel; - + ssh_channel channel = NULL; char request[256]; - char buff[256] = {0}; + uint32_t exit_status = (uint32_t)-1; int rc; - snprintf(request, 256, - "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + rc = snprintf(request, sizeof(request), "true"); + assert_return_code(rc, errno); channel = ssh_channel_new(session); assert_non_null(channel); @@ -391,26 +374,24 @@ static void torture_freed_channel_read_nonblocking(void **state) 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); + exit_status = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, 0); } -/* 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) +static void torture_channel_exit_signal(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; - ssh_channel channel; - bool channel_freed = false; + ssh_channel channel = NULL; char request[256]; - char buff[256] = {0}; + uint32_t exit_status = (uint32_t)-1; + char *exit_signal = NULL; + int core_dumped = false; int rc; - snprintf(request, 256, - "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + rc = snprintf(request, sizeof(request), "cat"); + assert_return_code(rc, errno); channel = ssh_channel_new(session); assert_non_null(channel); @@ -421,25 +402,18 @@ static void torture_freed_channel_get_exit_status(void **state) /* 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); + rc = ssh_channel_request_send_signal(channel, "TERM"); 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); + rc = ssh_channel_get_exit_state(channel, + &exit_status, + &exit_signal, + &core_dumped); - if (!channel_freed) { - rc = ssh_channel_get_exit_status(channel); - assert_ssh_return_code_equal(session, rc, SSH_ERROR); - } + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, (uint32_t)-1); + assert_string_equal(exit_signal, "TERM"); + SAFE_FREE(exit_signal); } static void @@ -471,11 +445,57 @@ torture_channel_read_stderr(void **state) /* 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); + } +} + +static void torture_openssh_banner_version(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int openssh_version = ssh_get_openssh_version(session); + int cmake_openssh_version = SSH_VERSION_INT(OPENSSH_VERSION_MAJOR, OPENSSH_VERSION_MINOR, 0); + + assert_int_equal(openssh_version, cmake_openssh_version); +} + + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -500,18 +520,24 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_freed_channel_poll, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_freed_channel_poll_timeout, + cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, + cmocka_unit_test_setup_teardown(torture_channel_exit_status, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_freed_channel_get_exit_status, + cmocka_unit_test_setup_teardown(torture_channel_exit_signal, 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), + cmocka_unit_test_setup_teardown(torture_openssh_banner_version, + 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..9bd5ff4e --- /dev/null +++ b/tests/client/torture_sftp_aio.c @@ -0,0 +1,778 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +#define DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(TEST_NAME) \ + { \ + #TEST_NAME, \ + TEST_NAME, \ + session_setup, \ + session_teardown, \ + NULL \ + }, \ + { \ + #TEST_NAME"_proxyjump", \ + TEST_NAME, \ + session_proxyjump_setup, \ + session_teardown, \ + NULL \ + } + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_setup_sshd_servers(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + /* this will take care of the server1 teardown too */ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup_helper(void **state, bool with_proxyjump) +{ + 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); + + if (with_proxyjump) { + s->ssh.session = torture_ssh_session_proxyjump(); + } else { + 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_setup(void **state) +{ + return session_setup_helper(state, false); +} + +static int session_proxyjump_setup(void **state) +{ + return session_setup_helper(state, true); +} + +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); +} + +/* + * Test that waiting for read responses in an order different from the + * sending order of corresponding read requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_read_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_read; + + struct { + /* buffer to store read data */ + char *buf; + + /* buffer to store data expected to be read */ + char *expected; + + /* + * length of the data to read. Keep this length small enough so that we + * don't get short reads due to the sftp limits. + */ + size_t len; + } r1 = {0}, r2 = {0}; + + int fd, rc; + + /* Initialize r1 */ + r1.len = 10; + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + r1.expected = calloc(r1.len, 1); + assert_non_null(r1.expected); + + /* Initialize r2 */ + r2.len = 20; + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + r2.expected = calloc(r2.len, 1); + assert_non_null(r2.expected); + + /* Get data that is expected to be read from the file */ + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + bytes_read = read(fd, r1.expected, r1.len); + assert_int_equal(bytes_read, r1.len); + + bytes_read = read(fd, r2.expected, r2.len); + assert_int_equal(bytes_read, r2.len); + + /* Open an sftp file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* + * Issue 2 consecutive read requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_read(file, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_read(file, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_read = sftp_aio_wait_read(&aio_2, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.buf, r2.expected, r2.len); + + bytes_read = sftp_aio_wait_read(&aio_1, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.buf, r1.expected, r1.len); + + /* Clean up */ + sftp_close(file); + + rc = close(fd); + assert_int_equal(rc, 0); + + free(r2.expected); + free(r2.buf); + + free(r1.expected); + free(r1.buf); +} + +/* + * Test that waiting for write responses in an order different from the + * sending order of corresponding write requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_write_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_written, bytes_read; + size_t i; + int rc, fd; + + struct { + /* + * length of the data to write. Keep this length small enough so that we + * don't get short writes due to the sftp limits + */ + size_t len; + + /* data to write */ + char *data; + + /* buffer used to validate the written data */ + char *buf; + } r1 = {0}, r2 = {0}; + + /* Initialize r1 */ + r1.len = 10; + + r1.data = calloc(r1.len, 1); + assert_non_null(r1.data); + + for (i = 0; i < r1.len; ++i) { + r1.data[i] = (char)rand(); + } + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + /* Initialize r2 */ + r2.len = 20; + + r2.data = calloc(r2.len, 1); + assert_non_null(r2.data); + + for (i = 0; i < r2.len; ++i) { + r2.data[i] = (char)rand(); + } + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + /* Open an sftp file for writing */ + snprintf(file_path, + sizeof(file_path), + "%s/libssh_sftp_aio_write_unordered_wait", + t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* + * Issue two consecutive write requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_write(file, r1.data, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_write(file, r2.data, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_written = sftp_aio_wait_write(&aio_2); + assert_int_equal(bytes_written, r2.len); + + bytes_written = sftp_aio_wait_write(&aio_1); + assert_int_equal(bytes_written, r1.len); + + /* + * Validate that the data has been written to the file correctly by reading + * from the file. + */ + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Validate that write request 1's data has been written to file */ + bytes_read = read(fd, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.data, r1.buf, r1.len); + + /* Validate that write request 2's data has been written to file */ + bytes_read = read(fd, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.data, r2.buf, r2.len); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(r2.buf); + free(r2.data); + + free(r1.buf); + free(r1.data); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_unordered_wait), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_unordered_wait), + }; + + 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 +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = 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_get_users_groups_by_id.c b/tests/client/torture_sftp_get_users_groups_by_id.c new file mode 100644 index 00000000..ed85dcbb --- /dev/null +++ b/tests/client/torture_sftp_get_users_groups_by_id.c @@ -0,0 +1,263 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + /* + * The SFTP server used for testing is executed as a separate binary, which + * is making the uid_wrapper lose information about what user is used, and + * therefore, pwd is initialized to some bad value. + * If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + 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_get_users_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for null */ + rc = sftp_get_users_groups_by_id(t->sftp, NULL, NULL); + assert_int_equal(rc, -1); + + /* test for 0 users */ + users_map = sftp_name_id_map_new(0); + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + + sftp_name_id_map_free(users_map); + + /* test for 3 users */ + users_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + + sftp_name_id_map_free(users_map); + + /* test for invalid uids */ + users_map = sftp_name_id_map_new(2); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = 42; /* invalid uid */ + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], ""); + + sftp_name_id_map_free(users_map); +} + +static void torture_sftp_get_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for 2 groups */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + + sftp_name_id_map_free(groups_map); + + /* test for invalid gids */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], ""); + + sftp_name_id_map_free(groups_map); +} + +static void torture_sftp_get_users_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + users_map = sftp_name_id_map_new(4); + groups_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + users_map->ids[3] = 42; /* invalid uid */ + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + groups_map->ids[2] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, groups_map); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + assert_string_equal(users_map->names[3], ""); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + assert_string_equal(groups_map->names[2], ""); + + sftp_name_id_map_free(users_map); + sftp_name_id_map_free(groups_map); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_get_users_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_groups_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_users_groups_by_id, + 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_home_directory.c b/tests/client/torture_sftp_home_directory.c new file mode 100644 index 00000000..a2399f02 --- /dev/null +++ b/tests/client/torture_sftp_home_directory.c @@ -0,0 +1,142 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + The SFTP server used for testing is executed as a separate binary, which + is making the uid_wrapper lose information about what user is used, and + therefore, pwd is initialized to some bad value. + If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + 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_home_directory(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *home_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "home-directory", "1"); + if (!rc) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for NULL sftp session */ + home_path = sftp_home_directory(NULL, NULL); + assert_null(home_path); + + /* testing for ~ */ + /* + home_path = sftp_home_directory(t->sftp, NULL); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + + home_path = sftp_home_directory(t->sftp, ""); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + */ + + /* + OpenSSH code handling this extension does not handle empty string for + username. getpwnam() also does not handle empty string. + PR in OpenSSH for fix: + https://github.com/openssh/openssh-portable/pull/477/ + */ + + /* testing for ~user */ + home_path = sftp_home_directory(t->sftp, pwd->pw_name); + fprintf(stderr, + "sftp error: %d, ssh error: %s\n", + sftp_get_error(t->sftp), + ssh_get_error(t->sftp->session)); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_home_directory, + 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_init.c b/tests/client/torture_sftp_init.c index a17f01fe..cdc24426 100644 --- a/tests/client/torture_sftp_init.c +++ b/tests/client/torture_sftp_init.c @@ -72,6 +72,63 @@ static void session_setup_channel(void **state) assert_non_null(s->ssh.tsftp); } +static void session_setup_extensions(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc, count; + const char *name = NULL, *data = NULL; + sftp_session sftp = NULL; + + 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); + sftp = s->ssh.tsftp->sftp; + + /* null parameter */ + count = sftp_extensions_get_count(NULL); + assert_int_equal(count, 0); + + count = sftp_extensions_get_count(sftp); + assert_int_not_equal(count, 0); + + /* first null parameter */ + name = sftp_extensions_get_name(NULL, 0); + assert_null(name); + data = sftp_extensions_get_data(NULL, 0); + assert_null(data); + + /* First extension */ + name = sftp_extensions_get_name(sftp, 0); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, 0); + assert_non_null(data); + + /* Last extension */ + name = sftp_extensions_get_name(sftp, count - 1); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, count - 1); + assert_non_null(data); + + /* Overrun */ + name = sftp_extensions_get_name(sftp, count); + assert_null(name); + data = sftp_extensions_get_data(sftp, count); + assert_null(data); +} + static int session_teardown(void **state) { struct torture_state *s = *state; @@ -92,7 +149,10 @@ int torture_run_tests(void) { session_teardown), cmocka_unit_test_setup_teardown(session_setup_channel, NULL, - session_teardown) + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_extensions, + NULL, + session_teardown), }; ssh_init(); 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 +#include +#include +#include + +#if HAVE_VALGRIND_VALGRIND_H + #include +#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_packet_read.c b/tests/client/torture_sftp_packet_read.c new file mode 100644 index 00000000..4eaba301 --- /dev/null +++ b/tests/client/torture_sftp_packet_read.c @@ -0,0 +1,118 @@ +/* + * This is a regression test to make sure that sftp_read_packet times out + * properly in blocking mode + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = 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_packet_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_packet packet = NULL; + + int fds[2]; + int rc; + + /* creating blocking fd is the default pipe behaviour */ + rc = pipe(fds); + assert_return_code(rc, errno); + + t->ssh->opts.timeout = 1; + ssh_socket_set_fd(t->ssh->socket, fds[0]); + + /* + * Making sure that the sftp_packet_read function times out and returns + * NULL. + */ + packet = sftp_packet_read(t->sftp); + assert_null(packet); + + close(fds[0]); + close(fds[1]); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_packet_read, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_recv_response_msg.c b/tests/client/torture_sftp_recv_response_msg.c new file mode 100644 index 00000000..c7bda318 --- /dev/null +++ b/tests/client/torture_sftp_recv_response_msg.c @@ -0,0 +1,197 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 Eshan Kelkar + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include + +#include +#include +#include +#include + +/* For the ability to access the members of the sftp_aio_struct in the test */ +#include "sftp_aio.c" + +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; +} + +/* Test that sftp_recv_response_msg() works properly in blocking mode */ +static void torture_sftp_recv_response_msg_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_message msg = NULL; + ssize_t bytes_requested; + int rc; + + /* + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Send an sftp read request */ + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Wait for the response (blocking mode) */ + rc = sftp_recv_response_msg(sftp, aio->id, true, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +/* Test that sftp_recv_response_msg() works properly in non blocking mode */ +static void torture_sftp_recv_response_msg_non_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + ssize_t bytes_requested; + int rc; + + /* + * At this point, the sftp channel shouldn't contain any outstanding + * responses. + * + * Hence, sftp_recv_response_msg() should return SSH_AGAIN immediately when + * we try to receive a response for any request ID in non-blocking mode. + */ + rc = sftp_recv_response_msg(sftp, 1984, false, &msg); + assert_int_equal(rc, SSH_AGAIN); + + /* + * Validate that after a response arrives in the sftp channel, trying to + * receive the response in non-blocking mode works properly. + * + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Poll the sftp channel for the response */ + rc = ssh_channel_poll_timeout(sftp->channel, 60000, 0); + assert_int_not_equal(rc, SSH_ERROR); + assert_int_not_equal(rc, SSH_EOF); + assert_int_not_equal(rc, 0); + + /* + * The response has arrived, trying to obtain it in non blocking mode + * should work + */ + rc = sftp_recv_response_msg(sftp, aio->id, false, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_recv_response_msg_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_sftp_recv_response_msg_non_blocking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_sftp_setstat.c b/tests/client/torture_sftp_setstat.c new file mode 100644 index 00000000..736e47c1 --- /dev/null +++ b/tests/client/torture_sftp_setstat.c @@ -0,0 +1,382 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "libssh/sftp.h" +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + * The OpenSSH invokes the sftp server command with execve(), which does + * not inherit the environment variables (including LD_PRELOAD, which + * is needed for the fs_wrapper). Using `internal-sftp` works around this, + * keeping the old environment around. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup_setstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char name[128] = {0}; + const char *test_1 = "l&setstat_test\n"; + 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); + + t = s->ssh.tsftp; + + snprintf(name, sizeof(name), "%s/libssh_sftp_setstat_test", t->testdir); + torture_write_file(name, test_1); + s->private_data = name; + + return 0; +} + +static int +session_setup_lsetstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char path[128] = {0}; + const char *test_1 = "lsetstat_test_1\n"; + int rc; + + char tmp_file[128] = {0}; + + 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); + + t = s->ssh.tsftp; + + rc = sftp_extension_supported(t->sftp, "lsetstat@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", t->testdir); + torture_write_file(tmp_file, test_1); + + snprintf(path, sizeof(path), "%s/linkname", t->testdir); + rc = symlink(tmp_file, path); + assert_int_equal(rc, SSH_OK); + s->private_data = path; + + 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; +} + +/*setstat tests*/ +static void +torture_sftp_setstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct sftp_attributes_struct attr; + struct passwd *pwd = NULL; + sftp_attributes tmp_attr = NULL; + const char *name = (char *)s->private_data; + int rc; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + tmp_attr = sftp_stat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_setstat_size(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + size_t len = 30; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE; + attr.size = len; + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(len, sb.st_size); +} + +static void +torture_sftp_setstat_chmod(void **state) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + attr.permissions = mode; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sb.st_mode & ACCESSPERMS, mode); +} + +static void +torture_sftp_setstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_setstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE; + + /* testing null sftp */ + rc = sftp_setstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing non-existing file */ + rc = sftp_setstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_setstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +/*lsetstat tests*/ +static void +torture_sftp_lsetstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_lstat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_lsetstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = lstat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_lsetstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID; + + /* testing non-existing file */ + rc = sftp_lsetstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_lsetstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* testing null sftp */ + rc = sftp_lsetstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chown, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chmod, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_utimes, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_size, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_negative, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_utimes, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_chown, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_negative, + session_setup_lsetstat, + 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/etc/group.in b/tests/etc/group.in index 8e13294b..df5ae8ab 100644 --- a/tests/etc/group.in +++ b/tests/etc/group.in @@ -2,4 +2,4 @@ users:x:9000: sshd:x:65531: nobody:x:65533: nogroup:x:65534:nobody -root:x:65532: +root:x:0: diff --git a/tests/etc/hosts.in b/tests/etc/hosts.in index 8891125d..ea519350 100644 --- a/tests/etc/hosts.in +++ b/tests/etc/hosts.in @@ -1,5 +1,12 @@ 127.0.0.10 server.libssh.site 127.0.0.21 client.libssh.site +127.0.0.11 kdc.libssh.site + 123.0.0.11 testing fd00::5357:5f0a testing + +127.0.0.10 afboth +fd00::5357:5f0a afboth +127.0.0.10 afinet +fd00::5357:5f0a afinet6 diff --git a/tests/etc/openssl.cnf b/tests/etc/openssl.cnf new file mode 100644 index 00000000..7149b6bf --- /dev/null +++ b/tests/etc/openssl.cnf @@ -0,0 +1,11 @@ +openssl_conf = openssl_init +[openssl_init] +providers = provider_sect +[provider_sect] +default = default_sect +pkcs11 = pkcs11_sect +[default_sect] +activate = 1 +[pkcs11_sect] +activate = 1 +pkcs11-module-assume-fips = true 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..fff78780 100644 --- a/tests/etc/passwd.in +++ b/tests/etc/passwd.in @@ -1,7 +1,9 @@ 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 +frank:x:5003:9000:doe gecos:@HOMEDIR@/frank:/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 +root:x:0:0:root gecos:@HOMEDIR@:/bin/false @LOCAL_USER@:x:@LOCAL_UID@:9000:local user:@HOMEDIR@:/bin/false diff --git a/tests/etc/shadow.in b/tests/etc/shadow.in 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 7c34b8c8..fcce52c7 100644 --- a/tests/external_override/CMakeLists.txt +++ b/tests/external_override/CMakeLists.txt @@ -44,7 +44,7 @@ else () ${libssh_SOURCE_DIR}/src/md_crypto.c ) set(override_libs - ${OPENSSL_CRYPTO_LIBRARIES} + OpenSSL::Crypto ) endif (WITH_GCRYPT) @@ -77,8 +77,30 @@ target_link_libraries(curve25519_override set(CURVE25519_OVERRIDE_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}curve25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) +# sntrup761_override +add_library(sntrup761_override SHARED + sntrup761_override.c + ${libssh_SOURCE_DIR}/src/external/sntrup761.c + ${override_src} +) +target_link_libraries(sntrup761_override + PRIVATE ${override_libs}) +set(SNTRUP761_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}sntrup761_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# mlkem768_override +add_library(mlkem768_override SHARED + mlkem768_override.c + ${libssh_SOURCE_DIR}/src/external/libcrux_mlkem768_sha3.c + ${override_src} +) +target_link_libraries(mlkem768_override + PRIVATE ${override_libs}) +set(MLKEM768_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}mlkem768_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(OVERRIDE_LIBRARIES - ${CHACHA20_OVERRIDE_LIBRARY}:${POLY1305_OVERRIDE_LIBRARY}:${ED25519_OVERRIDE_LIBRARY}:${CURVE25519_OVERRIDE_LIBRARY} + ${CHACHA20_OVERRIDE_LIBRARY}:${POLY1305_OVERRIDE_LIBRARY}:${ED25519_OVERRIDE_LIBRARY}:${CURVE25519_OVERRIDE_LIBRARY}:${SNTRUP761_OVERRIDE_LIBRARY}:${MLKEM768_OVERRIDE_LIBRARY} ) if (WITH_MBEDTLS) @@ -87,16 +109,36 @@ if (WITH_MBEDTLS) else () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") endif () + + if(HAVE_MBEDTLS_CURVE25519) + 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_ED25519=1") - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") elseif (WITH_GCRYPT) if (HAVE_GCRYPT_CHACHA_POLY) list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") else () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") endif () + + if(HAVE_GCRYPT_CURVE25519) + 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_ED25519=1") - list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=0") + if(HAVE_GCRYPT_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() else () if (HAVE_OPENSSL_EVP_CHACHA20) list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") @@ -105,6 +147,12 @@ else () endif () list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + if(HAVE_OPENSSL_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() endif () if (NOT OSX) @@ -135,6 +183,8 @@ foreach(_OVERRIDE_TEST ${LIBSSH_OVERRIDE_TESTS}) poly1305_override ed25519_override curve25519_override + sntrup761_override + mlkem768_override ) if (OSX) diff --git a/tests/external_override/mlkem768_override.c b/tests/external_override/mlkem768_override.c new file mode 100644 index 00000000..be135cda --- /dev/null +++ b/tests/external_override/mlkem768_override.c @@ -0,0 +1,83 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * 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, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "libssh/mlkem_native.h" +#include "mlkem768_override.h" + +static bool internal_function_called = false; + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_generate_key_pair(randomness); +} + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_validate_public_key(public_key); +} + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_encapsulate(public_key, randomness); +} + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_decapsulate(private_key, + ciphertext, + ret); +} + +bool internal_mlkem768_function_called(void) +{ + return internal_function_called; +} + +void reset_mlkem768_function_called(void) +{ + internal_function_called = false; +} diff --git a/tests/external_override/mlkem768_override.h b/tests/external_override/mlkem768_override.h new file mode 100644 index 00000000..ca9522e3 --- /dev/null +++ b/tests/external_override/mlkem768_override.h @@ -0,0 +1,43 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * 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, + * see . + */ + +#include "libssh/mlkem_native.h" + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]); + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +bool internal_mlkem768_function_called(void); +void reset_mlkem768_function_called(void); diff --git a/tests/external_override/sntrup761_override.c b/tests/external_override/sntrup761_override.c new file mode 100644 index 00000000..dcdca326 --- /dev/null +++ b/tests/external_override/sntrup761_override.c @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * 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, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "sntrup761_override.h" + +static bool internal_function_called = false; + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_keypair(pk, sk, random_ctx, random); +} + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_enc(c, k, pk, random_ctx, random); +} + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_dec(k, c, sk); +} + +bool internal_sntrup761_function_called(void) +{ + return internal_function_called; +} + +void reset_sntrup761_function_called(void) +{ + internal_function_called = false; +} diff --git a/tests/external_override/sntrup761_override.h b/tests/external_override/sntrup761_override.h new file mode 100644 index 00000000..4ec9cb5e --- /dev/null +++ b/tests/external_override/sntrup761_override.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * 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, + * see . + */ + +#include "libssh/sntrup761.h" + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +bool internal_sntrup761_function_called(void); +void reset_sntrup761_function_called(void); diff --git a/tests/external_override/torture_override.c b/tests/external_override/torture_override.c index 8024574b..468f6635 100644 --- a/tests/external_override/torture_override.c +++ b/tests/external_override/torture_override.c @@ -30,9 +30,11 @@ #include #include "chacha20_override.h" -#include "poly1305_override.h" #include "curve25519_override.h" #include "ed25519_override.h" +#include "mlkem768_override.h" +#include "poly1305_override.h" +#include "sntrup761_override.h" const char template[] = "temp_dir_XXXXXX"; @@ -118,6 +120,8 @@ static int session_setup(void **state) reset_poly1305_function_called(); reset_curve25519_function_called(); reset_ed25519_function_called(); + reset_sntrup761_function_called(); + reset_mlkem768_function_called(); return 0; } @@ -255,6 +259,132 @@ static void torture_override_ecdh_curve25519_sha256_libssh_org(void **state) } #endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_override_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_override_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#ifdef OPENSSH_MLKEM768X25519_SHA256 +static void torture_override_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_mlkem768_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ + +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 +static void torture_override_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_mlkem768_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif +} +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ + #ifdef OPENSSH_SSH_ED25519 static void torture_override_ed25519(void **state) { @@ -299,6 +429,26 @@ int torture_run_tests(void) session_setup, session_teardown), #endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#ifdef OPENSSH_MLKEM768X25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ #ifdef OPENSSH_SSH_ED25519 cmocka_unit_test_setup_teardown(torture_override_ed25519, session_setup, diff --git a/tests/fs_wrapper.c b/tests/fs_wrapper.c new file mode 100644 index 00000000..b718eff0 --- /dev/null +++ b/tests/fs_wrapper.c @@ -0,0 +1,255 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/******************************************************************************* + * Structs + ******************************************************************************/ +struct file { + char *name; + uid_t uid; + gid_t gid; +} file = {0}; + +/******************************************************************************* + * Destructor + ******************************************************************************/ + +void destructor(void) __attribute__((destructor)); + +void +destructor(void) +{ + free(file.name); +} + +/******************************************************************************* + * Chown wrapping + ******************************************************************************/ + +/** Records the UID and GID and pretend syscall worked */ +static int +chown_helper(const char *pathname, uid_t owner, gid_t group) +{ + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* + * The OpenSSH server modified the PTY which requires root permissions + * see torture_request_pty_modes + * */ + return 0; + } + if (strlen(pathname) > 4 && strncmp(pathname, "/tmp", 4) == 0) { + /* + * faking chown because It requires root permissions to modify the owner + * under /tmp + * It's also a helper for torture_sftp_setstat + * */ + if (file.name != NULL) { + free((char *)file.name); + } + file.name = strdup(pathname); + file.uid = owner; + file.gid = group; + return 0; + } + return -1; +} + +#define WRAP_CHOWN(syscall_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + uid_t owner, \ + gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group) \ + { \ + __libc_##syscall_name original_##syscall_name = NULL; \ + int rc; \ + \ + rc = chown_helper(pathname, owner, group); \ + if (rc == 0) { \ + return 0; \ + } \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + return (*original_##syscall_name)(pathname, owner, group); \ + } + +WRAP_CHOWN(chown) +WRAP_CHOWN(chown32) +WRAP_CHOWN(lchown) + +/* fchownat */ +typedef int (*__libc_fchownat)(int dirfd, + const char *pathname, + uid_t owner, + gid_t group, + int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) +{ + __libc_fchownat original_fchownat = NULL; + int rc; + + rc = chown_helper(pathname, owner, group); + if (rc == 0) { + return 0; + } + + original_fchownat = (__libc_fchownat)dlsym(RTLD_NEXT, "fchownat"); + return (*original_fchownat)(dirfd, pathname, owner, group, flags); +} + +/******************************************************************************* + * Stat wrapping + ******************************************************************************/ + +/** Returns previously set UID/GID for the filename */ +static void +stat_helper(const char *pathname, struct stat *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +static void +stat64_helper(const char *pathname, struct stat64 *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +#define WRAP_STAT(syscall_name, struct_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(pathname, statbuf); \ + struct_name##_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_STAT(stat, stat) +WRAP_STAT(lstat, stat) +/* i686 arch */ +WRAP_STAT(stat64, stat64) +WRAP_STAT(lstat64, stat64) + +#define WRAP_XSTAT(syscall_name) \ + typedef int (*__libc_##syscall_name)(int ver, \ + const char *pathname, \ + struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(ver, pathname, statbuf); \ + stat_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_XSTAT(__xstat) /* CentOS8 */ +WRAP_XSTAT(__lxstat) + +/* i686 arch (likely not wrappable) */ +static void +statx_helper(const char *pathname, struct statx *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->stx_uid = file.uid; + statbuf->stx_gid = file.gid; + } +} + +typedef int (*__libc_statx)(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int +statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf) +{ + int rc; + __libc_statx original_statx = NULL; + + original_statx = (__libc_statx)dlsym(RTLD_NEXT, "statx"); + rc = (*original_statx)(dirfd, pathname, flags, mask, statbuf); + statx_helper(pathname, statbuf); + + return rc; +} + +static int is_file_blocked(const char *pathname) +{ + if (pathname == NULL) { + return 0; + } + + static const char *blocked_files[] = { + /* Block for torture_gssapi_server_key_exchange_null */ + "/etc/ssh/ssh_host_ecdsa_key", + "/etc/ssh/ssh_host_rsa_key", + "/etc/ssh/ssh_host_ed25519_key", + }; + + for (size_t i = 0; i < sizeof(blocked_files) / sizeof(blocked_files[0]); + i++) { + if (strcmp(pathname, blocked_files[i]) == 0) { + errno = ENOENT; /* No such file or directory */ + return 1; + } + } + return 0; +} + +#define WRAP_FOPEN(func_name) \ + FILE *func_name(const char *pathname, const char *mode) \ + { \ + typedef FILE *(*orig_func_t)(const char *pathname, const char *mode); \ + static orig_func_t orig_func = NULL; \ + if (orig_func == NULL) { \ + orig_func = (orig_func_t)dlsym(RTLD_NEXT, #func_name); \ + } \ + if (is_file_blocked(pathname)) { \ + return NULL; \ + } \ + return orig_func(pathname, mode); \ + } + +WRAP_FOPEN(fopen) +WRAP_FOPEN(fopen64) diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 8d9b2bea..8f2f97e6 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -2,9 +2,7 @@ project(fuzzing CXX) macro(fuzzer name) add_executable(${name} ${name}.c) - target_link_libraries(${name} - PRIVATE - ssh::static) + target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set_target_properties(${name} PROPERTIES @@ -12,6 +10,9 @@ macro(fuzzer name) LINK_FLAGS "-fsanitize=fuzzer") # Run the fuzzer to make sure it works add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + # Run the fuzzer with nalloc to make sure it works + add_test(${name}_nalloc ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + set_property(TEST ${name}_nalloc PROPERTY ENVIRONMENT NALLOC_FREQ 32) else() target_sources(${name} PRIVATE fuzzer.c) # Run the fuzzer to make sure it works @@ -28,7 +29,12 @@ macro(fuzzer name) 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) +fuzzer(ssh_sshsig_fuzzer) +if (WITH_SERVER) + fuzzer(ssh_server_fuzzer) + fuzzer(ssh_bind_config_fuzzer) +endif (WITH_SERVER) diff --git a/tests/fuzz/README.md b/tests/fuzz/README.md index 088ce27d..a8f9a1be 100644 --- a/tests/fuzz/README.md +++ b/tests/fuzz/README.md @@ -10,6 +10,8 @@ but they are suitable for debugging. ## Background +### Turn off encryption + Fuzzing ssh protocol is complicated by the way that all the communication between client and server is encrypted and authenticated using keys based on random data, making it impossible to fuzz the actual underlying protocol @@ -17,6 +19,17 @@ as every change in the encrypted data causes integrity errors. For that reason, libssh needs to implement "none" cipher and MAC as described in RFC 4253 and these need to be used during fuzzing to be able to accomplish reproducibility and for fuzzers to be able to progress behind key exchange. +This is enabled with the `WITH_INSECURE_NONE` CMake option. + +### Do not allow filesystem modification + +The OpenSSH configuration files are quite rich and expects users to know what +they do when they write their configuration files. The fuzzer driver is not an +average user so it is very happy to try whatever commands come to its "mind", +including `rm -rf /` and libssh would be very happy to run it by default. This +might remove some parts of the system that are mandatory for fuzzing. +To avoid executing dangerous commands like this, the `WITH_EXEC=OFF` CMake +option prevents invoking any external command through `exec()` syscall. ## Corpus creation @@ -31,7 +44,7 @@ to use none cipher for the key exchange to be plausible. * Compile libssh with support for none cipher and pcap: - cmake -DWITH_INSECURE_NONE=ON -DWITH_PCAP=ON ../ + cmake -DWITH_INSECURE_NONE=ON -DWITH_EXEC=OFF -DWITH_PCAP=ON ../ * Create a configuration file enabling none cipher and mac: diff --git a/tests/fuzz/fuzzer.c b/tests/fuzz/fuzzer.c index 4db6a2bc..bd7a9edb 100644 --- a/tests/fuzz/fuzzer.c +++ b/tests/fuzz/fuzzer.c @@ -1,8 +1,14 @@ /* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ +#include "config.h" + #include #include #include +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); __attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); @@ -35,5 +41,9 @@ main (int argc, char **argv) free (buf); printf ("Done!\n"); + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif return 0; } diff --git a/tests/fuzz/nallocinc.c b/tests/fuzz/nallocinc.c new file mode 100644 index 00000000..6ce3c442 --- /dev/null +++ b/tests/fuzz/nallocinc.c @@ -0,0 +1,344 @@ +/* + MIT License + + Copyright (c) 2025 Catena cyber + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* Nalloc fuzz : framework to make allocations and IO fail while fuzzing */ + +/* Environment variables to control nalloc fuzz behavior : + * NALLOC_VERBOSE: set it to log failed allocations with their stacktraces + * NALLOC_FREQ: set it to control how frequently allocations fail + * value 0 disables nalloc (no allocations fail) + * value 1..31 : allocations fail always (1) or very rarely (31 -> 1 / 2^31) + * value 32 : allocations fail at a random rate between 5 and 20 for each run + */ +#if defined(__clang__) && defined(__has_feature) +#if __has_feature(address_sanitizer) +#define NALLOC_ASAN 1 +#endif +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static const uint32_t nalloc_crc32_table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}; + +// Nallocfuzz data to take a decision +uint32_t nalloc_random_state = 0; +__thread unsigned int nalloc_running = 0; +bool nalloc_initialized = false; +uint32_t nalloc_runs = 0; + +// Nalloc fuzz parameters +uint32_t nalloc_bitmask = 0xFF; +bool nalloc_random_bitmask = true; +uint32_t nalloc_magic = 0x294cee63; +bool nalloc_verbose = false; + +#ifdef NALLOC_ASAN +extern void __sanitizer_print_stack_trace(void); +#endif + +// Generic init, using env variables to get parameters +void nalloc_init(const char *prog) +{ + if (nalloc_initialized) { + return; + } + nalloc_initialized = true; + char *bitmask = getenv("NALLOC_FREQ"); + if (bitmask) { + int shift = atoi(bitmask); + if (shift > 0 && shift < 31) { + nalloc_bitmask = 1 << shift; + nalloc_random_bitmask = false; + } else if (shift == 0) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + } + } else if (prog == NULL || strstr(prog, "nalloc") == NULL) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + return; + } + + char *verbose = getenv("NALLOC_VERBOSE"); + if (verbose) { + nalloc_verbose = true; + } +} + +// add one byte to the CRC +static inline void nalloc_random_update(uint8_t b) +{ + nalloc_random_state = + ((uint32_t)((uint32_t)nalloc_random_state << 8)) ^ + nalloc_crc32_table[((nalloc_random_state >> 24) ^ b) & 0xFF]; +} + +// Start the failure injections, using a buffer as seed +static int nalloc_start(const uint8_t *data, size_t size) +{ + if (nalloc_random_bitmask) { + if (nalloc_random_state & 0x10) { + nalloc_bitmask = 0xFFFFFFFF; + } else { + nalloc_bitmask = 1 << (5 + (nalloc_random_state & 0xF)); + } + } else if (nalloc_bitmask == 0) { + // nalloc disabled + return 2; + } + nalloc_random_state = 0; + for (size_t i = 0; i < size; i++) { + nalloc_random_update(data[i]); + } + if (__sync_fetch_and_add(&nalloc_running, 1)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return 0; + } + nalloc_runs++; + return 1; +} + +// Stop the failure injections +static void nalloc_end() +{ + __sync_fetch_and_sub(&nalloc_running, 1); +} + +static bool nalloc_backtrace_exclude(size_t size, const char *op) +{ + if (nalloc_verbose) { + fprintf(stderr, "failed %s(%zu) \n", op, size); +#ifdef NALLOC_ASAN + __sanitizer_print_stack_trace(); +#endif + } + + return false; +} + +// +static bool nalloc_fail(size_t size, const char *op) +{ + // do not fail before thread init + if (nalloc_runs == 0) { + return false; + } + if (__sync_fetch_and_add(&nalloc_running, 1) != 1) { + // do not fail allocations outside of fuzzer input + // and do not fail inside of this function + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + nalloc_random_update((uint8_t)size); + if (size >= 0x100) { + nalloc_random_update((uint8_t)(size >> 8)); + if (size >= 0x10000) { + nalloc_random_update((uint8_t)(size >> 16)); + // bigger may already fail or oom + } + } + if (((nalloc_random_state ^ nalloc_magic) & nalloc_bitmask) == 0) { + if (nalloc_backtrace_exclude(size, op)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return true; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return false; +} + +// ASAN interceptor for libc routines +#ifdef NALLOC_ASAN +extern void *__interceptor_malloc(size_t); +extern void *__interceptor_calloc(size_t, size_t); +extern void *__interceptor_realloc(void *, size_t); +extern void *__interceptor_reallocarray(void *, size_t, size_t); + +extern ssize_t __interceptor_read(int, void *, size_t); +extern ssize_t __interceptor_write(int, const void *, size_t); +extern ssize_t __interceptor_recv(int, void *, size_t, int); +extern ssize_t __interceptor_send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __interceptor_malloc(s) +#define nalloc_calloc(s, n) __interceptor_calloc(s, n) +#define nalloc_realloc(p, s) __interceptor_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __interceptor_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __interceptor_read(f, b, s) +#define nalloc_write(f, b, s) __interceptor_write(f, b, s) +#define nalloc_recv(f, b, s, x) __interceptor_recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __interceptor_send(f, b, s, x) + +#else +extern void *__libc_malloc(size_t); +extern void *__libc_calloc(size_t, size_t); +extern void *__libc_realloc(void *, size_t); +extern void *__libc_reallocarray(void *, size_t, size_t); + +extern ssize_t __read(int, void *, size_t); +extern ssize_t __write(int, const void *, size_t); +extern ssize_t __recv(int, void *, size_t, int); +extern ssize_t __send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __libc_malloc(s) +#define nalloc_calloc(s, n) __libc_calloc(s, n) +#define nalloc_realloc(p, s) __libc_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __libc_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __read(f, b, s) +#define nalloc_write(f, b, s) __write(f, b, s) +#define nalloc_recv(f, b, s, x) __recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __send(f, b, s, x) +#endif + +// nalloc standard function overwrites with pseudo-random failures +ssize_t read(int fd, void *buf, size_t count) +{ + if (nalloc_fail(count, "read")) { + errno = EIO; + return -1; + } + return nalloc_read(fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) +{ + if (nalloc_fail(count, "write")) { + errno = EIO; + return -1; + } + return nalloc_write(fd, buf, count); +} + +ssize_t recv(int fd, void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "recv")) { + errno = EIO; + return -1; + } + return nalloc_recv(fd, buf, count, flags); +} + +ssize_t send(int fd, const void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "send")) { + errno = EIO; + return -1; + } + return nalloc_send(fd, buf, count, flags); +} + +void *calloc(size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "calloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_calloc(nmemb, size); +} + +void *malloc(size_t size) +{ + if (nalloc_fail(size, "malloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_malloc(size); +} + +void *realloc(void *ptr, size_t size) +{ + if (nalloc_fail(size, "realloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_realloc(ptr, size); +} + +void *reallocarray(void *ptr, size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "reallocarray")) { + errno = ENOMEM; + return NULL; + } + return nalloc_reallocarray(ptr, nmemb, size); +} + +#ifdef __cplusplus +} // extern "C" { +#endif diff --git a/tests/fuzz/ssh_bind_config_fuzzer.c b/tests/fuzz/ssh_bind_config_fuzzer.c index 3d0d8be8..fb5e03ee 100644 --- a/tests/fuzz/ssh_bind_config_fuzzer.c +++ b/tests/fuzz/ssh_bind_config_fuzzer.c @@ -24,6 +24,26 @@ #include "libssh/server.h" #include "libssh/bind_config.h" +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ssh_bind bind = NULL; @@ -36,17 +56,20 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) strncpy(input, (const char *)data, size); input[size] = '\0'; - ssh_init(); + assert(nalloc_start(data, size) > 0); bind = ssh_bind_new(); - assert(bind != NULL); + if (bind == NULL) { + goto out; + } ssh_bind_config_parse_string(bind, input); ssh_bind_free(bind); - ssh_finalize(); +out: free(input); + nalloc_end(); return 0; } diff --git a/tests/fuzz/ssh_client_config_fuzzer.c b/tests/fuzz/ssh_client_config_fuzzer.c index 62eae93c..a994f807 100644 --- a/tests/fuzz/ssh_client_config_fuzzer.c +++ b/tests/fuzz/ssh_client_config_fuzzer.c @@ -23,6 +23,26 @@ #include "libssh/libssh.h" #include "libssh/options.h" +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ssh_session session = NULL; @@ -35,10 +55,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) strncpy(input, (const char *)data, size); input[size] = '\0'; - ssh_init(); + assert(nalloc_start(data, size) > 0); session = ssh_new(); - assert(session != NULL); + if (session == NULL) { + goto out; + } /* Make sure we have default options set */ ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); @@ -47,9 +69,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssh_config_parse_string(session, input); ssh_free(session); - ssh_finalize(); +out: free(input); + nalloc_end(); return 0; } diff --git a/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop b/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop new file mode 100644 index 00000000..f1b37a52 --- /dev/null +++ b/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop @@ -0,0 +1,5 @@ +Host ssh-host +Hostname 10.1.1.1 + +Host 10.1.1.* 10.1.20.* +ProxyJump ssh-host \ No newline at end of file diff --git a/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username b/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username new file mode 100644 index 00000000..d766c61c --- /dev/null +++ b/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username @@ -0,0 +1,7 @@ +Host jumpbox +User oliverw +Hostname jumpbox.example.org + +Host myserver.example.org +Hostname 1.2.3.4 +ProxyJump jumpbox \ No newline at end of file diff --git a/tests/fuzz/ssh_client_fuzzer.c b/tests/fuzz/ssh_client_fuzzer.c index e69bf385..4f70282c 100644 --- a/tests/fuzz/ssh_client_fuzzer.c +++ b/tests/fuzz/ssh_client_fuzzer.c @@ -24,6 +24,26 @@ #include #include +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + static int auth_callback(const char *prompt, char *buf, size_t len, @@ -113,33 +133,53 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) rc = shutdown(socket_fds[1], SHUT_WR); assert(rc == 0); - ssh_init(); + assert(nalloc_start(data, size) > 0); session = ssh_new(); - assert(session != NULL); + if (session == NULL) { + goto out; + } env = getenv("LIBSSH_VERBOSITY"); if (env != NULL && strlen(env) > 0) { ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY_STR, env); } rc = ssh_options_set(session, SSH_OPTIONS_FD, &socket_fds[0]); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.0.0.1"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_USER, "alice"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &no); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } ssh_callbacks_init(&cb); ssh_set_callbacks(session, &cb); @@ -176,10 +216,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssh_disconnect(session); ssh_free(session); - ssh_finalize(); - close(socket_fds[0]); close(socket_fds[1]); + nalloc_end(); return 0; } diff --git a/tests/fuzz/ssh_known_hosts_fuzzer.c b/tests/fuzz/ssh_known_hosts_fuzzer.c index fabdc28f..839ae537 100644 --- a/tests/fuzz/ssh_known_hosts_fuzzer.c +++ b/tests/fuzz/ssh_known_hosts_fuzzer.c @@ -23,6 +23,26 @@ #include "libssh/libssh.h" #include "knownhosts.c" +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *hostname = NULL; @@ -59,7 +79,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) fwrite(data + hostname_len, size - hostname_len, 1, fp); fclose(fp); - ssh_init(); + assert(nalloc_start(data, size) > 0); ssh_known_hosts_read_entries(hostname, filename, &entries); for (it = ssh_list_get_iterator(entries); @@ -78,5 +98,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) free(hostname); unlink(filename); + nalloc_end(); return 0; } diff --git a/tests/fuzz/ssh_privkey_fuzzer.c b/tests/fuzz/ssh_privkey_fuzzer.c new file mode 100644 index 00000000..ff79103d --- /dev/null +++ b/tests/fuzz/ssh_privkey_fuzzer.c @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Jakub Jelen + * + * 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 +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + uint8_t *input = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + input = bin_to_base64(data, size); + if (input == NULL) { + goto out; + } + + rc = ssh_pki_import_privkey_base64((char *)input, NULL, NULL, NULL, &pkey); + free(input); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + 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..bb96dcc3 --- /dev/null +++ b/tests/fuzz/ssh_pubkey_fuzzer.c @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Jakub Jelen + * + * 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 +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + char *filename = NULL; + int fd; + int rc; + ssize_t sz; + + 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; + } + + assert(nalloc_start(data, size) > 0); + + rc = ssh_pki_import_pubkey_file(filename, &pkey); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + unlink(filename); + free(filename); + nalloc_end(); + 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.c b/tests/fuzz/ssh_server_fuzzer.c index aa84b8d2..e5504c72 100644 --- a/tests/fuzz/ssh_server_fuzzer.c +++ b/tests/fuzz/ssh_server_fuzzer.c @@ -31,6 +31,8 @@ #include #include +#include "nallocinc.c" + static const char kRSAPrivateKeyPEM[] = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" @@ -68,6 +70,24 @@ struct session_data_struct { bool authenticated; }; +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + static int auth_none(ssh_session session, const char *user, void *userdata) { struct session_data_struct *sdata = @@ -125,6 +145,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) bool no = false; const char *env = NULL; int rc; + ssh_bind sshbind = NULL; + ssh_session session = NULL; + ssh_event event = NULL; /* Our struct holding information about the session. */ struct session_data_struct sdata = { @@ -161,35 +184,54 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) rc = shutdown(socket_fds[1], SHUT_WR); assert(rc == 0); - /* Set up the libssh server */ - ssh_bind sshbind = ssh_bind_new(); - assert(sshbind != NULL); + assert(nalloc_start(data, size) > 0); - ssh_session session = ssh_new(); - assert(session != NULL); + /* Set up the libssh server */ + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + session = ssh_new(); + if (session == NULL) { + goto out; + } env = getenv("LIBSSH_VERBOSITY"); if (env != NULL && strlen(env) > 0) { rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, env); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, "/tmp/libssh_fuzzer_private_key"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, "none"); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no); - assert(rc == 0); + if (rc != SSH_OK) { + goto out; + } ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); @@ -197,10 +239,14 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ssh_set_server_callbacks(session, &server_cb); rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); - assert(rc == SSH_OK); + if (rc != SSH_OK) { + goto out; + } - ssh_event event = ssh_event_new(); - assert(event != NULL); + event = ssh_event_new(); + if (event == NULL) { + goto out; + } if (ssh_handle_key_exchange(session) == SSH_OK) { ssh_event_add_session(event, session); @@ -219,6 +265,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) } } +out: + nalloc_end(); + ssh_event_free(event); close(socket_fds[0]); diff --git a/tests/fuzz/ssh_sshsig_fuzzer.c b/tests/fuzz/ssh_sshsig_fuzzer.c new file mode 100644 index 00000000..8493f2b5 --- /dev/null +++ b/tests/fuzz/ssh_sshsig_fuzzer.c @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + const char input[] = "badc0de"; + const char namespace[] = "namespace"; + char *signature = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + signature = (char *)malloc(size + 1); + if (signature == NULL) { + goto out; + } + strncpy(signature, (const char *)data, size); + signature[size] = '\0'; + + rc = sshsig_verify(input, sizeof(input), signature, namespace, &pkey); + free(signature); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + return 0; +} diff --git a/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 b/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 new file mode 100644 index 00000000..d9de5df5 --- /dev/null +++ b/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 @@ -0,0 +1,14 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBALP3yM/hsvPV41IV3mzatq +7NStESRGVw233KH29dxEgyfX0m3fkZQlDOovn6BFVdt8VnWp3bNgZJ+9rRopyWnSIDllPp +KMafoEZrSSxPzjYgCiUKkSt3jiTQR+gLfejTKieBsL+ehuFuvLj4A8FFUMFSHOhHOkcqYs ++wxPkvvoErwUCFVELe15D3Fzsjec7o+ag4WTOJelezoPS1o+P9iBeWnLyo3yDKXqpp6fc+ +gU2GULbkFOm9VbhGIV8rzOi5DMJ3bFRoeOpAyjJkUIcgPAOqrywJYjDKvPJOYEeAHiXk56 +g0f0NdtCOjzKmDZeky05PPyqJzjjw0f11xm94heu8AAAAJbmFtZXNwYWNlAAAAAAAAAAZz +aGE1MTIAAAEUAAAADHJzYS1zaGEyLTUxMgAAAQApuWdMEHGcQgCagN8Tgcs72DEuLMBp/v +DXbjHbSyGRrcWcusZEvLClWkEJaouuvf7Vpqs1SaJvwW9nIcK0Md9UgZMXFOFMbKGg8LzC +YKp7O6Qud7skUgWclP4qyQrFWhYOfuijNY2rWajy+F42DI28j84CYx9bvHHWtqCEGihKdn +KLJltw/D7T3GnoKOeknOUl1Kr4Ca3G+qxSLxNsu0sa6TtP7ZnH+75tSlHunhVhOKHKf/f4 +YpjMCjuPIOolMbFm+UFojZcGMVvyZKelV2m4dPQ7OMpGcl7KTRMAbzm7yfsQeHSc132pnn +OwfsIiy75wDBtvudMSFOYftG1EeEzN +-----END SSH SIGNATURE----- diff --git a/tests/gss/kdcsetup.sh b/tests/gss/kdcsetup.sh new file mode 100755 index 00000000..c5e38bc8 --- /dev/null +++ b/tests/gss/kdcsetup.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +SOCKDIR=$1 +WORKDIR=$SOCKDIR/gss + +mkdir "$WORKDIR"/k "$WORKDIR"/d + +cat< "$WORKDIR"/k/kdc.conf +[realms] + LIBSSH.SITE = { + database_name = $WORKDIR/principal + key_stash_file = $WORKDIR/stash + kdc_listen = $(hostname -f) + kdc_tcp_listen = $(hostname -f) + default_principal_flags = +preauth,+forwardable + } +[logging] + kdc = FILE:$WORKDIR/kdc.log + debug = true +EOF + +cat< "$WORKDIR"/k/krb5.conf +[libdefaults] + default_realm = LIBSSH.SITE + forwardable = true + +[realms] + LIBSSH.SITE = { + kdc = $(hostname -f) + } +[domain_realm] + .$(hostname -d) = LIBSSH.SITE + +EOF + +kdb5_util -P foo create -s + +bash "$WORKDIR"/kadmin.sh + +krb5kdc -w 1 -P "$WORKDIR"/pid + +# Wait till KDC binds to the ports, 0x58 is port 88 +i=0 +while [ ! -S "$SOCKDIR"/T0B0058 ] && [ ! -S "$SOCKDIR"/U0B0058 ]; do + i=$((i + 1)) + [ "$i" -eq 5 ] && exit 1 + sleep 1 +done + +bash "$WORKDIR"/kinit.sh + +klist +exit 0 diff --git a/tests/keys/id_ecdsa_sk b/tests/keys/id_ecdsa_sk new file mode 100644 index 00000000..4c35105a --- /dev/null +++ b/tests/keys/id_ecdsa_sk @@ -0,0 +1,14 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAjwAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRv1/dD0qNb +Bbm4JmHwa9AQdHwzYOBDkptAAUJcyLX3kc8koKLoQF6rhUKGeZP6pv+AanVRTyOd/ITGUm +Zbgt7hAAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29tAAABkH7S+n5+0vp+AAAAInNrLWVjZHNh +LXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFub +gmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC +3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20BAAAA4y0tLS0tQkVHSU4gRUMgUFJJVkFURS +BLRVktLS0tLQpNSGNDQVFFRUlIRGZSL1NqWkRlczZrUmtTM0dLQTZoTUtSYmxRQjFWQlp3 +KzdqR2pIWU5xb0FvR0NDcUdTTTQ5CkF3RUhvVVFEUWdBRWI5ZjNROUtqV3dXNXVDWmg4R3 +ZRRUhSOE0yRGdRNUtiUUFGQ1hNaTE5NUhQSktDaTZFQmUKcTRWQ2hubVQrcWIvZ0dwMVVV +OGpuZnlFeGxKbVc0TGU0UT09Ci0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KAAAAAA +AAAAABAgMEBQYHCAk= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/keys/id_ecdsa_sk.pub b/tests/keys/id_ecdsa_sk.pub new file mode 100644 index 00000000..fbb35ffd --- /dev/null +++ b/tests/keys/id_ecdsa_sk.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFubgmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20= phoenix@phoenix-pc diff --git a/tests/keys/id_ed25519_sk b/tests/keys/id_ed25519_sk new file mode 100644 index 00000000..1622d28a --- /dev/null +++ b/tests/keys/id_ed25519_sk @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAWgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U3 +8SS2vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQAAALC75D22u+Q9tgAAABpzay1zc2gtZW +QyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2 +vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQEAAABA7QoCSXA/S9yF96YpCLNTVap+mYg0vH +yhKlMAUNnPqeXa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2vgAAAAAAAAAAAQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/keys/id_ed25519_sk.pub b/tests/keys/id_ed25519_sk.pub new file mode 100644 index 00000000..e31326e8 --- /dev/null +++ b/tests/keys/id_ed25519_sk.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAINr1udr8IT2FdkMxfojftF7oCXM1VmHTIHwP9TfxJLa+AAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29t phoenix@phoenix-pc 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/pkcs11/id_pkcs11_ed25519_openssh.pub b/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub new file mode 100644 index 00000000..96e32191 --- /dev/null +++ b/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN1aDCZfQOPArIsxnS8uMipbQtjM/FD77+hpkyo9i95W diff --git a/tests/pkcs11/setup-softhsm-tokens.sh b/tests/pkcs11/setup-softhsm-tokens.sh index bd8e0944..ddc684a0 100755 --- a/tests/pkcs11/setup-softhsm-tokens.sh +++ b/tests/pkcs11/setup-softhsm-tokens.sh @@ -8,7 +8,6 @@ OBJNAME=$3 TOKENLABEL=$3 # yeah. The same as object label LOADPUBLIC=$4 LIBSOFTHSM_PATH=$5 -P11_KIT_CLIENT=$6 shift 5 PUBKEY="$PRIVKEY.pub" @@ -29,6 +28,9 @@ if [ ! -d "$TESTDIR/db" ]; then directories.tokendir = $TESTDIR/db objectstore.backend = file log.level = DEBUG +# # The hashed ECDSA mechanisms wrongly do not support multi-part operations +# https://github.com/softhsm/SoftHSMv2/issues/842 +slots.mechanisms = -CKM_ECDSA_SHA1,CKM_ECDSA_SHA224,CKM_ECDSA_SHA256,CKM_ECDSA_SHA384,CKM_ECDSA_SHA512 EOF cat "$TESTDIR/softhsm.conf" @@ -86,55 +88,6 @@ if [ $ret -ne 0 ]; then 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 "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 +pkcs11-tool -M --login --pin=1234 --module="$LIBSOFTHSM_PATH" --token-label="$TOKENLABEL" exit 0 diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt index 3dc72c75..d921cd68 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}) @@ -18,15 +17,16 @@ set(pkd_hello_src ) set(pkd_libs - ${CMOCKA_LIBRARY} - ssh::static + ${TORTURE_LINK_LIBRARIES} ${ARGP_LIBRARIES} - pthread ) add_executable(pkd_hello ${pkd_hello_src}) target_compile_options(pkd_hello PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) -target_link_libraries(pkd_hello ${pkd_libs}) +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 diff --git a/tests/pkd/pkd_client.h b/tests/pkd/pkd_client.h index de42098b..fd44563f 100644 --- a/tests/pkd/pkd_client.h +++ b/tests/pkd/pkd_client.h @@ -24,10 +24,20 @@ "-o PubkeyAcceptedKeyTypes=" \ OPENSSH_KEYS +#ifdef HAVE_SK_DUMMY +#define SECURITY_KEY_PROVIDER \ + "-oSecurityKeyProvider=\"" SK_DUMMY_LIBRARY_PATH "\" " +#else +#define SECURITY_KEY_PROVIDER "" +#endif + +/* GlobalKnownHostsFile is just a place holder and won't actually set the hostkey */ #define OPENSSH_CMD_START(hostkey_algos) \ OPENSSH_BINARY " " \ "-o UserKnownHostsFile=/dev/null " \ "-o StrictHostKeyChecking=no " \ + SECURITY_KEY_PROVIDER \ + "-o GlobalKnownHostsFile=%s " \ "-F /dev/null " \ hostkey_algos " " \ OPENSSH_PKACCEPTED_TYPES " " \ @@ -61,12 +71,14 @@ /* Dropbear */ -#define DROPBEAR_BINARY "dbclient" +#define DROPBEAR_BINARY DROPBEAR_EXECUTABLE #define DROPBEAR_KEYGEN "dropbearkey" +/* HostKeyAlias is just a place holder and won't actually set the hostkey */ #define DROPBEAR_CMD_START \ DROPBEAR_BINARY " " \ "-y -y " \ + "-o HostKeyAlias=%s " \ "-i " CLIENT_ID_FILE " " \ "1> %s.out " \ "2> %s.err " @@ -87,4 +99,22 @@ #define DROPBEAR_MAC_CMD(macs) \ DROPBEAR_CMD_START "-m " macs " " DROPBEAR_CMD_END +/* PuTTY */ + +#define PUTTY_BINARY PUTTY_EXECUTABLE +#define PUTTY_KEYGEN PUTTYGEN_EXECUTABLE + +#define PUTTY_CMD_START \ + PUTTY_BINARY " " \ + "-batch -ssh -P 1234 " \ + "-i " CLIENT_ID_FILE " " \ + "-hostkey $(" OPENSSH_KEYGEN \ + " -l -f %s.pub -E md5 | awk '{print $2}' | cut -d: -f2-) " \ + "1> %s.out 2> %s.err " + +#define PUTTY_CMD_END " localhost ls" + +#define PUTTY_CMD \ + PUTTY_CMD_START PUTTY_CMD_END + #endif /* __PKD_CLIENT_H__ */ diff --git a/tests/pkd/pkd_daemon.c b/tests/pkd/pkd_daemon.c index ac4b53b7..6c9ca03e 100644 --- a/tests/pkd/pkd_daemon.c +++ b/tests/pkd/pkd_daemon.c @@ -297,7 +297,7 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) /* Add methods not enabled by default */ /* Enable all supported key exchange methods */ - all_kex = ssh_kex_get_supported_method(SSH_KEX); + all_kex = ssh_get_supported_methods(SSH_KEX); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_KEY_EXCHANGE, all_kex); if (rc != 0) { pkderr("ssh_bind_options_set kex methods: %s\n", ssh_get_error(b)); @@ -305,14 +305,14 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) } /* Enable all supported ciphers */ - all_ciphers = ssh_kex_get_supported_method(SSH_CRYPT_C_S); + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_C_S); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_C_S, all_ciphers); if (rc != 0) { pkderr("ssh_bind_options_set Ciphers C-S: %s\n", ssh_get_error(b)); goto outclose; } - all_ciphers = ssh_kex_get_supported_method(SSH_CRYPT_S_C); + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_S_C); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_S_C, all_ciphers); if (rc != 0) { pkderr("ssh_bind_options_set Ciphers S-C: %s\n", ssh_get_error(b)); @@ -320,7 +320,7 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) } /* Enable all hostkey algorithms */ - all_hostkeys = ssh_kex_get_supported_method(SSH_HOSTKEYS); + all_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, all_hostkeys); if (rc != 0) { pkderr("ssh_bind_options_set Hostkeys: %s\n", ssh_get_error(b)); @@ -328,14 +328,14 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) } /* Enable all message authentication codes */ - all_macs = ssh_kex_get_supported_method(SSH_MAC_C_S); + all_macs = ssh_get_supported_methods(SSH_MAC_C_S); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_C_S, all_macs); if (rc != 0) { pkderr("ssh_bind_options_set MACs C-S: %s\n", ssh_get_error(b)); goto outclose; } - all_macs = ssh_kex_get_supported_method(SSH_MAC_S_C); + all_macs = ssh_get_supported_methods(SSH_MAC_S_C); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_S_C, all_macs); if (rc != 0) { pkderr("ssh_bind_options_set MACs S-C: %s\n", ssh_get_error(b)); diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c index a64124d3..feedaf34 100644 --- a/tests/pkd/pkd_hello.c +++ b/tests/pkd/pkd_hello.c @@ -8,6 +8,7 @@ #include #include // for cmocka #include // for cmocka +#include // for cmocka #include #include #include @@ -21,6 +22,11 @@ #include "pkd_keyutil.h" #include "pkd_util.h" +#if defined(HAVE_LIBCRYPTO) +/* for OPENSSL_cleanup() of OpenSSL context */ +#include +#endif + #define DEFAULT_ITERATIONS 10 static struct pkd_daemon_args pkd_dargs; @@ -209,252 +215,322 @@ static int torture_pkd_setup_ecdsa_521(void **state) { */ #define PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ - f(client, rsa_default, cmd, setup_rsa, teardown) \ - f(client, ecdsa_256_default, cmd, setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_default, cmd, setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_default, cmd, setup_ecdsa_521, teardown) + f(client, rsa_default, cmd, setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_default, cmd, setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_default, cmd, setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_default, cmd, setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #define PKDTESTS_DEFAULT(f, client, cmd) \ /* Default passes by server key type. */ \ - PKDTESTS_DEFAULT_FIPS(f, client, cmd) - -#define PKDTESTS_DEFAULT_OPENSSHONLY(f, client, cmd) \ - /* Default passes by server key type. */ \ - f(client, ed25519_default, cmd, setup_ed25519, teardown) + PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, ed25519_default, cmd, setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) #define GEX_SHA256 "diffie-hellman-group-exchange-sha256" #define GEX_SHA1 "diffie-hellman-group-exchange-sha1" #if defined(WITH_GEX) #define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ - f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown) \ - f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown) \ - f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown) \ - f(client, rsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_rsa, teardown) \ - f(client, ecdsa_256_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_521, teardown) + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #else /* !defined(WITH_GEX) */ #define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ - f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown) \ - f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown) \ - f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown) + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +#define SNTRUP_OPENSSH_NAME "sntrup761x25519-sha512@openssh.com" +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +#define SNTRUP_NAME "sntrup761x25519-sha512" +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + f(client, rsa_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + f(client, rsa_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) +#endif + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) #endif #define PKDTESTS_KEX_COMMON(f, client, kexcmd) \ PKDTESTS_KEX_FIPS(f, client, kexcmd) \ - f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown) \ - f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown) \ - f(client, rsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_rsa, teardown) \ - f(client, ecdsa_256_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_521, teardown) + PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #if defined(WITH_GEX) /* GEX_SHA256 is included in PKDTESTS_KEX_FIPS if available */ #define PKDTESTS_KEX(f, client, kexcmd) \ /* Kex algorithms. */ \ PKDTESTS_KEX_COMMON(f, client, kexcmd) \ - f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown) \ - f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown) + f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #else #define PKDTESTS_KEX(f, client, kexcmd) \ /* Kex algorithms. */ \ - PKDTESTS_KEX_COMMON(f, client, kexcmd) + f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) #endif -#define PKDTESTS_KEX_OPENSSHONLY(f, client, kexcmd) \ - /* Kex algorithms. */ \ - f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown) \ - f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown) \ - f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown) \ - f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown) \ - f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown) \ - f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown) - -#define CHACHA20 "chacha20-poly1305@openssh.com" - #define PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ - f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown) \ - f(client, rsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_rsa, teardown) \ - f(client, ecdsa_256_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_521, teardown) + f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #define PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ - f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown) \ - f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown) \ - f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown) \ - 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) + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#define CHACHA20 "chacha20-poly1305@openssh.com" #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) + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #define PKDTESTS_CIPHER(f, client, ciphercmd) \ /* Ciphers. */ \ PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ - PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) + PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, ed25519_aes128_ctr, ciphercmd("aes128-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_ctr, ciphercmd("aes256-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) #define AES128_GCM "aes128-gcm@openssh.com" #define AES256_GCM "aes256-gcm@openssh.com" #define PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ - f(client, rsa_aes128_gcm, ciphercmd(AES128_GCM), setup_rsa, teardown) \ - f(client, rsa_aes256_gcm, ciphercmd(AES256_GCM), setup_rsa, teardown) \ - f(client, ecdsa_256_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_521, teardown) + f(client, rsa_aes128_gcm, ciphercmd(AES128_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_gcm, ciphercmd(AES256_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) #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, 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_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) \ - 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_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_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, rsa_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_3des_cbc, ciphercmd("3des-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_cbc, ciphercmd("aes128-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_cbc, ciphercmd("aes256-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_cbc, ciphercmd("aes192-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_ctr, ciphercmd("aes192-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_gcm, ciphercmd(AES128_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_gcm, ciphercmd(AES256_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + + +#define PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#ifdef DROPBEAR_SUPPORTS_HMAC_SHA1 +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) +#else #define PKDTESTS_MAC_FIPS(f, client, maccmd) \ - f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown) \ - f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown) \ - f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown) + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) +#endif #define PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ - f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_256_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_256, teardown) \ - f(client, ecdsa_384_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_384_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_384, teardown) \ - f(client, ecdsa_521_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_521, teardown) \ - f(client, ecdsa_521_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_521, teardown) \ - f(client, rsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_rsa, teardown) \ - f(client, rsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_rsa, teardown) \ - f(client, rsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_rsa, teardown) \ - f(client, rsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_rsa, teardown) + PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) #define PKDTESTS_MAC(f, client, maccmd) \ /* MACs. */ \ - PKDTESTS_MAC_FIPS(f, client, maccmd) + PKDTESTS_MAC_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + #define PKDTESTS_MAC_OPENSSHONLY(f, client, maccmd) \ PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ - f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown) \ - f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown) \ - f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown) \ - f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown) \ - f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown) \ - f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown) + f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) #define PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) \ - f(client, rsa_sha2_256, hkcmd("rsa-sha2-256"), setup_rsa, teardown) \ - f(client, rsa_sha2_512, hkcmd("rsa-sha2-512"), setup_rsa, teardown) \ - f(client, rsa_sha2_256_512, hkcmd("rsa-sha2-256,rsa-sha2-512"), setup_rsa, teardown) \ - f(client, rsa_sha2_512_256, hkcmd("rsa-sha2-512,rsa-sha2-256"), setup_rsa, teardown) + f(client, rsa_sha2_256, hkcmd("rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512, hkcmd("rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_256_512, hkcmd("rsa-sha2-256,rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512_256, hkcmd("rsa-sha2-512,rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) #define PKDTESTS_HOSTKEY_OPENSSHONLY(f, client, hkcmd) \ PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) @@ -491,12 +567,12 @@ static void torture_pkd_runtest(const char *testname, * defined here to result in distinct output when running the tests. */ -#define emit_keytest(client, testname, sshcmd, setup, teardown) \ +#define emit_keytest(client, testname, sshcmd, setup, teardown, hostkey) \ static void torture_pkd_## client ## _ ## testname(void **state) { \ const char *tname = "torture_pkd_" #client "_" #testname; \ char testcmd[2048] = { 0 }; \ (void) state; \ - snprintf(&testcmd[0], sizeof(testcmd), sshcmd, tname, tname); \ + snprintf(&testcmd[0], sizeof(testcmd), sshcmd, hostkey, tname, tname); \ torture_pkd_runtest(tname, testcmd); \ } @@ -507,9 +583,7 @@ static void torture_pkd_runtest(const char *testname, PKDTESTS_DEFAULT(emit_keytest, openssh_rsa, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_keytest, openssh_cert_rsa, OPENSSH_CERT_CMD) PKDTESTS_DEFAULT(emit_keytest, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) -PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CMD) PKDTESTS_KEX(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) -PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) @@ -520,9 +594,7 @@ PKDTESTS_HOSTKEY_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_HOSTKEY_CMD) #define CLIENT_ID_FILE OPENSSH_ECDSA256_TESTKEY PKDTESTS_DEFAULT(emit_keytest, openssh_e256, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_keytest, openssh_cert_e256, OPENSSH_CERT_CMD) -PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CMD) PKDTESTS_KEX(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) -PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) @@ -536,19 +608,63 @@ PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) #define CLIENT_ID_FILE OPENSSH_ED25519_TESTKEY PKDTESTS_DEFAULT(emit_keytest, openssh_ed, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed, OPENSSH_CERT_CMD) -PKDTESTS_DEFAULT_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CMD) PKDTESTS_KEX(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) -PKDTESTS_KEX_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) #undef CLIENT_ID_FILE +#ifdef HAVE_SK_DUMMY +#define CLIENT_ID_FILE OPENSSH_ECDSA_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ec_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ec_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ec_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ED25519_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE +#endif /* HAVE_SK_DUMMY */ + #define CLIENT_ID_FILE DROPBEAR_RSA_TESTKEY -PKDTESTS_DEFAULT(emit_keytest, dropbear, DROPBEAR_CMD) -PKDTESTS_CIPHER(emit_keytest, dropbear, DROPBEAR_CIPHER_CMD) -PKDTESTS_MAC(emit_keytest, dropbear, DROPBEAR_MAC_CMD) +PKDTESTS_DEFAULT(emit_keytest, dropbear_rsa, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_rsa, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_rsa, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_e256, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_e256, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_e256, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_ed, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_ed, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_ed, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_RSA_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_rsa, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ED25519_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_ed, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ECDSA256_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_e256, PUTTY_CMD) #undef CLIENT_ID_FILE /* @@ -557,17 +673,17 @@ PKDTESTS_MAC(emit_keytest, dropbear, DROPBEAR_MAC_CMD) * the command line. */ -#define emit_testmap(client, testname, sshcmd, setup, teardown) \ +#define emit_testmap(client, testname, sshcmd, setup, teardown, ...) \ { "torture_pkd_" #client "_" #testname, \ - emit_unit_test(client, testname, sshcmd, setup, teardown) }, + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__) }, -#define emit_unit_test(client, testname, sshcmd, setup, teardown) \ +#define emit_unit_test(client, testname, sshcmd, setup, teardown, ...) \ cmocka_unit_test_setup_teardown(torture_pkd_ ## client ## _ ## testname, \ torture_pkd_ ## setup, \ torture_pkd_ ## teardown) -#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown) \ - emit_unit_test(client, testname, sshcmd, setup, teardown), +#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown, ...) \ + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__), struct { const char *testname; @@ -578,9 +694,7 @@ struct { PKDTESTS_DEFAULT(emit_testmap, openssh_rsa, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_testmap, openssh_cert_rsa, OPENSSH_CERT_CMD) PKDTESTS_DEFAULT(emit_testmap, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CMD) PKDTESTS_KEX(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) - PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) @@ -589,9 +703,7 @@ struct { PKDTESTS_DEFAULT(emit_testmap, openssh_e256, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_testmap, openssh_cert_e256, OPENSSH_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CMD) PKDTESTS_KEX(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) - PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) @@ -599,21 +711,34 @@ struct { PKDTESTS_DEFAULT(emit_testmap, openssh_ed, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_testmap, openssh_cert_ed, OPENSSH_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CMD) PKDTESTS_KEX(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) - PKDTESTS_KEX_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) /* Dropbear */ - PKDTESTS_DEFAULT(emit_testmap, dropbear, DROPBEAR_CMD) - PKDTESTS_CIPHER(emit_testmap, dropbear, DROPBEAR_CIPHER_CMD) - PKDTESTS_MAC(emit_testmap, dropbear, DROPBEAR_MAC_CMD) + PKDTESTS_DEFAULT(emit_testmap, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_ed, DROPBEAR_MAC_CMD) + + /* PuTTY */ + PKDTESTS_DEFAULT(emit_testmap, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_ed, PUTTY_CMD) /* Noop */ - emit_testmap(client, noop, "", setup_noop, teardown) + emit_testmap(client, noop, "", setup_noop, teardown, NULL) /* NULL tail entry */ { .testname = NULL, @@ -633,7 +758,6 @@ static int pkd_run_tests(void) { PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_rsa, OPENSSH_CERT_CMD) PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) PKDTESTS_KEX(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) @@ -642,7 +766,6 @@ static int pkd_run_tests(void) { PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) PKDTESTS_KEX(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) @@ -651,12 +774,29 @@ static int pkd_run_tests(void) { PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed, OPENSSH_CERT_CMD) - PKDTESTS_DEFAULT_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) PKDTESTS_KEX(emit_unit_test_comma, openssh_ed, OPENSSH_KEX_CMD) PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) PKDTESTS_MAC(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + +#ifdef HAVE_SK_DUMMY + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ec_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ec_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) +#endif /* HAVE_SK_DUMMY */ }; /* It is not possible to test hostkey and kex algorithms, because @@ -664,9 +804,25 @@ static int pkd_run_tests(void) { * 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) - PKDTESTS_MAC(emit_unit_test_comma, dropbear, DROPBEAR_MAC_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_ed, DROPBEAR_MAC_CMD) + }; + + const struct CMUnitTest putty_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_ed, PUTTY_CMD) }; const struct CMUnitTest openssh_fips_tests[] = { @@ -689,12 +845,13 @@ static int pkd_run_tests(void) { }; const struct CMUnitTest noop_tests[] = { - emit_unit_test(client, noop, "", setup_noop, teardown) + emit_unit_test(client, noop, "", setup_noop, teardown, NULL) }; /* Test list is populated depending on which clients are enabled. */ struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) + (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) + + (sizeof(putty_tests) / sizeof(putty_tests[0])) + (sizeof(noop_tests) / sizeof(noop_tests[0]))]; memset(&all_tests[0], 0x0, sizeof(all_tests)); @@ -711,13 +868,21 @@ static int pkd_run_tests(void) { } if (is_dropbear_client_enabled()) { - setup_dropbear_client_rsa_key(); + setup_dropbear_client_keys(); if (!ssh_fips_mode()) { memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests)); tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])); } } + if (is_putty_client_enabled()) { + setup_putty_client_keys(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &putty_tests[0], sizeof(putty_tests)); + tindex += (sizeof(putty_tests) / sizeof(putty_tests[0])); + } + } + memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests)); tindex += (sizeof(noop_tests) / sizeof(noop_tests[0])); @@ -763,13 +928,17 @@ static int pkd_run_tests(void) { /* Clean up client keys for each enabled client. */ if (is_dropbear_client_enabled()) { - cleanup_dropbear_client_rsa_key(); + cleanup_dropbear_client_keys(); } if (is_openssh_client_enabled()) { cleanup_openssh_client_keys(); } + if (is_putty_client_enabled()) { + cleanup_putty_client_keys(); + } + /* Clean up any server keys that were generated. */ cleanup_rsa_key(); cleanup_ecdsa_keys(); @@ -984,6 +1153,9 @@ int main(int argc, char **argv) { if (rc != 0) { fprintf(stderr, "ssh_finalize: %d\n", rc); } +#if defined(HAVE_LIBCRYPTO) + OPENSSL_cleanup(); +#endif out: return exit_code; -} +} \ No newline at end of file diff --git a/tests/pkd/pkd_keyutil.c b/tests/pkd/pkd_keyutil.c index 4e032f17..34e071e2 100644 --- a/tests/pkd/pkd_keyutil.c +++ b/tests/pkd/pkd_keyutil.c @@ -8,6 +8,7 @@ #include // for cmocka #include // for cmocka +#include // for cmocka #include // for cmocka #include @@ -152,6 +153,21 @@ void setup_openssh_client_keys(void) { } assert_int_equal(rc, 0); } + +#ifdef HAVE_SK_DUMMY + setenv("SSH_SK_PROVIDER", SK_DUMMY_LIBRARY_PATH, 1); + if (access(OPENSSH_ECDSA_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa-sk -q -N \"\" -f " + OPENSSH_ECDSA_SK_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519-sk -q -N \"\" -f " + OPENSSH_ED25519_SK_TESTKEY); + } + assert_int_equal(rc, 0); +#endif } void cleanup_openssh_client_keys(void) { @@ -164,17 +180,90 @@ void cleanup_openssh_client_keys(void) { if (!ssh_fips_mode()) { cleanup_key(OPENSSH_ED25519_TESTKEY); } +#ifdef HAVE_SK_DUMMY + cleanup_key(OPENSSH_ECDSA_SK_TESTKEY); + cleanup_key(OPENSSH_ED25519_SK_TESTKEY); +#endif } -void setup_dropbear_client_rsa_key(void) { +void setup_dropbear_client_keys(void) +{ int rc = 0; if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " DROPBEAR_RSA_TESTKEY " 1>/dev/null 2>/dev/null"); } assert_int_equal(rc, 0); + if (access(DROPBEAR_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ecdsa -f " + DROPBEAR_ECDSA256_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); + if (access(DROPBEAR_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ed25519 -f " + DROPBEAR_ED25519_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); } -void cleanup_dropbear_client_rsa_key(void) { - unlink(DROPBEAR_RSA_TESTKEY); +void cleanup_dropbear_client_keys(void) +{ + cleanup_key(DROPBEAR_RSA_TESTKEY); + cleanup_key(DROPBEAR_ECDSA256_TESTKEY); + cleanup_key(DROPBEAR_ED25519_TESTKEY); } + +void setup_putty_client_keys(void) +{ + int rc = 0; + + /* RSA Keys */ + if (access(PUTTY_RSA_TESTKEY, F_OK) != 0 || + access(PUTTY_RSA_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + PUTTY_RSA_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_RSA_TESTKEY + " -O private -o " PUTTY_RSA_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ECDSA 256 Keys */ + if (access(PUTTY_ECDSA256_TESTKEY, F_OK) != 0 || + access(PUTTY_ECDSA256_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + PUTTY_ECDSA256_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ECDSA256_TESTKEY + " -O private -o " PUTTY_ECDSA256_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ED25519 Keys */ + if (access(PUTTY_ED25519_TESTKEY, F_OK) != 0 || + access(PUTTY_ED25519_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + PUTTY_ED25519_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ED25519_TESTKEY + " -O private -o " PUTTY_ED25519_PPK_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_putty_client_keys(void) +{ + cleanup_key(PUTTY_RSA_TESTKEY); + cleanup_file(PUTTY_RSA_PPK_TESTKEY); + + cleanup_key(PUTTY_ECDSA256_TESTKEY); + cleanup_file(PUTTY_ECDSA256_PPK_TESTKEY); + + cleanup_key(PUTTY_ED25519_TESTKEY); + cleanup_file(PUTTY_ED25519_PPK_TESTKEY); +} \ No newline at end of file diff --git a/tests/pkd/pkd_keyutil.h b/tests/pkd/pkd_keyutil.h index 8e8f50ae..86357c44 100644 --- a/tests/pkd/pkd_keyutil.h +++ b/tests/pkd/pkd_keyutil.h @@ -30,14 +30,28 @@ void cleanup_ecdsa_keys(void); #define OPENSSH_ECDSA521_TESTKEY "openssh_testkey.id_ecdsa521" #define OPENSSH_ED25519_TESTKEY "openssh_testkey.id_ed25519" #define OPENSSH_CA_TESTKEY "libssh_testkey.ca" +#define OPENSSH_ECDSA_SK_TESTKEY "openssh_testkey.id_ecdsa-sk" +#define OPENSSH_ED25519_SK_TESTKEY "openssh_testkey.id_ed25519-sk" #define DROPBEAR_RSA_TESTKEY "dropbear_testkey.id_rsa" +#define DROPBEAR_ECDSA256_TESTKEY "dropbear_testkey.id_ecdsa256" +#define DROPBEAR_ED25519_TESTKEY "dropbear_testkey.id_ed25519" + +#define PUTTY_RSA_TESTKEY "putty_testkey.id_rsa" +#define PUTTY_RSA_PPK_TESTKEY "putty_testkey.id_rsa.ppk" +#define PUTTY_ECDSA256_TESTKEY "putty_testkey.id_ecdsa256" +#define PUTTY_ECDSA256_PPK_TESTKEY "putty_testkey.id_ecdsa256.ppk" +#define PUTTY_ED25519_TESTKEY "putty_testkey.id_ed25519" +#define PUTTY_ED25519_PPK_TESTKEY "putty_testkey.id_ed25519.ppk" void setup_openssh_client_keys(void); void cleanup_openssh_client_keys(void); -void setup_dropbear_client_rsa_key(void); -void cleanup_dropbear_client_rsa_key(void); +void setup_dropbear_client_keys(void); +void cleanup_dropbear_client_keys(void); + +void setup_putty_client_keys(void); +void cleanup_putty_client_keys(void); #define cleanup_file(name) do {\ if (access((name), F_OK) != -1) {\ diff --git a/tests/pkd/pkd_util.c b/tests/pkd/pkd_util.c index e8e6fbb7..e4866bd5 100644 --- a/tests/pkd/pkd_util.c +++ b/tests/pkd/pkd_util.c @@ -112,3 +112,10 @@ int is_openssh_client_enabled(void) { int is_dropbear_client_enabled(void) { return (bin_exists(DROPBEAR_BINARY) && bin_exists(DROPBEAR_KEYGEN)); } + +int is_putty_client_enabled(void) +{ + return (bin_exists(PUTTY_BINARY) && + bin_exists(PUTTY_KEYGEN) && + bin_exists(OPENSSH_KEYGEN)); +} diff --git a/tests/pkd/pkd_util.h b/tests/pkd/pkd_util.h index aedbbe9f..8c4a637d 100644 --- a/tests/pkd/pkd_util.h +++ b/tests/pkd/pkd_util.h @@ -12,5 +12,6 @@ int system_checked(const char *cmd); /* Is client 'X' enabled? */ int is_openssh_client_enabled(void); int is_dropbear_client_enabled(void); +int is_putty_client_enabled(void); -#endif /* __PKD_UTIL_H__ */ +#endif /* __PKD_UTIL_H__ */ \ No newline at end of file diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index f741e95a..ab9ce5b4 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -7,18 +7,32 @@ find_package(socket_wrapper) add_subdirectory(test_server) set(LIBSSH_SERVER_TESTS - torture_server + torture_server_default torture_server_auth_kbdint torture_server_config torture_server_algorithms torture_sftpserver ) +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_SERVER_TESTS + ${LIBSSH_SERVER_TESTS} + torture_gssapi_server_auth + torture_gssapi_server_auth_cb + torture_gssapi_server_delegation + torture_gssapi_server_key_exchange + torture_gssapi_server_key_exchange_null + torture_gssapi_server_key_exchange_fallback) +endif() + include_directories(${libssh_SOURCE_DIR}/include ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} test_server) +set(TORTURE_SERVER_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_SERVER_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + if (ARGP_INCLUDE_DIR) include_directories(${ARGP_INCLUDE_DIR}) endif () @@ -41,7 +55,7 @@ foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) TEST ${_SRV_TEST} PROPERTY - ENVIRONMENT ${TORTURE_ENVIRONMENT}) + ENVIRONMENT ${TORTURE_SERVER_ENVIRONMENT}) endif() endforeach() diff --git a/tests/server/test_server/CMakeLists.txt b/tests/server/test_server/CMakeLists.txt index f1453d43..f109a47d 100644 --- a/tests/server/test_server/CMakeLists.txt +++ b/tests/server/test_server/CMakeLists.txt @@ -11,11 +11,11 @@ set(server_SRCS add_library(testserver STATIC test_server.c default_cb.c - sftpserver_cb.c) - -set(LIBSSH_SERVER_TESTS -# torture_server_kbdint -) + sftpserver_cb.c + testserver_common.c) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(testserver) +endif (WITH_COVERAGE) include_directories(${libssh_SOURCE_DIR}/include ${libssh_BINARY_DIR}/include @@ -29,10 +29,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_LIBRARIES} - util) + PRIVATE testserver ${TORTURE_LINK_LIBRARIES} ${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 a4ac81b0..10426f33 100644 --- a/tests/server/test_server/default_cb.c +++ b/tests/server/test_server/default_cb.c @@ -21,9 +21,11 @@ * MA 02111-1307, USA. */ + #include "config.h" #include "test_server.h" #include "default_cb.h" +#include "testserver_common.h" #include #include @@ -170,6 +172,65 @@ int auth_password_cb(UNUSED_PARAM(ssh_session session), return SSH_AUTH_DENIED; } +static int kbdint_check_response(ssh_session session, struct session_data_st *sdata) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp(sdata->username, answer); + if (cmp != 0) { + return 0; + } + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp(sdata->password, answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint_cb(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Get yourself authenticated"; + const char *prompts[2] = {"Username: ", "Password: "}; + char echo[] = {1, 0}; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + return SSH_AUTH_DENIED; + } + + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session, sdata)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + + return SSH_AUTH_DENIED; +} + #if WITH_GSSAPI int auth_gssapi_mic_cb(ssh_session session, UNUSED_PARAM(const char *user), @@ -193,7 +254,6 @@ int auth_gssapi_mic_cb(ssh_session session, printf("Received some gssapi credentials\n"); } else { printf("Not received any forwardable creds\n"); - goto denied; } printf("Authenticated\n"); @@ -203,8 +263,6 @@ int auth_gssapi_mic_cb(ssh_session session, return SSH_AUTH_SUCCESS; -denied: - sdata->auth_attempts++; null_userdata: return SSH_AUTH_DENIED; } @@ -451,9 +509,11 @@ static int exec_pty(const char *mode, case 0: close(cdata->pty_master); if (login_tty(cdata->pty_slave) != 0) { + finalize_openssl(); exit(1); } execl("/bin/sh", "sh", mode, command, NULL); + finalize_openssl(); exit(0); default: close(cdata->pty_slave); @@ -503,6 +563,7 @@ static int exec_nopty(const char *command, struct channel_data_st *cdata) close(err[1]); /* exec the requested command. */ execl("/bin/sh", "sh", "-c", command, NULL); + finalize_openssl(); exit(0); } @@ -781,6 +842,7 @@ struct ssh_server_callbacks_struct *get_default_server_cb(void) cb->auth_password_function = auth_password_cb; cb->auth_pubkey_function = auth_pubkey_cb; cb->channel_open_request_session_function = channel_new_session_cb; + cb->auth_kbdint_function = auth_kbdint_cb; #if WITH_GSSAPI cb->auth_gssapi_mic_function = auth_gssapi_mic_cb; #endif @@ -910,7 +972,9 @@ void default_handle_session_cb(ssh_event event, } else { ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | - SSH_AUTH_METHOD_PUBLICKEY); + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC); } ssh_event_add_session(event, session); @@ -925,7 +989,7 @@ void default_handle_session_cb(ssh_event event, if (ssh_event_dopoll(event, 100) == SSH_ERROR) { fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); - return; + goto end; } n++; } diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index 39c01223..bc4db359 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -59,6 +59,7 @@ struct arguments_st { char *password; char *config_file; + char *log_file; bool with_global_config; char *pid_file; }; @@ -66,7 +67,7 @@ struct arguments_st { static void free_arguments(struct arguments_st *arguments) { if (arguments == NULL) { - goto end; + return; } SAFE_FREE(arguments->address); @@ -84,10 +85,8 @@ static void free_arguments(struct arguments_st *arguments) SAFE_FREE(arguments->username); SAFE_FREE(arguments->password); SAFE_FREE(arguments->config_file); + SAFE_FREE(arguments->log_file); SAFE_FREE(arguments->pid_file); - -end: - return; } #ifdef HAVE_ARGP_H @@ -113,6 +112,9 @@ static void print_auth_methods(int auth_methods) if (auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { printf("\tSSH_AUTH_METHOD_GSSAPI_MIC\n"); } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + printf("\tSSH_AUTH_METHOD_GSSAPI_KEYEX\n"); + } } static void print_verbosity(int verbosity) @@ -174,6 +176,7 @@ static void print_server_state(struct server_state_st *state) state->parse_global_config? "TRUE": "FALSE"); printf("config_file = %s\n", state->config_file? state->config_file: "NULL"); + printf("log_file = %s\n", state->log_file ? state->log_file : "NULL"); printf("=================================================\n"); } } @@ -269,7 +272,9 @@ static int init_server_state(struct server_state_st *state, state->auth_methods = atoi(arguments->auth_methods); } else { state->auth_methods = SSH_AUTH_METHOD_PASSWORD | - SSH_AUTH_METHOD_PUBLICKEY; + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC; } state->with_pcap = arguments->with_pcap; @@ -291,12 +296,18 @@ static int init_server_state(struct server_state_st *state, } state->parse_global_config = arguments->with_global_config; + state->gssapi_key_exchange_algs = NULL; if (arguments->config_file) { state->config_file = arguments->config_file; arguments->config_file = NULL; } + if (arguments->log_file) { + state->log_file = arguments->log_file; + arguments->log_file = NULL; + } + /* TODO make configurable */ state->max_tries = 3; state->error = 0; @@ -440,6 +451,14 @@ static struct argp_option options[] = { .doc = "Use this server configuration file.", .group = 0 }, + { + .name = "log_file", + .key = 'l', + .arg = "LOG_FILE", + .flags = 0, + .doc = "Output log to this file.", + .group = 0 + }, { .name = NULL } }; @@ -553,6 +572,14 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) goto end; } break; + case 'l': + arguments->log_file = strdup(arg); + if (arguments->log_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { /* Too many arguments. */ diff --git a/tests/server/test_server/sftpserver_cb.c b/tests/server/test_server/sftpserver_cb.c index 6c58b262..7acf9d27 100644 --- a/tests/server/test_server/sftpserver_cb.c +++ b/tests/server/test_server/sftpserver_cb.c @@ -239,19 +239,9 @@ void sftp_handle_session_cb(ssh_event event, 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 + .sftp = NULL, }; /* Our struct holding information about the session. */ @@ -260,7 +250,7 @@ void sftp_handle_session_cb(ssh_event event, .auth_attempts = 0, .authenticated = 0, .username = SSHD_DEFAULT_USER, - .password = SSHD_DEFAULT_PASSWORD + .password = SSHD_DEFAULT_PASSWORD, }; struct ssh_channel_callbacks_struct *channel_cb = NULL; @@ -368,17 +358,11 @@ void sftp_handle_session_cb(ssh_event event, 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) { + if (ssh_event_dopoll(event, 100) == 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)); + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); ssh_channel_send_eof(sdata.channel); ssh_channel_close(sdata.channel); diff --git a/tests/server/test_server/test_server.c b/tests/server/test_server/test_server.c index 6d0f0808..69b6dac2 100644 --- a/tests/server/test_server/test_server.c +++ b/tests/server/test_server/test_server.c @@ -22,6 +22,7 @@ */ #include "test_server.h" +#include "testserver_common.h" #include #include @@ -56,6 +57,8 @@ void free_server_state(struct server_state_st *state) SAFE_FREE(state->expected_password); SAFE_FREE(state->config_file); SAFE_FREE(state->log_file); + SAFE_FREE(state->server_cb); + SAFE_FREE(state->channel_cb); } /* SIGCHLD handler for cleaning up dead children. */ @@ -123,12 +126,14 @@ int run_server(struct server_state_st *state) if (fd == -1) { fprintf(stderr, "dup2 of log file to stderr failed: %s\n", strerror(errno)); + fclose(f); goto out; } fd = dup2(fileno(f), STDOUT_FILENO); if (fd == -1) { fprintf(stderr, "dup2 of log file to stdout failed: %s\n", strerror(errno)); + fclose(f); goto out; } fclose(f); @@ -140,9 +145,8 @@ int run_server(struct server_state_st *state) } if (state->host_key == NULL && state->rsa_key == NULL && - state->ecdsa_key == NULL && state->ed25519_key) { + state->ecdsa_key == NULL && state->ed25519_key == NULL) { fprintf(stderr, "Missing host key\n"); - goto out; } sshbind = ssh_bind_new(); @@ -189,6 +193,30 @@ int run_server(struct server_state_st *state) goto out; } +#ifdef WITH_GSSAPI + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + &(state->gssapi_key_exchange)); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->gssapi_key_exchange_algs != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + state->gssapi_key_exchange_algs); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange algorithms: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } +#endif /* WITH_GSSAPI */ + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &(state->port)); @@ -284,6 +312,7 @@ int run_server(struct server_state_st *state) free_server_state(state); SAFE_FREE(state); + finalize_openssl(); exit(0); case -1: fprintf(stderr, "Failed to fork\n"); @@ -351,11 +380,8 @@ fork_run_server(struct server_state_st *state, /* The child process starts a server which will listen for connections */ rc = run_server(state); - if (rc != 0) { - exit(rc); - } - - exit(0); + finalize_openssl(); + exit(rc); case -1: strerror_r(errno, err_str, 1024); fprintf(stderr, "Failed to fork: %s\n", diff --git a/tests/server/test_server/test_server.h b/tests/server/test_server/test_server.h index 7c6bcb76..b4e17f69 100644 --- a/tests/server/test_server/test_server.h +++ b/tests/server/test_server/test_server.h @@ -53,6 +53,8 @@ struct server_state_st { bool parse_global_config; char *log_file; + bool gssapi_key_exchange; + const char *gssapi_key_exchange_algs; /* State */ int max_tries; diff --git a/tests/server/test_server/testserver_common.c b/tests/server/test_server/testserver_common.c new file mode 100644 index 00000000..c0970738 --- /dev/null +++ b/tests/server/test_server/testserver_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "testserver_common.h" + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +void finalize_openssl(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} diff --git a/tests/server/test_server/testserver_common.h b/tests/server/test_server/testserver_common.h new file mode 100644 index 00000000..01a9c57f --- /dev/null +++ b/tests/server/test_server/testserver_common.h @@ -0,0 +1,26 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +void finalize_openssl(void); diff --git a/tests/server/torture_gssapi_server_auth.c b/tests/server/torture_gssapi_server_auth.c new file mode 100644 index 00000000..8058d4d0 --- /dev/null +++ b/tests/server/torture_gssapi_server_auth.c @@ -0,0 +1,456 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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; + 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void**)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); + + /* Valid server identity option*/ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_gssapi_server_auth_cb.c b/tests/server/torture_gssapi_server_auth_cb.c new file mode 100644 index 00000000..381af800 --- /dev/null +++ b/tests/server/torture_gssapi_server_auth_cb.c @@ -0,0 +1,480 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/gssapi.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static ssh_string +select_oid(ssh_session session, + const char *user, + int n_oid, + ssh_string *oids, + void *userdata) +{ + /* Choose the first oid */ + return oids[0]; +} + +static int +accept_sec_ctx(ssh_session session, + ssh_string input_token, + ssh_string *output_token, + void *userdata) +{ + ssh_string token; + OM_uint32 min_stat; + gss_buffer_desc itoken, otoken = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_channel_bindings_t input_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + itoken.length = ssh_string_len(input_token); + itoken.value = ssh_string_data(input_token); + + gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &itoken, + input_bindings, + &client_name, + NULL /*mech_oid*/, + &otoken, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + + if (client_name != GSS_C_NO_NAME) { + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + token = ssh_string_new(otoken.length); + ssh_string_fill(token, otoken.value, otoken.length); + *output_token = token; + + gss_release_buffer(&min_stat, &otoken); + gss_release_name(&min_stat, &client_name); + SSH_STRING_FREE(input_token); + + return 0; +} + +static int +verify_mic(ssh_session session, + ssh_string mic, + void *mic_buffer, + size_t mic_buffer_size, + void *userdata) +{ + /* Verify without checking */ + return 0; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->gssapi_select_oid_function = select_oid; + ss->server_cb->gssapi_accept_sec_ctx_function = accept_sec_ctx; + ss->server_cb->gssapi_verify_mic_function = verify_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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); + + SAFE_FREE(tss->ss->server_cb); + 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; + 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_cb_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_gssapi_server_delegation.c b/tests/server/torture_gssapi_server_delegation.c new file mode 100644 index 00000000..dd3ad335 --- /dev/null +++ b/tests/server/torture_gssapi_server_delegation.c @@ -0,0 +1,376 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +auth_gssapi_mic(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + OM_uint32 min_stat; + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + assert_non_null(creds); + + gss_release_cred(&min_stat, creds); + + return SSH_AUTH_SUCCESS; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->auth_gssapi_mic_function = auth_gssapi_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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); + + SAFE_FREE(tss->ss->server_cb); + 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 = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_delegate_creds(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_delegate_creds, + session_setup, + session_teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_gssapi_server_key_exchange.c b/tests/server/torture_gssapi_server_key_exchange.c new file mode 100644 index 00000000..476786f3 --- /dev/null +++ b/tests/server/torture_gssapi_server_key_exchange.c @@ -0,0 +1,547 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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 = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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 = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_kex_is_gss(session->current_crypto)); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_server_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_server_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_server_key_exchange_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + bool f = false; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_gssapi_server_key_exchange_fallback.c b/tests/server/torture_gssapi_server_key_exchange_fallback.c new file mode 100644 index 00000000..99207e95 --- /dev/null +++ b/tests/server/torture_gssapi_server_key_exchange_fallback.c @@ -0,0 +1,334 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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 = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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 = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_fallback(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + "gss-group16-sha512-"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_fallback, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_gssapi_server_key_exchange_null.c b/tests/server/torture_gssapi_server_key_exchange_null.c new file mode 100644 index 00000000..9dc0c4e0 --- /dev/null +++ b/tests/server/torture_gssapi_server_key_exchange_null.c @@ -0,0 +1,340 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + 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); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* 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); + + /* 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; + + /* 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_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* 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"); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + torture_teardown_kdc_server((void **)&s); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + 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 = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = 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); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_null(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/tests/server/torture_server_algorithms.c b/tests/server/torture_server_algorithms.c index 3963e368..5f71de65 100644 --- a/tests/server/torture_server_algorithms.c +++ b/tests/server/torture_server_algorithms.c @@ -54,6 +54,7 @@ static int setup_files(void **state) struct test_server_st *tss; struct torture_state *s; char sshd_path[1024]; + char log_file[1024]; int rc; @@ -74,12 +75,17 @@ static int setup_files(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(tss->rsa_hostkey, sizeof(tss->rsa_hostkey), "%s/sshd/ssh_host_rsa_key", s->socket_dir); torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + /* not to mix up the client and server messages */ + s->log_file = strdup(log_file); + tss->state = s; *state = tss; @@ -257,7 +263,7 @@ static void test_algorithm_no_hmac_overlap(void **state, const char *algorithm) s = tss->state; assert_non_null(s); - /* Prepare key files */ + /* Prepare config file */ snprintf(config_content, sizeof(config_content), "HostKey %s\nCiphers %s\nMACs %s\n", @@ -268,9 +274,10 @@ static void test_algorithm_no_hmac_overlap(void **state, const char *algorithm) assert_non_null(s->srv_config); torture_write_file(s->srv_config, config_content); - fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, config_content); - fflush(stderr); /* Start server */ rc = start_server(state); @@ -339,6 +346,87 @@ static void torture_algorithm_aes128gcm_with_no_hmac_overlap(void **state) test_algorithm_no_hmac_overlap(state, "aes128-gcm@openssh.com"); } +/* + * Check the self-compatibility of a given key exchange method. + */ +static void test_kex_self_compat(void **state, const char *kex) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char config_content[4096]; + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare config file */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + tss->rsa_hostkey, + kex); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, + config_content); + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + 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_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); +} + +static void torture_algorithm_mlkem768x25119_self_compat(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_kex_self_compat(state, "mlkem768x25519-sha256"); +} + +static void torture_algorithm_mlkem768nistp256_self_compat(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_kex_self_compat(state, "mlkem768nistp256-sha256"); +} + +#ifdef HAVE_MLKEM1024 +static void torture_algorithm_mlkem1024nistp384_self_compat(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_kex_self_compat(state, "mlkem1024nistp384-sha384"); +} +#endif /* HAVE_MLKEM1024 */ + int torture_run_tests(void) { int rc; @@ -349,6 +437,14 @@ int torture_run_tests(void) setup_temp_dir, teardown_temp_dir), cmocka_unit_test_setup_teardown(torture_algorithm_aes128gcm_with_no_hmac_overlap, setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768x25119_self_compat, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768nistp256_self_compat, + setup_temp_dir, teardown_temp_dir), +#ifdef HAVE_MLKEM1024 + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem1024nistp384_self_compat, + setup_temp_dir, teardown_temp_dir), +#endif /* HAVE_MLKEM1024 */ }; ssh_init(); diff --git a/tests/server/torture_server_auth_kbdint.c b/tests/server/torture_server_auth_kbdint.c index 1d5eb733..9beb7289 100644 --- a/tests/server/torture_server_auth_kbdint.c +++ b/tests/server/torture_server_auth_kbdint.c @@ -375,6 +375,22 @@ static void handle_kbdint_session_cb(ssh_event event, goto end; } + /* + * This test was written prior to adding the kbdint callback + * for the server. Hence, here the server uses the + * ssh_message_callback for kbdint authentication, + * instead of the kbdint callback. + * + * Setting the kbdint callback as NULL ensures that the + * default kbdint callback for test_server doesn't get used + * for kbdint authentication. + * + * The test for kbdint callback based authentication has + * been added in torture_server.c, libssh keeps this test to + * test the old way of doing kbdint authentication using + * ssh_message_callback. + */ + server_cb->auth_kbdint_function = NULL; server_cb->userdata = &sdata; /* This is a macro, it does not return a value */ diff --git a/tests/server/torture_server_config.c b/tests/server/torture_server_config.c index 0bb11ec0..c3c5f3a5 100644 --- a/tests/server/torture_server_config.c +++ b/tests/server/torture_server_config.c @@ -226,7 +226,7 @@ static int session_setup(void **state) struct test_server_st *tss = *state; struct torture_state *s; int verbosity = torture_libssh_verbosity(); - const char *compat_hostkeys = ssh_kex_get_supported_method(SSH_HOSTKEYS); + const char *compat_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); struct passwd *pwd; bool b = false; int rc; diff --git a/tests/server/torture_server.c b/tests/server/torture_server_default.c similarity index 78% rename from tests/server/torture_server.c rename to tests/server/torture_server_default.c index f6b9dea4..a3c3d9da 100644 --- a/tests/server/torture_server.c +++ b/tests/server/torture_server_default.c @@ -39,6 +39,8 @@ #include "test_server.h" #include "default_cb.h" +#include "channels.c" + #define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" const char template[] = "temp_dir_XXXXXX"; @@ -54,6 +56,8 @@ static int libssh_server_setup(void **state) struct test_server_st *tss = NULL; struct torture_state *s = NULL; + char log_file[1024]; + assert_non_null(state); tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); @@ -62,6 +66,13 @@ static int libssh_server_setup(void **state) torture_setup_socket_dir((void **)&s); torture_setup_create_libssh_config((void **)&s); + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + s->log_file = strdup(log_file); + /* The second argument is the relative path to the "server" directory binary */ torture_setup_libssh_server((void **)&s, "./test_server/test_server"); @@ -276,6 +287,76 @@ static void torture_server_auth_pubkey(void **state) assert_int_equal(rc, SSH_AUTH_SUCCESS); } +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, "wrongpassword"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong username */ + rc = ssh_userauth_kbdint_setanswer(session, 0, "wrongusername"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing the right password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + static void torture_server_hostkey_mismatch(void **state) { struct test_server_st *tss = *state; @@ -370,6 +451,51 @@ static void torture_server_unknown_global_request(void **state) ssh_channel_close(channel); } +static void torture_server_unknown_channel_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Open a channel session */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = channel_request(channel, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = channel_request(channel, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); + ssh_channel_free(channel); +} + static void torture_server_no_more_sessions(void **state) { struct test_server_st *tss = *state; @@ -415,6 +541,7 @@ static void torture_server_no_more_sessions(void **state) assert_int_equal(rc, SSH_ERROR); /* Free the unused channel */ + ssh_channel_close(channels[1]); ssh_channel_free(channels[1]); /* Close and free open channel */ @@ -494,12 +621,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_auth_pubkey, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_server_hostkey_mismatch, session_setup, session_teardown), cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_channel_request, + session_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_server_no_more_sessions, session_setup, session_teardown), diff --git a/tests/server/torture_sftpserver.c b/tests/server/torture_sftpserver.c index 275f4508..5bf392c1 100644 --- a/tests/server/torture_sftpserver.c +++ b/tests/server/torture_sftpserver.c @@ -31,11 +31,17 @@ #include #include -#include "torture.h" -#include "torture_key.h" +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "libssh/buffer.h" #include "libssh/libssh.h" #include "libssh/priv.h" #include "libssh/session.h" +#include "libssh/sftp_priv.h" +#include "torture.h" +#include "torture_key.h" #include "test_server.h" #include "default_cb.h" @@ -229,8 +235,10 @@ static int session_setup(void **state) struct test_server_st *tss = *state; struct torture_state *s; int verbosity = torture_libssh_verbosity(); + char template2[] = "/tmp/ssh_torture_XXXXXX"; char *cwd = NULL; char *tmp_dir = NULL; + char *p = NULL; bool b = false; int rc; @@ -243,6 +251,8 @@ static int session_setup(void **state) assert_non_null(cwd); tmp_dir = torture_make_temp_dir(template); + p = mkdtemp(template2); + assert_non_null(p); assert_non_null(tmp_dir); tss->cwd = cwd; @@ -256,6 +266,8 @@ static int session_setup(void **state) s->ssh.tsftp = (struct torture_sftp*)calloc(1, sizeof(struct torture_sftp)); assert_non_null(s->ssh.tsftp); + s->ssh.tsftp->testdir = strdup(p); + assert_non_null(s->ssh.tsftp->testdir); rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); assert_ssh_return_code(s->ssh.session, rc); @@ -271,10 +283,10 @@ static int session_setup(void **state) 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; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; int rc; assert_non_null(tss); @@ -310,7 +322,6 @@ static int session_setup_sftp(void **state) /* 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; @@ -332,6 +343,7 @@ static int session_teardown(void **state) s = tss->state; assert_non_null(s); + SAFE_FREE(s->ssh.tsftp->testdir); sftp_free(s->ssh.tsftp->sftp); SAFE_FREE(s->ssh.tsftp); @@ -392,7 +404,6 @@ static void torture_server_establish_sftp(void **state) /* init sftp session */ tsftp = s->ssh.tsftp; - sftp = tsftp->sftp; printf("in establish before sftp_new\n"); sftp = sftp_new(session); @@ -448,8 +459,6 @@ static void torture_server_test_sftp_function(void **state) 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); @@ -501,10 +510,10 @@ static void torture_server_test_sftp_function(void **state) 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; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + sftp_session sftp = NULL; + ssh_session session = NULL; sftp_attributes a = NULL; sftp_file new_file = NULL; char tmp_file[PATH_MAX] = {0}; @@ -560,6 +569,30 @@ static void torture_server_sftp_open_read_write(void **state) assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); sftp_attributes_free(a); + /* + * Now that file exists and contains some data, lets try O_TRUNC, + * mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_TRUNC, 0); + assert_non_null(new_file); + + /* Verify that the existing data in the file has been truncated */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->size, 0); /* No content due to truncation */ + sftp_attributes_free(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 O_APPEND, mode is ignored */ @@ -973,6 +1006,260 @@ static void torture_server_sftp_extended(void **state) assert_int_equal(rc, SSH_OK); } +static void +torture_server_sftp_setstat(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + 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); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_stat(sftp, name); + assert_non_null(tmp_attr); + + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + + assert_int_equal(len, tmp_attr->size); + assert_int_equal(tmp_attr->permissions & ACCESSPERMS, mode); + assert_int_equal(tmp_attr->mtime, mtime); + assert_int_equal(tmp_attr->atime, atime); + + /*negative tests*/ + rc = sftp_setstat(sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + sftp_unlink(sftp, name); + sftp_attributes_free(tmp_attr); +} + +/* The max number of handles is 256 in sftpserver.h -- keep in sync! */ +#define SFTP_HANDLES 256 +static void torture_server_sftp_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handle, handles[SFTP_HANDLES] = {0}; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Next handle should fail, but not crash or OOB */ + snprintf(name, sizeof(name), "%s/failfn", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_null(handle); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_handle_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_handle = SFTP_HANDLES, bad_handle_len = 4; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Initialize the sftp handles by opening first file */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + /* Craft an malicious SFTP packet trying to access handle 256 + * (SFTP_HANDLES) */ + buffer = ssh_buffer_new(); + id = sftp_get_new_id(sftp); + assert_non_null(buffer); + + rc = ssh_buffer_pack(buffer, + "ddPqd", + id, + (uint32_t)bad_handle_len, + (size_t)bad_handle_len, + &bad_handle, /* a 32b int as ssh_string */ + (uint64_t)0, + (uint32_t)1024); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 29); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + assert_int_equal(rc, SSH_OK); + assert_int_equal(msg->packet_type, SSH_FXP_STATUS); + status = parse_status_msg(msg); + sftp_message_free(msg); + assert_int_equal(status->status, SSH_FX_INVALID_HANDLE); + status_msg_free(status); + + rc = sftp_close(handle); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_payload_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_payload_len = 0x7ffffffc; + int rc; + +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + /* This malformed message does not crash the server, but keeps waiting + * for more data as announced in the payloiad length so the opened FD on + * the server side is leaking when the server terminates. + * Given that the custom sftp server could store anything into the + * handles, it should take care of cleaning up the outstanding handles, + * but this is something to solve in the future. Now just skipping the + * test. + */ + skip(); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Open a file for writing */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + /* Craft an malicious SFTP packet trying to write to the file with + * payload_length overrun */ + buffer = ssh_buffer_new(); + id = sftp_get_new_id(sftp); + assert_non_null(buffer); + + rc = ssh_buffer_pack(buffer, + "dbdSqd", + bad_payload_len, + SSH_FXP_WRITE, + id, + handle->handle, + (uint64_t)0, + (uint32_t)0); + assert_int_equal(rc, SSH_OK); + rc = ssh_channel_write(sftp->channel, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + assert_int_equal(rc, 29); + SSH_BUFFER_FREE(buffer); + + /* We do not get answer for this malformed packet -- just kill the + * connection */ + ssh_string_free(handle->handle); + free(handle); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -997,6 +1284,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_sftp_extended, session_setup_sftp, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_setstat, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_payload_overrun, + session_setup_sftp, + session_teardown), }; ssh_init(); diff --git a/tests/ssh_ping.c b/tests/ssh_ping.c index 01754590..31bb64b4 100644 --- a/tests/ssh_ping.c +++ b/tests/ssh_ping.c @@ -27,7 +27,11 @@ int main(int argc, char **argv) const char *banner = NULL; ssh_session session = NULL; const char *hostkeys = NULL; + const char *kex = NULL; int rc = 1; +#ifdef WITH_GSSAPI + bool t = true; +#endif /* WITH_GSSAPI */ bool process_config = false; @@ -61,12 +65,26 @@ int main(int argc, char **argv) } /* Enable all supported algorithms */ - hostkeys = ssh_kex_get_supported_method(SSH_HOSTKEYS); + hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkeys); if (rc < 0) { goto out; } + /* Enable all supported kex algorithms */ + kex = ssh_get_supported_methods(SSH_KEX); + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + if (rc < 0) { + goto out; + } + +#ifdef WITH_GSSAPI + rc = ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + if (rc < 0) { + goto out; + } +#endif /* WITH_GSSAPI */ + rc = ssh_connect(session); if (rc != SSH_OK) { fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); diff --git a/tests/suppressions/lsan.supp b/tests/suppressions/lsan.supp index 42a17e35..dc95dfa5 100644 --- a/tests/suppressions/lsan.supp +++ b/tests/suppressions/lsan.supp @@ -1 +1,6 @@ leak:libcrypto.so + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +leak:sk-dummy.so diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake index bb9164bd..f448df63 100644 --- a/tests/tests_config.h.cmake +++ b/tests/tests_config.h.cmake @@ -2,6 +2,7 @@ #cmakedefine OPENSSH_VERSION_MAJOR ${OPENSSH_VERSION_MAJOR} #cmakedefine OPENSSH_VERSION_MINOR ${OPENSSH_VERSION_MINOR} +#cmakedefine OPENSSH_SUPPORTS_SSHSIG ${OPENSSH_SUPPORTS_SSHSIG} #cmakedefine OPENSSH_CIPHERS "${OPENSSH_CIPHERS}" #cmakedefine OPENSSH_MACS "${OPENSSH_MACS}" @@ -49,6 +50,11 @@ #cmakedefine OPENSSH_ECDH_SHA2_NISTP521 1 #cmakedefine OPENSSH_CURVE25519_SHA256 1 #cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1 +#cmakedefine OPENSSH_MLKEM768X25519_SHA256 1 +#cmakedefine OPENSSH_MLKEM768NISTP256_SHA256 1 +#cmakedefine OPENSSH_MLKEM1024NISTP384_SHA384 1 #cmakedefine OPENSSH_SSH_ED25519 1 #cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1 #cmakedefine OPENSSH_SSH_RSA 1 @@ -59,13 +65,24 @@ #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 +#cmakedefine OPENSSH_SK_SSH_ED25519_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_SSH_ED25519_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 /* Available programs */ #cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" #cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}" +#cmakedefine SSH_KEYGEN_EXECUTABLE "${SSH_KEYGEN_EXECUTABLE}" +#cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}" +#cmakedefine PUTTY_EXECUTABLE "${PUTTY_EXECUTABLE}" +#cmakedefine PUTTYGEN_EXECUTABLE "${PUTTYGEN_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}" +#cmakedefine HAVE_SK_DUMMY 1 +#cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}" diff --git a/tests/torture.c b/tests/torture.c index 2641b2bf..0c5a218f 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -23,17 +23,17 @@ #include "config.h" #include "tests_config.h" +#include +#include #include #include -#include #include -#include -#include +#include #ifndef _WIN32 -# include -# include -# include +#include +#include +#include #endif #ifdef HAVE_UNISTD_H @@ -43,41 +43,54 @@ #define chdir _chdir #endif -#include "torture.h" -#include "torture_key.h" +#include "libssh/libssh.h" #include "libssh/misc.h" #include "libssh/token.h" +#include "torture.h" +#include "torture_key.h" #ifdef HAVE_VALGRIND_VALGRIND_H #include #endif +#ifdef WITH_GSSAPI +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + #define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" +#define TORTURE_SSHD_SRV1_IPV4 "127.0.0.11" /* socket wrapper IPv6 prefix fd00::5357:5fxx */ #define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" +#define TORTURE_SSHD_SRV1_IPV6 "fd00::5357:5f0b" #define TORTURE_SSHD_SRV_PORT 22 +#define TORTURE_SSHD_SRV_IFACE "10" +#define TORTURE_SSHD_SRV1_IFACE "11" #define TORTURE_SOCKET_DIR "/tmp/test_socket_wrapper_XXXXXX" #define TORTURE_SSHD_PIDFILE "sshd/sshd.pid" #define TORTURE_SSHD_CONFIG "sshd/sshd_config" +#define TORTURE_SSHD1_PIDFILE "sshd1/sshd.pid" +#define TORTURE_SSHD1_CONFIG "sshd1/sshd_config" #define TORTURE_PCAP_FILE "socket_trace.pcap" -static const char torture_rsa_certauth_pub[]= - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" - "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" - "SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTG" - "kgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691je" - "IQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbY" - "K06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvF " - "torture_certauth_key"; +static const char torture_rsa_certauth_pub[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" + "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" + "SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTG" + "kgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691je" + "IQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbY" + "K06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvF " + "torture_certauth_key"; static int verbosity = 0; static const char *pattern = NULL; #ifndef _WIN32 -static int _torture_auth_kbdint(ssh_session session, - const char *password) { +/* TODO missing code coverage */ +static int _torture_auth_kbdint(ssh_session session, const char *password) +{ const char *prompt; char echo; int err; @@ -114,34 +127,35 @@ static int _torture_auth_kbdint(ssh_session session, return err; } -int torture_rmdirs(const char *path) { +int torture_rmdirs(const char *path) +{ DIR *d; struct dirent *dp; struct stat sb; char *fname; if ((d = opendir(path)) != NULL) { - while(stat(path, &sb) == 0) { + while (stat(path, &sb) == 0) { /* if we can remove the directory we're done */ if (rmdir(path) == 0) { break; } switch (errno) { - case ENOTEMPTY: - case EEXIST: - case EBADF: - break; /* continue */ - default: - closedir(d); - return 0; + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + closedir(d); + return 0; } while ((dp = readdir(d)) != NULL) { size_t len; /* skip '.' and '..' */ if (dp->d_name[0] == '.' && - (dp->d_name[1] == '\0' || - (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { + (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { continue; } @@ -181,18 +195,18 @@ int torture_rmdirs(const char *path) { return 0; } -int torture_isdir(const char *path) { +int torture_isdir(const char *path) +{ struct stat sb; - if (lstat (path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + if (lstat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { return 1; } return 0; } -static pid_t -torture_read_pidfile(const char *pidfile) +static pid_t torture_read_pidfile(const char *pidfile) { char buf[8] = {0}; long int tmp; @@ -228,6 +242,7 @@ torture_read_pidfile(const char *pidfile) int torture_terminate_process(const char *pidfile) { +#ifndef WIN32 ssize_t rc; pid_t pid; int is_running = 1; @@ -235,20 +250,25 @@ int torture_terminate_process(const char *pidfile) /* read the pidfile */ pid = torture_read_pidfile(pidfile); + if (pid == -1) { + fprintf(stderr, "Failed to read PID file %s\n", pidfile); + return -1; + } assert_int_not_equal(pid, -1); - for (count = 0; count < 10; count++) { + for (count = 0; count < 500; count++) { /* Make sure the daemon goes away! */ kill(pid, SIGTERM); - /* 10 ms */ - usleep(10 * 1000); + /* 25 ms */ + usleep(25 * 1000); #ifdef HAVE_VALGRIND_VALGRIND_H if (RUNNING_ON_VALGRIND) { - SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, wait one more " + 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); @@ -264,17 +284,23 @@ int torture_terminate_process(const char *pidfile) if (is_running) { fprintf(stderr, - "WARNING: The process with pid %u is still running!\n", pid); + "WARNING: The process with pid %u is still running!\n", + pid); } return rc; +#else + (void)pidfile; + return -1; /* Stub implementation for Windows */ +#endif } ssh_session torture_ssh_session(struct torture_state *s, const char *host, const unsigned int *port, const char *user, - const char *password) { + const char *password) +{ ssh_session session; int method; int rc; @@ -305,9 +331,9 @@ ssh_session torture_ssh_session(struct torture_state *s, } if (port != NULL) { - if (ssh_options_set(session, SSH_OPTIONS_PORT, port) < 0) { - goto failed; - } + if (ssh_options_set(session, SSH_OPTIONS_PORT, port) < 0) { + goto failed; + } } if (user != NULL) { @@ -316,8 +342,8 @@ ssh_session torture_ssh_session(struct torture_state *s, } } - if (ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, - &process_config) < 0) { + if (ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config) < + 0) { goto failed; } @@ -363,12 +389,150 @@ ssh_session torture_ssh_session(struct torture_state *s, return NULL; } +/* always return verification successful */ +static int verify_knownhost_trust_all(UNUSED_PARAM(ssh_session jump_session), + UNUSED_PARAM(void *user)) +{ + return SSH_OK; +} + +/** + * @brief Create a session connected to server via proxyjump + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * + * @warning It is expected that both sshd servers are setup before calling + * this, see torture_setup_sshd_server() and + * torture_setup_sshd_servers() + * + * TODO: If needed, in future, we can extend this function to: + * - allow caller to pass server host port, user, password similar to + * torture_ssh_session() or club this with that function + * + * - allow caller to customize jump hosts and callbacks for each of them + */ +ssh_session torture_ssh_session_proxyjump(void) +{ + /* + * We'll setup the connection chain: + * - client + * - jump host 1: doe (sshd server, IPV4) + * - jump host 2: alice (sshd server1, IPV6) + * - server: alice (sshd server, IPV4) + */ + char jump_host_list[1024] = {0}; + int jump_host_count = 2; + const char *jump_host_1_address = torture_server_address(AF_INET); + const char *jump_host_2_address = torture_server1_address(AF_INET6); + struct ssh_jump_callbacks_struct jump_host_callbacks = { + .before_connection = NULL, + .verify_knownhost = verify_knownhost_trust_all, + .authenticate = NULL, + }; + + ssh_session session = NULL; + bool process_config = false; + int rc, i; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to create new ssh session\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + if (rc < 0) { + fprintf(stderr, + "Failed to set session host: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + if (rc < 0) { + fprintf(stderr, + "Failed to set session user: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + fprintf(stderr, + "Failed to set process config option: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = snprintf(jump_host_list, + sizeof(jump_host_list), + "doe@%s:22,alice@%s:22", + jump_host_1_address, + jump_host_2_address); + if (rc < 0) { + fprintf(stderr, "snprintf failed: %s\n", strerror(errno)); + goto failed; + } + + if (rc >= (int)sizeof(jump_host_list)) { + fprintf(stderr, "Insufficient jump host list buffer size\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, jump_host_list); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump hosts for the session: %s\n", + ssh_get_error(session)); + goto failed; + } + + for (i = 0; i < jump_host_count; ++i) { + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + &jump_host_callbacks); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump callbacks for jump host %d: %s\n", + i + 1, + ssh_get_error(session)); + goto failed; + } + } + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, + "Failed to connect to ssh server: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Public key authentication did not succeed\n"); + goto failed; + } + + return session; + +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + #ifdef WITH_SERVER ssh_bind torture_ssh_bind(const char *addr, const unsigned int port, enum ssh_keytypes_e key_type, - const char *private_key_file) { + const char *private_key_file) +{ int rc; ssh_bind sshbind = NULL; enum ssh_bind_options_e opts = -1; @@ -388,16 +552,23 @@ ssh_bind torture_ssh_bind(const char *addr, goto out_free; } + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); + if (rc < 0) { + goto out_free; + } + switch (key_type) { - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_ED25519: - opts = SSH_BIND_OPTIONS_HOSTKEY; - break; - default: - goto out_free; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + opts = SSH_BIND_OPTIONS_HOSTKEY; + break; + default: + goto out_free; } rc = ssh_bind_options_set(sshbind, opts, private_key_file); @@ -411,10 +582,10 @@ ssh_bind torture_ssh_bind(const char *addr, } goto out; - out_free: +out_free: ssh_bind_free(sshbind); sshbind = NULL; - out: +out: return sshbind; } @@ -422,7 +593,8 @@ ssh_bind torture_ssh_bind(const char *addr, #ifdef WITH_SFTP -struct torture_sftp *torture_sftp_session_channel(ssh_session session, ssh_channel channel) +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel) { struct torture_sftp *t; char template[] = "/tmp/ssh_torture_XXXXXX"; @@ -471,7 +643,7 @@ struct torture_sftp *torture_sftp_session_channel(ssh_session session, ssh_chann goto failed; } /* useful if TESTUSER is not the local user */ - chmod(template,0777); + chmod(template, 0777); t->testdir = strdup(p); if (t->testdir == NULL) { goto failed; @@ -494,7 +666,8 @@ struct torture_sftp *torture_sftp_session(ssh_session session) return torture_sftp_session_channel(session, NULL); } -void torture_sftp_close(struct torture_sftp *t) { +void torture_sftp_close(struct torture_sftp *t) +{ if (t == NULL) { return; } @@ -555,12 +728,27 @@ const char *torture_server_address(int family) return NULL; } +const char *torture_server1_address(int family) +{ + switch (family) { + case AF_INET: + return TORTURE_SSHD_SRV1_IPV4; + case AF_INET6: + return TORTURE_SSHD_SRV1_IPV6; + default: + return NULL; + } + + return NULL; +} + void torture_setup_socket_dir(void **state) { struct torture_state *s; const char *p; size_t len; char *env = NULL; + char gss_dir[1024] = {0}; int rc; s = calloc(1, sizeof(struct torture_state)); @@ -580,6 +768,13 @@ void torture_setup_socket_dir(void **state) s->socket_dir = torture_make_temp_dir(TORTURE_SOCKET_DIR); assert_non_null(s->socket_dir); +#ifdef WITH_GSSAPI + snprintf(gss_dir, sizeof(gss_dir), "%s/gss", s->socket_dir); + rc = mkdir(gss_dir, 0755); + assert_return_code(rc, errno); + s->gss_dir = strdup(gss_dir); +#endif + p = s->socket_dir; /* pcap file */ @@ -606,6 +801,8 @@ void torture_setup_socket_dir(void **state) snprintf(s->srv_config, len, "%s/%s", p, TORTURE_SSHD_CONFIG); + s->disable_hostkeys = false; + setenv("SOCKET_WRAPPER_DIR", p, 1); setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "170", 1); env = getenv("TORTURE_GENERATE_PCAP"); @@ -616,6 +813,32 @@ void torture_setup_socket_dir(void **state) *state = s; } +static void torture_setup_second_sshd_dir(void **state) +{ + struct torture_state *s = *state; + size_t len; + + /* pid file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_PIDFILE) + 1; + + s->srv1_pidfile = malloc(len); + assert_non_null(s->srv1_pidfile); + + snprintf(s->srv1_pidfile, + len, + "%s/%s", + s->socket_dir, + TORTURE_SSHD1_PIDFILE); + + /* config file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_CONFIG) + 1; + + s->srv1_config = malloc(len); + assert_non_null(s->srv1_config); + + snprintf(s->srv1_config, len, "%s/%s", s->socket_dir, TORTURE_SSHD1_CONFIG); +} + /** * @brief Create a libssh server configuration file * @@ -637,26 +860,30 @@ void torture_setup_create_libssh_config(void **state) char sshd_path[1024]; const char *additional_config = NULL; struct stat sb; - const char config_string[]= - "LogLevel DEBUG3\n" - "Port 22\n" - "ListenAddress 127.0.0.10\n" - "%s %s\n" - "%s %s\n" - "%s %s\n" - "%s\n"; /* The space for test-specific options */ + const char config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ + const char fips_config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ bool written = false; int rc; assert_non_null(s->socket_dir); - snprintf(sshd_path, - sizeof(sshd_path), - "%s/sshd", - s->socket_dir); + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); rc = lstat(sshd_path, &sb); - if (rc == 0 ) { /* The directory is already in place */ + if (rc == 0) { /* The directory is already in place */ written = true; } @@ -689,21 +916,32 @@ void torture_setup_create_libssh_config(void **state) torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); } - additional_config = (s->srv_additional_config != NULL ? - s->srv_additional_config : ""); + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); - snprintf(sshd_config, sizeof(sshd_config), - config_string, - "HostKey", ed25519_hostkey, - "HostKey", rsa_hostkey, - "HostKey", ecdsa_hostkey, - additional_config); + if (ssh_fips_mode()) { + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + ed25519_hostkey, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } torture_write_file(s->srv_config, sshd_config); } #ifdef SSHD_EXECUTABLE -static void torture_setup_create_sshd_config(void **state, bool pam) +static void +torture_setup_create_sshd_config(void **state, bool pam, bool second_sshd) { struct torture_state *s = *state; char ed25519_hostkey[1024] = {0}; @@ -719,98 +957,106 @@ static void torture_setup_create_sshd_config(void **state, bool pam) "/usr/libexec/ssh/sftp-server", /* Tumbleweed 20200829 */ "/usr/libexec/sftp-server", "/usr/libexec/openssh/sftp-server", - "/usr/lib/openssh/sftp-server", /* Debian */ + "/usr/lib/openssh/sftp-server", /* Debian */ }; - const char config_string[]= - "Port 22\n" - "ListenAddress 127.0.0.10\n" - "ListenAddress fd00::5357:5f0a\n" - "%s %s\n" /* ed25519 HostKey */ - "%s %s\n" /* RSA HostKey */ - "%s %s\n" /* ECDSA HostKey */ - "\n" - "TrustedUserCAKeys %s\n" - "\n" - "LogLevel DEBUG3\n" - "Subsystem sftp %s -l DEBUG2\n" - "\n" - "PasswordAuthentication yes\n" - "PubkeyAuthentication yes\n" - "\n" - "StrictModes no\n" - "\n" - "%s\n" /* Here comes UsePam */ - "%s" /* The space for test-specific options */ - "\n" - /* add all supported algorithms */ - "HostKeyAlgorithms " OPENSSH_KEYS "\n" + const char config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* ed25519 HostKey */ + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" + /* add all supported algorithms */ + "HostKeyAlgorithms " OPENSSH_KEYS "\n" #if OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR >= 2 - "CASignatureAlgorithms " OPENSSH_KEYS "\n" + "CASignatureAlgorithms " OPENSSH_KEYS "\n" +#endif +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" #endif - "Ciphers " OPENSSH_CIPHERS "\n" - "KexAlgorithms " OPENSSH_KEX "\n" - "MACs " OPENSSH_MACS "\n" - "\n" - "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES\n" - "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" - "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" - "\n" - "PidFile %s\n"; + "Ciphers " OPENSSH_CIPHERS "\n" + "KexAlgorithms " OPENSSH_KEX "\n" + "MACs " OPENSSH_MACS "\n" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; /* FIPS config */ - 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" - "TrustedUserCAKeys %s\n" /* Trusted CA */ - "\n" - "LogLevel DEBUG3\n" - "Subsystem sftp %s -l DEBUG2\n" /* SFTP server */ - "\n" - "PasswordAuthentication yes\n" - "PubkeyAuthentication yes\n" - "\n" - "StrictModes no\n" - "\n" - "%s\n" /* Here comes UsePam */ - "%s" /* The space for test-specific options */ - "\n" - "Ciphers " - "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc," - "aes128-gcm@openssh.com,aes128-ctr,aes128-cbc" - "\n" - "MACs " - "hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com," - "hmac-sha2-512-etm@openssh.com,hmac-sha2-256," - "hmac-sha1,hmac-sha2-512" - "\n" - "GSSAPIKeyExchange no\n" - "KexAlgorithms " - "ecdh-sha2-nistp256,ecdh-sha2-nistp384," - "ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256," - "diffie-hellman-group14-sha256,diffie-hellman-group16-sha512," - "diffie-hellman-group18-sha512" - "\n" - "PubkeyAcceptedKeyTypes " - "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com," - "ecdsa-sha2-nistp256,ecdsa-sha2-nistp256-cert-v01@openssh.com," - "ecdsa-sha2-nistp384,ecdsa-sha2-nistp384-cert-v01@openssh.com," - "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com," - "ecdsa-sha2-nistp521,ecdsa-sha2-nistp521-cert-v01@openssh.com" - "\n" - "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES\n" - "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" - "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" - "\n" - "PidFile %s\n"; /* PID file */ - const char usepam_yes[] = - "UsePAM yes\n" - "KbdInteractiveAuthentication yes\n"; - const char usepam_no[] = - "UsePAM no\n" - "KbdInteractiveAuthentication no\n"; + const char fips_config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" /* Trusted CA */ + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" /* SFTP server */ + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" +#endif + "Ciphers " + "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc," + "aes128-gcm@openssh.com,aes128-ctr,aes128-cbc" + "\n" + "MACs " + "hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com,hmac-sha2-256," + "hmac-sha1,hmac-sha2-512" + "\n" + "GSSAPIKeyExchange no\n" + "KexAlgorithms " + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512" + "\n" + "PubkeyAcceptedKeyTypes " + "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp256-cert-v01@openssh.com," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp384-cert-v01@openssh.com," + "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp521-cert-v01@openssh.com" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; + const char usepam_yes[] = "UsePAM yes\n" + "KbdInteractiveAuthentication yes\n"; + const char usepam_no[] = "UsePAM no\n" + "KbdInteractiveAuthentication no\n"; size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); const char *sftp_server, *usepam; size_t i; @@ -828,11 +1074,12 @@ static void torture_setup_create_sshd_config(void **state, bool pam) snprintf(sshd_path, sizeof(sshd_path), - "%s/sshd", - s->socket_dir); + "%s/sshd%s", + s->socket_dir, + second_sshd ? "1" : ""); rc = lstat(sshd_path, &sb); - if (rc == 0 ) { /* The directory is already in place */ + if (rc == 0) { /* The directory is already in place */ written = true; } @@ -841,25 +1088,29 @@ static void torture_setup_create_sshd_config(void **state, bool pam) assert_return_code(rc, errno); } - snprintf(ed25519_hostkey, - sizeof(ed25519_hostkey), - "%s/sshd/ssh_host_ed25519_key", - s->socket_dir); + rc = snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/ssh_host_ed25519_key", + sshd_path); + assert_true(rc >= 0); - snprintf(rsa_hostkey, - sizeof(rsa_hostkey), - "%s/sshd/ssh_host_rsa_key", - s->socket_dir); + rc = snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/ssh_host_rsa_key", + sshd_path); + assert_true(rc >= 0); - snprintf(ecdsa_hostkey, - sizeof(ecdsa_hostkey), - "%s/sshd/ssh_host_ecdsa_key", - s->socket_dir); + rc = snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/ssh_host_ecdsa_key", + sshd_path); + assert_true(rc >= 0); - snprintf(trusted_ca_pubkey, - sizeof(trusted_ca_pubkey), - "%s/sshd/user_ca.pub", - s->socket_dir); + rc = snprintf(trusted_ca_pubkey, + sizeof(trusted_ca_pubkey), + "%s/user_ca.pub", + sshd_path); + assert_true(rc >= 0); if (!written) { torture_write_file(ed25519_hostkey, @@ -883,33 +1134,43 @@ static void torture_setup_create_sshd_config(void **state, bool pam) } assert_non_null(sftp_server); - additional_config = (s->srv_additional_config != NULL ? - s->srv_additional_config : ""); + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); if (ssh_fips_mode()) { - snprintf(sshd_config, sizeof(sshd_config), - fips_config_string, - "HostKey", rsa_hostkey, - "HostKey", ecdsa_hostkey, - trusted_ca_pubkey, - sftp_server, - usepam, - additional_config, - s->srv_pidfile); + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ed25519_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } + + if (second_sshd) { + torture_write_file(s->srv1_config, sshd_config); } else { - snprintf(sshd_config, sizeof(sshd_config), - config_string, - "HostKey", ed25519_hostkey, - "HostKey", rsa_hostkey, - "HostKey", ecdsa_hostkey, - trusted_ca_pubkey, - sftp_server, - usepam, - additional_config, - s->srv_pidfile); + torture_write_file(s->srv_config, sshd_config); } - - torture_write_file(s->srv_config, sshd_config); } int torture_wait_for_daemon(unsigned int seconds) @@ -930,6 +1191,52 @@ int torture_wait_for_daemon(unsigned int seconds) return 1; } +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size) +{ + int rc; + rc = snprintf(env, + size, + "KRB5CCNAME=%s/cc " + "KRB5_CONFIG=%s/k/krb5.conf " + "KRB5_KDC_PROFILE=%s/k " + "KRB5_KTNAME=%s/d/ssh.keytab " + "KRB5RCACHETYPE=none ", + gss_dir, + gss_dir, + gss_dir, + gss_dir); + if (rc < 0 || rc >= (int)size) { + fail_msg("snprintf failed"); + } +} + +void torture_set_env_from_str(const char *env) +{ + struct ssh_tokens_st *vars = NULL, *var = NULL; + + vars = ssh_tokenize(env, ' '); + if (vars == NULL) { + fail_msg("failed to tokenize environment string"); + } + + for (int i = 0; vars->tokens[i]; i++) { + var = ssh_tokenize(vars->tokens[i], '='); + if (var == NULL) { + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + if (var->tokens[0] != NULL && var->tokens[1] != NULL) { + setenv(var->tokens[0], var->tokens[1], 1); + } else { + ssh_tokens_free(var); + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + ssh_tokens_free(var); + } + ssh_tokens_free(vars); +} + /** * @brief Run a libssh based server under timeout. * @@ -955,6 +1262,7 @@ void torture_setup_libssh_server(void **state, const char *server_path) char start_cmd[1024]; char timeout_cmd[512]; char env[1024]; + char kdc_env[255]; char extra_options[1024]; int rc; char *ld_preload = NULL; @@ -972,16 +1280,18 @@ void torture_setup_libssh_server(void **state, const char *server_path) ld_preload = getenv("LD_PRELOAD"); if (s->srv_additional_config != NULL) { - printed = snprintf(extra_options, sizeof(extra_options), " %s ", + printed = snprintf(extra_options, + sizeof(extra_options), + " %s ", s->srv_additional_config); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { fail_msg("Failed to print additional config!"); /* Unreachable */ __builtin_unreachable(); } } else { printed = snprintf(extra_options, sizeof(extra_options), " "); - if (printed < 0) { + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { fail_msg("Failed to print empty additional config!"); /* Unreachable */ __builtin_unreachable(); @@ -994,43 +1304,61 @@ void torture_setup_libssh_server(void **state, const char *server_path) force_fips = ""; } + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + /* Write the environment setting */ /* OPENSSL variable is needed to enable SHA1 */ - printed = snprintf(env, sizeof(env), + printed = snprintf(env, + sizeof(env), "SOCKET_WRAPPER_DIR=%s " "SOCKET_WRAPPER_DEFAULT_IFACE=10 " "LD_PRELOAD=%s " "%s " - "OPENSSL_ENABLE_SHA1_SIGNATURES=1", - s->socket_dir, ld_preload, force_fips); - if (printed < 0) { + "OPENSSL_ENABLE_SHA1_SIGNATURES=1 " + "NSS_WRAPPER_HOSTNAME=server.libssh.site " + "%s ", + s->socket_dir, + ld_preload, + force_fips, + kdc_env); + if (printed < 0 || printed >= (ssize_t)sizeof(env)) { fail_msg("Failed to print env!"); /* Unreachable */ __builtin_unreachable(); } #ifdef WITH_TIMEOUT - snprintf(timeout_cmd, sizeof(timeout_cmd), - "%s %s ", TIMEOUT_EXECUTABLE, "5m"); + snprintf(timeout_cmd, + sizeof(timeout_cmd), + "%s %s ", + TIMEOUT_EXECUTABLE, + "5m"); #else timeout_cmd[0] = '\0'; #endif /* Write the start command */ - printed = snprintf(start_cmd, sizeof(start_cmd), + printed = snprintf(start_cmd, + sizeof(start_cmd), "%s" - "%s -f%s -v4 -p22 -i%s -C%s%s%s", + "%s -f%s -v4 -p22 -i%s -C%s%s%s%s%s", timeout_cmd, - server_path, s->pcap_file, s->srv_pidfile, - s->srv_config, extra_options, TORTURE_SSH_SERVER); - if (printed < 0) { + server_path, + s->pcap_file, + s->srv_pidfile, + s->srv_config, + s->log_file ? " -l " : "", + s->log_file ? s->log_file : "", + extra_options, + TORTURE_SSH_SERVER); + if (printed < 0 || printed >= (ssize_t)sizeof(start_cmd)) { fail_msg("Failed to print start command!"); /* Unreachable */ __builtin_unreachable(); } pid = fork(); - switch(pid) { + switch (pid) { case 0: env_tokens = ssh_tokenize(env, ' '); if (env_tokens == NULL || env_tokens->tokens == NULL) { @@ -1047,7 +1375,8 @@ void torture_setup_libssh_server(void **state, const char *server_path) __builtin_unreachable(); } - execve(arg_tokens->tokens[0], (char **)arg_tokens->tokens, + execve(arg_tokens->tokens[0], + (char **)arg_tokens->tokens, (char **)env_tokens->tokens); /* execve returns only in case of error */ @@ -1072,23 +1401,42 @@ void torture_setup_libssh_server(void **state, const char *server_path) } } -static int torture_start_sshd_server(void **state) +static int torture_start_sshd_server(void **state, bool second_sshd) { struct torture_state *s = *state; char sshd_start_cmd[1024]; int rc; + char kdc_env[255] = {0}; - /* Set the default interface for the server */ - setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + /* Set the default interface for the server + * default is 10 */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", + second_sshd ? TORTURE_SSHD_SRV_IFACE : TORTURE_SSHD_SRV_IFACE, + 1); setenv("PAM_WRAPPER", "1", 1); - snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), - SSHD_EXECUTABLE " -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", - s->srv_config, s->socket_dir, s->socket_dir); +#ifdef WITH_GSSAPI + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); +#endif + rc = snprintf(sshd_start_cmd, + sizeof(sshd_start_cmd), + "%s " SSHD_EXECUTABLE + " -r -f %s -E %s/sshd%s/daemon.log 2> %s/sshd%s/cwrap.log", + kdc_env, + second_sshd ? s->srv1_config : s->srv_config, + s->socket_dir, + second_sshd ? "1" : "", + s->socket_dir, + second_sshd ? "1" : ""); + if (rc < 0 || rc >= (int)sizeof(sshd_start_cmd)) { + fail_msg("snprintf failed"); + } rc = system(sshd_start_cmd); assert_return_code(rc, errno); + unsetenv("NSS_WRAPPER_HOSTNAME"); setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); unsetenv("PAM_WRAPPER"); @@ -1104,18 +1452,122 @@ void torture_setup_sshd_server(void **state, bool pam) int rc; torture_setup_socket_dir(state); - torture_setup_create_sshd_config(state, pam); + torture_setup_create_sshd_config(state, pam, false); + + rc = torture_start_sshd_server(state, false); + assert_int_equal(rc, 0); +} + +/* Create an another sshd instance in the same SOCKET_WRAPPER_DIR + * Param state has to be initialized with torture_setup_sshd_server */ +void torture_setup_sshd_servers(void **state, bool pam) +{ + int rc; + + torture_setup_second_sshd_dir(state); + torture_setup_create_sshd_config(state, pam, true); - rc = torture_start_sshd_server(state); + rc = torture_start_sshd_server(state, true); assert_int_equal(rc, 0); } +#ifdef WITH_GSSAPI +/** + * @brief Setup KDC for GSSAPI testing + * + * This should be called after sshd or libssh server's setup functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * @param[in] kadmin_script kadmin commands to be executed on the KDC + * @param[in] kinit_script kinit commands to get the TGT + * + */ +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script) +{ + struct torture_state *s = *state; + int rc; + char command[1024] = {0}; + char kdc_env[255] = {0}; + char kadmin_file[255] = {0}; + char kinit_file[255] = {0}; + + /* Remove the previous files and folders, but keep the same directory + * because we pass only one temporary directory to the server */ + rc = snprintf(command, sizeof(command), "rm -rf %s/*", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "11", 1); + setenv("NSS_WRAPPER_HOSTNAME", "kdc.libssh.site", 1); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + snprintf(kadmin_file, sizeof(kadmin_file), "%s/kadmin.sh", s->gss_dir); + snprintf(kinit_file, sizeof(kinit_file), "%s/kinit.sh", s->gss_dir); + + torture_write_file(kadmin_file, kadmin_script); + torture_write_file(kinit_file, kinit_script); + + rc = snprintf(command, + sizeof(command), + "%s/tests/gss/kdcsetup.sh %s", + BINARYDIR, + s->socket_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + assert_int_equal(rc, 0); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + /* Back to client */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); +} + +/** + * @brief Teardown KDC + * + * This should be called before sshd or libssh server's teardown functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_teardown_kdc_server(void **state) +{ + struct torture_state *s = *state; + int rc; + char pid_path[1024] = {0}; + + rc = snprintf(pid_path, sizeof(pid_path), "%s/pid", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(pid_path)) { + fail_msg("snprintf failed"); + } + rc = torture_terminate_process(pid_path); + assert_return_code(rc, errno); +} + +#endif /* WITH_GSSAPI */ + void torture_free_state(struct torture_state *s) { free(s->srv_config); + free(s->srv1_config); free(s->socket_dir); +#ifdef WITH_GSSAPI + free(s->gss_dir); +#endif free(s->pcap_file); + free(s->log_file); free(s->srv_pidfile); + free(s->srv1_pidfile); free(s->srv_additional_config); free(s); } @@ -1127,7 +1579,9 @@ void torture_teardown_socket_dir(void **state) int rc; if (env != NULL && env[0] == '1') { - fprintf(stderr, "[ TORTURE ] >>> Skipping cleanup of %s\n", s->socket_dir); + fprintf(stderr, + "[ TORTURE ] >>> Skipping cleanup of %s\n", + s->socket_dir); } else { rc = torture_rmdirs(s->socket_dir); if (rc < 0) { @@ -1146,8 +1600,7 @@ void torture_teardown_socket_dir(void **state) torture_free_state(s); } -static int -torture_reload_sshd_server(void **state) +static int torture_reload_sshd_server(void **state) { struct torture_state *s = *state; int rc; @@ -1155,7 +1608,7 @@ torture_reload_sshd_server(void **state) rc = torture_terminate_process(s->srv_pidfile); assert_return_code(rc, errno); - return torture_start_sshd_server(state); + return torture_start_sshd_server(state, false); } /* @brief: Updates SSHD server configuration with more options and @@ -1163,8 +1616,7 @@ torture_reload_sshd_server(void **state) * Note, that this still uses the default configuration options specified * in this file and overwrites options previously specified by this function. */ -int -torture_update_sshd_config(void **state, const char *config) +int torture_update_sshd_config(void **state, const char *config) { struct torture_state *s = *state; int rc; @@ -1175,7 +1627,7 @@ torture_update_sshd_config(void **state, const char *config) assert_non_null(s->srv_additional_config); /* Rewrite the configuration file */ - torture_setup_create_sshd_config(state, s->srv_pam); + torture_setup_create_sshd_config(state, s->srv_pam, false); /* Reload the server */ rc = torture_reload_sshd_server(state); @@ -1184,17 +1636,25 @@ torture_update_sshd_config(void **state, const char *config) return SSH_OK; } - void torture_teardown_sshd_server(void **state) { struct torture_state *s = *state; - int rc; - - rc = torture_terminate_process(s->srv_pidfile); - assert_return_code(rc, errno); + torture_terminate_process(s->srv_pidfile); + if (s->srv1_pidfile != NULL) { + torture_terminate_process(s->srv1_pidfile); + } torture_teardown_socket_dir(state); } + +void torture_teardown_sshd_server1(void **state) +{ + struct torture_state *s = *state; + + torture_terminate_process(s->srv1_pidfile); + SAFE_FREE(s->srv1_pidfile); + SAFE_FREE(s->srv1_config); +} #endif /* SSHD_EXECUTABLE */ #ifdef WITH_PKCS11_URI @@ -1206,58 +1666,47 @@ void torture_setup_tokens(const char *temp_dir, char token_setup_start_cmd[1024] = {0}; char socket_path[1204] = {0}; char conf_path[1024] = {0}; +#ifdef WITH_PKCS11_PROVIDER + char *env = NULL; +#endif /* WITH_PKCS11_PROVIDER */ 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", + "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s %s", BINARYDIR, temp_dir, filename, object_name, load_public, - SOFTHSM2_LIBRARY, -#ifdef WITH_PKCS11_PROVIDER - P11_KIT_CLIENT -#else - "" -#endif - ); + SOFTHSM2_LIBRARY); 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); + setenv("PKCS11_PROVIDER_MODULE", SOFTHSM2_LIBRARY, 1); + /* This is useful for debugging PKCS#11 calls */ - setenv("PKCS11SPY", P11_KIT_CLIENT, 1); - setenv("PKCS11_PROVIDER_MODULE", "/usr/lib64/pkcs11-spy.so", 1); + env = getenv("TORTURE_PKCS11"); + if (env != NULL && env[0] != '\0') { +#ifdef PKCS11SPY + setenv("PKCS11SPY", SOFTHSM2_LIBRARY, 1); + setenv("PKCS11_PROVIDER_MODULE", PKCS11SPY, 1); #else + fprintf(stderr, "[ TORTURE ] >>> pkcs11-spy not found\n"); +#endif /* PKCS11SPY */ + } +#endif /* WITH_PKCS11_PROVIDER */ + 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 */ @@ -1430,14 +1879,12 @@ static int recursive_rm_dir_content(const char *path) /* Empty directory */ if (last_error == ERROR_FILE_NOT_FOUND) { rc = 0; - } - else { + } else { /*TODO print error message?*/ rc = last_error; } goto end; - } - else { + } else { do { rc = strcmp(file_data.cFileName, "."); if (rc == 0) { @@ -1471,15 +1918,14 @@ static int recursive_rm_dir_content(const char *path) rc = last_error; goto end; } - } - else { + } else { rc = remove(file_path); if (rc) { goto end; } } - } while(FindNextFile(file_handle, &file_data)); + } while (FindNextFile(file_handle, &file_data)); FindClose(file_handle); } @@ -1606,19 +2052,21 @@ int torture_change_dir(char *path) return rc; } -int torture_libssh_verbosity(void){ - return verbosity; +int torture_libssh_verbosity(void) +{ + return verbosity; } void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests) { - (void) tests; - (void) ntests; + (void)tests; + (void)ntests; return; } -void torture_write_file(const char *filename, const char *data){ +void torture_write_file(const char *filename, const char *data) +{ int fd; int rc; @@ -1638,6 +2086,36 @@ void torture_write_file(const char *filename, const char *data){ void torture_reset_config(ssh_session session) { memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); + if (ssh_libssh_proxy_jumps()) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } +} + +void torture_unsetenv(const char *variable) +{ + int rc; +#ifdef WIN32 + rc = _putenv_s(variable, ""); +#else + rc = unsetenv(variable); +#endif // WIN32 + assert_return_code(rc, errno); +} + +void torture_setenv(const char *variable, const char *value) +{ + int rc; +#ifdef WIN32 + if (value != NULL) { + rc = _putenv_s(variable, value); + assert_return_code(rc, errno); + } else { + torture_unsetenv(variable); + } +#else + rc = setenv(variable, value, 1); + assert_return_code(rc, errno); +#endif // WIN32 } #if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) @@ -1649,15 +2127,37 @@ __attribute__((weak)) int torture_run_tests(void) } #endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ -int main(int argc, char **argv) { +/** + * Finalize the torture context. No-op except for OpenSSL or GSSAPI + * + * When OpenSSL is built without the at-exit handlers, it won't call the + * OPENSSL_cleanup() from destructor or at-exit handler, which means we need to + * do it manually in the tests. + * + * It is never a good idea to call this function from the library context as we + * can not be sure the libssh is really the last one using the OpenSSL. + * + * This needs to be called at the end of the main function or any time before + * any forked process (servers) exits. + */ +void torture_finalize(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} + +int main(int argc, char **argv) +{ struct argument_s arguments; char *env = getenv("LIBSSH_VERBOSITY"); + int rv; - arguments.verbose=0; - arguments.pattern=NULL; + arguments.verbose = 0; + arguments.pattern = NULL; torture_cmdline_parse(argc, argv, &arguments); - verbosity=arguments.verbose; - pattern=arguments.pattern; + verbosity = arguments.verbose; + pattern = arguments.pattern; if (verbosity == 0 && env != NULL && env[0] != '\0') { if (env[0] > '0' && env[0] < '9') { @@ -1669,5 +2169,114 @@ int main(int argc, char **argv) { cmocka_set_test_filter(pattern); #endif - return torture_run_tests(); + rv = torture_run_tests(); + + torture_finalize(); + + return rv; +} + +/** + * @brief Setup an SSH agent for testing + * + * This function starts an SSH agent, exports the environment variables, + * and optionally adds an SSH key to the agent. + * + * @param s The torture state + * @param add_key Path to the key to add to the agent, or NULL to skip + * + * @return 0 on success, -1 on error + */ +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key) +{ +#ifndef WIN32 + int rc; + char ssh_agent_cmd[4096]; + char ssh_agent_sock[1024]; + char ssh_agent_pidfile[1024]; + char long_cmd[2048]; + + /* Setup SSH agent */ + 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); + + /* Create command to start SSH agent with our custom socket */ + 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 as the normal user */ + torture_unsetenv("UID_WRAPPER_ROOT"); + + rc = system(ssh_agent_cmd); + if (rc != 0) { + return -1; + } + + /* Set environment variables for SSH agent */ + torture_setenv("SSH_AUTH_SOCK", ssh_agent_sock); + torture_setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile); + + /* Add key to the agent if specified */ + if (add_key != NULL) { + snprintf(long_cmd, sizeof(long_cmd), "ssh-add %s", add_key); + rc = system(long_cmd); + if (rc != 0) { + return -1; + } + } + + return 0; +#else + /* On Windows, we don't set up an SSH agent */ + (void)s; + (void)add_key; + + /* Return failure to make it explicit that agent forwarding isn't supported + * on Windows */ + return -1; +#endif +} + +/** + * @brief Teardown an SSH agent + * + * This function kills the SSH agent process and cleans up environment + * variables. + * + * @return 0 on success, -1 on error + */ +int torture_cleanup_ssh_agent(void) +{ +#ifndef WIN32 + const char *ssh_agent_pidfile; + int rc; + + ssh_agent_pidfile = getenv("TORTURE_SSH_AGENT_PIDFILE"); + if (ssh_agent_pidfile == NULL) { + return 0; + } + + rc = torture_terminate_process(ssh_agent_pidfile); + if (rc != 0) { + return -1; + } + + torture_unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + torture_unsetenv("SSH_AUTH_SOCK"); + + return 0; +#else + /* On Windows, we don't start an SSH agent, so nothing to clean up */ + return -1; +#endif } diff --git a/tests/torture.h b/tests/torture.h index eb2765da..0120bd6f 100644 --- a/tests/torture.h +++ b/tests/torture.h @@ -24,11 +24,11 @@ #ifndef _TORTURE_H #define _TORTURE_H -#include -#include +#include #include #include -#include +#include +#include #include "libssh/priv.h" #include "libssh/server.h" @@ -36,13 +36,12 @@ #include -#include "torture_cmocka.h" #include "tests_config.h" +#include "torture_cmocka.h" #ifndef assert_return_code /* hack for older versions of cmocka */ -#define assert_return_code(code, errno) \ - assert_true(code >= 0) +#define assert_return_code(code, errno) assert_true(code >= 0) #endif /* assert_return_code */ #define TORTURE_SSH_SERVER "127.0.0.10" @@ -55,8 +54,8 @@ /* Used by main to communicate with parse_opt. */ struct argument_s { - const char *pattern; - int verbose; + const char *pattern; + int verbose; }; struct torture_sftp { @@ -65,16 +64,28 @@ struct torture_sftp { char *testdir; }; +struct torture_ssh { + ssh_session session; + void *cb_state; /* For storing callback state */ + void *callbacks; /* For storing callbacks */ +}; + struct torture_state { char *socket_dir; + char *gss_dir; char *pcap_file; + char *log_file; char *srv_pidfile; char *srv_config; + char *srv1_pidfile; + char *srv1_config; bool srv_pam; + bool disable_hostkeys; char *srv_additional_config; struct { ssh_session session; struct torture_sftp *tsftp; + struct torture_ssh ssh; } ssh; #ifdef WITH_PCAP ssh_pcap_file plain_pcap; @@ -104,21 +115,26 @@ ssh_session torture_ssh_session(struct torture_state *s, const char *user, const char *password); +ssh_session torture_ssh_session_proxyjump(void); + ssh_bind torture_ssh_bind(const char *addr, const unsigned int port, enum ssh_keytypes_e key_type, const char *private_key_file); struct torture_sftp *torture_sftp_session(ssh_session session); -struct torture_sftp *torture_sftp_session_channel(ssh_session session, ssh_channel channel); +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel); void torture_sftp_close(struct torture_sftp *t); void torture_write_file(const char *filename, const char *data); -#define torture_filter_tests(tests) _torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0]) +#define torture_filter_tests(tests) \ + _torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0]) void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); const char *torture_server_address(int domain); +const char *torture_server1_address(int domain); int torture_server_port(void); int torture_wait_for_daemon(unsigned int seconds); @@ -126,9 +142,11 @@ 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_sshd_servers(void **state, bool pam); void torture_teardown_socket_dir(void **state); void torture_teardown_sshd_server(void **state); +void torture_teardown_sshd_server1(void **state); int torture_update_sshd_config(void **state, const char *config); #endif /* SSHD_EXECUTABLE */ @@ -147,6 +165,15 @@ void torture_setup_create_libssh_config(void **state); void torture_setup_libssh_server(void **state, const char *server_path); +#ifdef WITH_GSSAPI +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script); +void torture_teardown_kdc_server(void **state); +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size); +void torture_set_env_from_str(const char *env); +#endif /* WITH_GSSAPI */ + #if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) __attribute__((weak)) int torture_run_tests(void); #else @@ -164,4 +191,12 @@ char *torture_create_temp_file(const char *template); char *torture_get_current_working_dir(void); int torture_change_dir(char *path); +void torture_setenv(char const *variable, char const *value); +void torture_unsetenv(char const *variable); + +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key); +int torture_cleanup_ssh_agent(void); + +void torture_finalize(void); + #endif /* _TORTURE_H */ diff --git a/tests/torture_key.c b/tests/torture_key.c index 9bf71995..a230484a 100644 --- a/tests/torture_key.c +++ b/tests/torture_key.c @@ -700,6 +700,37 @@ static const char torture_ecdsa521_testkey_cert[] = "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" "= ./ec521.pub"; +static const char torture_ecdsa_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2\n" + "RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRUfa1IVvak\n" + "xFQZudDtXVlTtw6SiuAgfTpqZBuMdcK55kgy3o7V2z02/XClN1zpvSydzdjGWVgLj6WE9Q\n" + "6xEOhQAAAABHNzaDoAAADoWSfkhlkn5IYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv\n" + "cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEVH2tSFb2pMRUGbnQ7V1ZU7cOkorgIH06am\n" + "QbjHXCueZIMt6O1ds9Nv1wpTdc6b0snc3YxllYC4+lhPUOsRDoUAAAAARzc2g6AQAAAEBS\n" + "Smuf/sZP2WxVdlqgSMN7E8VLFdZI717mTi/svHahGy3wcFp2tPPylCaIG9aKAQrfVt+pOJ\n" + "U+OPsm8rphRRM1AAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCzS672qr\n" + "+0DRopx7VjkjCnAAAAGAAAAAEAAAB/AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl\n" + "bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBFR9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4\n" + "x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuPpYT1DrEQ6FAAAAAEc3NoOgAAAPDoWSmM\n" + "ki/XGLXidNxyLy2uRGejaZTOI3Ran10b7UF2ddRCrmBc6eVEXzgJ+BzB0sO0/uc1Q7QJhy\n" + "fGR9bz1rvwJd5RpLLw9cSoTHbDiap4tkQu2snQt7AF/E6MOgQ3mvdhDDYoTYvxNIiwZTH1\n" + "/Cxl2ZcRBKwSl6yp3JOxIVgttmJmNTqpt2U/uYwag9N1o6wxhWy1aamKZd1qHtPVC7MPL8\n" + "/Q96mBlCEIe3vd4Hge4wgDa24F4Lwat7IA0/NGNFISIQH7x4VaGHAiTeMFL1NOVyw52xWr\n" + "aAgXfkyplffxlB7ZfCf7RLsiCZDinMCE9y8=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_public_testkey[] = + "sk-ecdsa-sha2-nistp256@openssh.com " + "AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBF" + "R9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuP" + "pYT1DrEQ6FAAAAAEc3NoOg== phoenix@phoenix-pc"; + /**************************************************************************** * ED25519 KEYS ****************************************************************************/ @@ -740,6 +771,11 @@ static const char torture_ed25519_public_testkey[] = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" "BU6Nh3PmAiqX aris@kalix86"; +static const char torture_ed25519_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MCowBQYDK2VwAyEAFZafG4JiI7K8xgu2SCgTIQqUtX6SUz4FTo2Hc+YCKpc=\n" + "-----END PUBLIC KEY-----\n"; + static const char torture_ed25519_testkey_cert[] = "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" @@ -752,6 +788,35 @@ static const char torture_ed25519_testkey_cert[] = "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; +static const char torture_ed25519_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2\n" + "gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCihqLlueARJOQCZMYRHefNkQ3WBHlhlUOuG7a/\n" + "ivCkaQAAAARzc2g6AAAA+OivRKLor0SiAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2\n" + "9tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoBAAAAgNxc\n" + "Q6pfw2S2fpCEB1UGO4Fy8O5gXZDbw3Vj8EHTcUDucNmk/iaI/GTPcUQK5cgPJH8AaB+lIZ\n" + "GasyHd28mghgpaztG2cYmxrF3ZuvNdEZJecflgMOJDXZwoYvKpb7rZWjQgf8AeDy2u2dpl\n" + "XCKHH8/LkJHdo4MABojarKofgaGzAAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAgMEBQ\n" + "YH\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAkfYBhph\n" + "EvYRpuOO6V4wihAAAAGAAAAAEAAABKAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t\n" + "AAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoAAAEA2WmpuB\n" + "2ip0Bq4XJ9c2C33fE5twVYvK3WrJfAJKzih7bFXxbt5NmUFs121SD/x+3xZLwBJWGOIhdf\n" + "idOD4gy9VWWAGCdJ0v87T/WaBYzEACr32hd99cD+Ki7VmmAxOKxx2/+/gg+WkbgygNns3c\n" + "7YoYW5SSJm7WlhtmHFCKHtSh0fd8X1Q7gLHWTdd4B+3U9PyGpVgCKe2s2IOoTIcWOHlDW3\n" + "KbEdlKELKCUEb0kof5m3hu8cktn0J/YIe1Y98YVjv472P6CO0Jw92jHSEPiTGn8JdSPkBY\n" + "Qcoq18tszucoR2gp+sf5UvQhW8iOALDxO72Yq6HINAXNbpCB22U++GJw==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_public_testkey[] = + "sk-ssh-ed25519@openssh.com " + "AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeW" + "GVQ64btr+K8KRpAAAABHNzaDo= phoenix@phoenix-pc"; + static const char * torture_get_testkey_public_internal(enum ssh_keytypes_e type, enum torture_format_e format) @@ -783,8 +848,7 @@ torture_get_testkey_public_internal(enum ssh_keytypes_e type, if (format == FORMAT_OPENSSH) { return torture_ed25519_public_testkey; } - /* not available in other formats */ - return NULL; + return torture_ed25519_public_testkey_pem; case SSH_KEYTYPE_DSS_CERT01: return torture_dsa_testkey_cert; case SSH_KEYTYPE_RSA_CERT01: @@ -797,11 +861,19 @@ torture_get_testkey_public_internal(enum ssh_keytypes_e type, return torture_ecdsa521_testkey_cert; case SSH_KEYTYPE_ED25519_CERT01: return torture_ed25519_testkey_cert; + case SSH_KEYTYPE_SK_ECDSA: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa_sk_public_testkey; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_sk_public_testkey; + } + return NULL; 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; @@ -876,6 +948,26 @@ torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, return NULL; } return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -884,9 +976,7 @@ torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, 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; @@ -961,6 +1051,24 @@ torture_get_testkey_internal(enum ssh_keytypes_e type, return NULL; } return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -969,9 +1077,7 @@ torture_get_testkey_internal(enum ssh_keytypes_e type, 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; diff --git a/tests/torture_sk.c b/tests/torture_sk.c new file mode 100644 index 00000000..fdb2a815 --- /dev/null +++ b/tests/torture_sk.c @@ -0,0 +1,395 @@ +/* + * torture_sk.c - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * 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 "torture_sk.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/sk_api.h" /* For SSH_SK_* flag definitions */ + +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private) +{ + char *app_str = NULL; + const char *expected_type_str = NULL; + + assert_non_null(key); + assert_true(is_sk_key_type(expected_type)); + assert_int_equal(key->type, expected_type); + + if (private) { + assert_int_equal(key->flags, + SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC); + } else { + assert_int_equal(key->flags, SSH_KEY_FLAG_PUBLIC); + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(expected_type_str); + + assert_non_null(key->type_c); + assert_string_equal(key->type_c, expected_type_str); + + /* Validate security key specific fields */ + assert_non_null(key->sk_application); + + /* Validate application string format and content */ + app_str = ssh_string_to_char(key->sk_application); + assert_non_null(app_str); + + assert_true(ssh_string_len(key->sk_application) >= 4); + assert_true(strncmp(app_str, "ssh:", 4) == 0); + ssh_string_free_char(app_str); + + if (private) { + assert_non_null(key->sk_key_handle); + assert_true(ssh_string_len(key->sk_key_handle) > 0); + } + + const uint8_t allowed_flags = SSH_SK_USER_PRESENCE_REQD | + SSH_SK_USER_VERIFICATION_REQD | + SSH_SK_RESIDENT_KEY | SSH_SK_FORCE_OPERATION; + + /* Validate sk_flags contain only allowed bits */ + uint8_t flags = key->sk_flags; + assert_int_equal(flags & ~allowed_flags, 0); + + /* Validate underlying cryptographic key exists based on type */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: +#if defined(HAVE_LIBGCRYPT) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBMBEDCRYPTO) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#endif + break; + + case SSH_KEYTYPE_SK_ED25519: +#if defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#elif !defined(HAVE_LIBCRYPTO) + assert_non_null(key->ed25519_pubkey); +#endif + break; + + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len) +{ + uint8_t valid_flags; + const char *expected_type_str = NULL; + ssh_string sig_blob = NULL; + ssh_signature reconstructed = NULL; + ssh_buffer sk_sig_buffer = NULL; + int rc; + + /* Basic null and type validation */ + assert_non_null(signature); + assert_int_equal(signature->type, expected_type); + + /* Validate hash type is appropriate for security keys */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: + assert_int_equal(signature->hash_type, SSH_DIGEST_SHA256); + break; + case SSH_KEYTYPE_SK_ED25519: + assert_int_equal(signature->hash_type, SSH_DIGEST_AUTO); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(signature->type_c); + assert_string_equal(signature->type_c, expected_type_str); + + /* Check that only valid SK flags are set */ + valid_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + assert_int_equal(signature->sk_flags & ~valid_flags, 0); + + assert_true(signature->sk_flags & SSH_SK_USER_PRESENCE_REQD); + assert_true(signature->sk_counter > 0); + + assert_non_null(signature->raw_sig); + assert_true(ssh_string_len(signature->raw_sig) > 0); + + rc = ssh_pki_export_signature_blob(signature, &sig_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(sig_blob); + + assert_non_null(signing_key); + rc = ssh_pki_import_signature_blob(sig_blob, signing_key, &reconstructed); + assert_int_equal(rc, SSH_OK); + assert_non_null(reconstructed); + + rc = pki_sk_signature_buffer_prepare(signing_key, + reconstructed, + data, + data_len, + &sk_sig_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(sk_sig_buffer); + + rc = pki_verify_data_signature(reconstructed, + signing_key, + ssh_buffer_get(sk_sig_buffer), + ssh_buffer_get_len(sk_sig_buffer)); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(sk_sig_buffer); + + ssh_signature_free(reconstructed); + ssh_string_free(sig_blob); +} + +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks) +{ + ssh_pki_ctx ctx = NULL; + ssh_buffer challenge_buffer = NULL; + int rc; + + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_APPLICATION, + application); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + assert_int_equal(rc, SSH_OK); + + if (challenge_data != NULL && challenge_len > 0) { + challenge_buffer = ssh_buffer_new(); + assert_non_null(challenge_buffer); + + rc = ssh_buffer_add_data(challenge_buffer, + challenge_data, + challenge_len); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CHALLENGE, + challenge_buffer); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(challenge_buffer); + + rc = ssh_pki_ctx_set_sk_pin_callback(ctx, pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (device_path != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + false); + assert_int_equal(rc, SSH_OK); + } + if (user_id != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + user_id, + false); + assert_int_equal(rc, SSH_OK); + } + + if (sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + return ctx; +} + +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags) +{ + assert_non_null(response); + + assert_non_null(response->public_key); + assert_true(response->public_key_len > 0); + + assert_non_null(response->key_handle); + assert_true(response->key_handle_len > 0); + + assert_non_null(response->signature); + assert_true(response->signature_len > 0); + + /* + * This check might fail for some authenticators, as returning an + * attestation certificate as part of the attestation statement is not + * mandated by the FIDO2 standard. + */ + assert_non_null(response->attestation_cert); + assert_true(response->attestation_cert_len > 0); + + assert_non_null(response->authdata); + assert_true(response->authdata_len > 0); + + assert_int_equal(response->flags, flags); +} + +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type) +{ + assert_non_null(response); + + assert_non_null(response->sig_r); + assert_true(response->sig_r_len > 0); + + /* sig_s is NULL for Ed25519, present for ECDSA */ + switch (key_type) { + case SSH_SK_ECDSA: + assert_non_null(response->sig_s); + assert_true(response->sig_s_len > 0); + break; + case SSH_SK_ED25519: + assert_null(response->sig_s); + assert_int_equal(response->sig_s_len, 0); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_resident_key(struct sk_resident_key *resident_key) +{ + assert_non_null(resident_key); + + assert_non_null(resident_key->application); + assert_true(strlen(resident_key->application) > 0); + + assert_non_null(resident_key->user_id); + assert_true(resident_key->user_id_len > 0); + + assert_non_null(resident_key->key.public_key); + assert_true(resident_key->key.public_key_len > 0); + + assert_non_null(resident_key->key.key_handle); + assert_true(resident_key->key.key_handle_len > 0); +} + +const char *torture_get_sk_pin(void) +{ + const char *pin = getenv("TORTURE_SK_PIN"); + return (pin != NULL && pin[0] != '\0') ? pin : NULL; +} + +#ifdef HAVE_SK_DUMMY + +/* External declarations for sk-dummy library functions + * These match the signatures in openssh sk-api.h */ +extern uint32_t sk_api_version(void); + +extern int sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +extern int sk_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +extern int sk_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +static struct ssh_sk_callbacks_struct sk_dummy_callbacks = { + .api_version = sk_api_version, + .enroll = sk_enroll, + .sign = sk_sign, + .load_resident_keys = sk_load_resident_keys, +}; + +#endif /* HAVE_SK_DUMMY */ + +#ifdef WITH_FIDO2 + +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void) +{ +#ifdef HAVE_SK_DUMMY + ssh_callbacks_init(&sk_dummy_callbacks); + return &sk_dummy_callbacks; +#else + return NULL; +#endif /* HAVE_SK_DUMMY */ +} + +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + bool torture_sk_usbhid = (env != NULL && env[0] != '\0'); + + if (torture_sk_usbhid) { + return ssh_sk_get_default_callbacks(); + } else { + return torture_get_sk_dummy_callbacks(); + } +} + +#endif /* WITH_FIDO2 */ + +bool torture_sk_is_using_sk_dummy(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + /* Return true if using sk-dummy callbacks (when TORTURE_SK_USBHID is NOT + * set) */ + return (env == NULL || env[0] == '\0'); +} diff --git a/tests/torture_sk.h b/tests/torture_sk.h new file mode 100644 index 00000000..d63b02a0 --- /dev/null +++ b/tests/torture_sk.h @@ -0,0 +1,167 @@ +/* + * torture_sk.h - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_SK_H +#define _TORTURE_SK_H + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/callbacks.h" +#include "libssh/pki.h" +#include "torture.h" +#include "torture_pki.h" + +/** + * @brief Validate a security key (ssh_key) structure + * + * Checks that the provided key is not NULL, matches the expected key type, + * and other internal fields. + * + * @param[in] key The key to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] private true if key should be private, false for public + */ +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private); + +/** + * @brief Validate a security key signature structure + * + * Checks that the signature is not NULL, matches the expected key type, and + * other internal fields. Also verifies that the signature was produced by the + * given signing key. + * + * @param[in] signature The signature to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] signing_key The key that should have produced the signature + * @param[in] data The signed data buffer + * @param[in] data_len Length of the signed data + */ +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len); + +/** + * @brief Create and initialize a PKI context configured for security key + * operations. + * + * Parameters: + * @param[in] application Application string + * @param[in] flags SK flags + * @param[in] challenge_data Optional challenge bytes (may be NULL) + * @param[in] challenge_len Length of challenge_data + * @param[in] pin_callback Callback used to obtain the PIN (may be NULL) + * @param[in] device_path Optional device path (may be NULL) + * @param[in] user_id Optional user_id string (may be NULL) + * @param[in] sk_callbacks Pointer to SK callbacks (may be NULL) + * + * @return A configured ssh_pki_ctx on success, or NULL on allocation failure. + */ +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks); + +/** + * @brief Validate a security key enrollment response structure + * + * Validates that an sk_enroll_response contains valid data from a FIDO2 + * enrollment operation, including public key, key handle, signature, + * attestation certificate, and authenticator data. + * + * @param[in] response The enrollment response to validate + * @param[in] flags The expected flags that should match the response flags + */ +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags); + +/** + * @brief Validate a security key sign response structure + * + * Validates that an sk_sign_response contains valid signature data from + * a FIDO2 sign operation. + * + * @param[in] response The sign response to validate + * @param[in] key_type The key type (e.g., SSH_SK_ECDSA, SSH_SK_ED25519) + */ +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type); + +/** + * @brief Validate a security key resident key structure + * + * Validates that an sk_resident_key contains valid data including application + * identifier, user ID, public key, and key handle. + * + * @param[in] resident_key The resident key to validate + */ +void assert_sk_resident_key(struct sk_resident_key *resident_key); + +/** + * @brief Get security key PIN from environment variable + * + * Reads the TORTURE_SK_PIN environment variable and returns its value. + * + * @return Pointer to PIN string if set and non-empty, NULL otherwise + */ +const char *torture_get_sk_pin(void); + +/** + * @brief Get dummy security key callbacks for testing + * + * Returns dummy security key callbacks from openssh's sk-dummy + * if available, or NULL if not. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable. + * + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void); + +/** + * @brief Get security key callbacks for testing + * + * Returns the default sk callbacks if TORTURE_SK_USBHID is set, + * otherwise returns dummy callbacks from openssh sk-dummy, or NULL if + * unavailable. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void); + +/** + * @brief Check if using sk-dummy callbacks for testing + * + * @return true if using sk-dummy callbacks, false otherwise + */ +bool torture_sk_is_using_sk_dummy(void); + +#endif /* _TORTURE_SK_H */ diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 04fcba11..79f6b218 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -1,7 +1,5 @@ project(unittests C) -include_directories(${OPENSSL_INCLUDE_DIR}) - set(LIBSSH_UNIT_TESTS torture_bignum torture_buffer @@ -21,6 +19,7 @@ set(LIBSSH_UNIT_TESTS torture_temp_file torture_push_pop_dir torture_session_keys + torture_string torture_tokens ) @@ -48,10 +47,19 @@ if (UNIX AND NOT WIN32) torture_pki_rsa torture_pki_dsa torture_pki_ed25519 + torture_pki_sk_ed25519 + torture_pki_sshsig # requires /dev/null torture_channel ) - + if (HAVE_IFADDRS_H) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # requires some non-standard API from netdb.h, in.h + # and arpa/inet.h for handling IP addresses + torture_config_match_localnetwork + ) + endif (HAVE_IFADDRS_H) if (WITH_SERVER) set(LIBSSH_UNIT_TESTS ${LIBSSH_UNIT_TESTS} @@ -70,13 +78,28 @@ if (UNIX AND NOT WIN32) torture_pki_rsa_uri torture_pki_ecdsa_uri ) - list(APPEND TORTURE_UNIT_ENVIRONMENT PKCS11_PROVIDER_DEBUG=file:/tmp/p11prov-debug.log) + if (WITH_PKCS11_PROVIDER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ed25519_uri + ) + list(APPEND TORTURE_UNIT_ENVIRONMENT + PKCS11_PROVIDER_DEBUG=file:/tmp/p11prov-debug.log) + endif() endif() + if (WITH_FIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_sk + ) + endif (WITH_FIDO2) + if (HAVE_ECC) set(LIBSSH_UNIT_TESTS ${LIBSSH_UNIT_TESTS} torture_pki_ecdsa + torture_pki_sk_ecdsa ) endif() @@ -86,16 +109,30 @@ if (UNIX AND NOT WIN32) torture_threads_pki_rsa ) if (WITH_SERVER) - # Not working correctly - # add_cmocka_test(torture_server_x11 torture_server_x11.c ${TEST_TARGET_LIBRARIES}) - # the signals are not testable under cmocka - # set(LIBSSH_THREAD_UNIT_TESTS - # ${LIBSSH_THREAD_UNIT_TESTS} - # torture_unit_server - # ) + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + torture_unit_server + torture_server_x11 + torture_forwarded_tcpip_callback + torture_server_direct_tcpip + ) endif (WITH_SERVER) endif (UNIX AND NOT WIN32) +if (HAVE_LIBFIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_sk_usbhid + ) +endif (HAVE_LIBFIDO2) + +if (WITH_SFTP) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_unit_sftp + ) +endif (WITH_SFTP) + foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) add_cmocka_test(${_UNIT_TEST} SOURCES ${_UNIT_TEST}.c diff --git a/tests/unittests/hello world.sh b/tests/unittests/hello world.sh index 9a021b4e..8f687028 100755 --- a/tests/unittests/hello world.sh +++ b/tests/unittests/hello world.sh @@ -1 +1,2 @@ -/bin/echo -n $1 2>&1 \ No newline at end of file +#!/bin/sh +printf '%s' "$1" 2>&1 diff --git a/tests/unittests/torture_bignum.c b/tests/unittests/torture_bignum.c index c36b81f8..6f679946 100644 --- a/tests/unittests/torture_bignum.c +++ b/tests/unittests/torture_bignum.c @@ -6,36 +6,61 @@ #include "libssh/bignum.h" #include "libssh/string.h" -static void check_str (int n, ssh_string str) +static void check_str(int n, ssh_string str) { if (n > 0 && n <= 127) { - assert_int_equal(1, ntohl (str->size)); + 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(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(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(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; +static void check_padded_str(int n, ssh_string str) +{ + assert_int_equal(4, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(0, str->data[2]); + assert_int_equal(n, str->data[3]); + } else if (n > 255 && n <= 65535) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(n >> 8, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } else { + assert_int_equal(0, str->data[0]); + assert_int_equal(n >> 16, str->data[1]); + assert_int_equal((n >> 8) & 0xFF, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } +} + +static void check_bignum(int n, const char *nstr) +{ + bignum num = NULL, num2 = NULL; + bignum num3 = NULL; + ssh_string str = NULL; + char *dec = NULL; + int rc; num = bignum_new(); assert_non_null(num); - assert_int_equal (1, bignum_set_word (num, n)); + rc = bignum_set_word(num, n); + assert_int_equal(rc, 1); ssh_print_bignum("num", num); @@ -66,8 +91,53 @@ static void check_bignum(int n, const char *nstr) { assert_string_equal (nstr, dec); ssh_crypto_free(dec); + bignum_dup(num, &num3); + assert_non_null(num3); + assert_int_equal(0, bignum_cmp(num, num3)); + + bignum_safe_free(num2); + + /* ssh_make_padded_bignum_string */ + + str = ssh_make_padded_bignum_string(num, 4); + assert_non_null(str); + + check_padded_str(n, str); + + num2 = ssh_make_string_bn(str); + ssh_string_free(str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_true(bignum_cmp(num, num2) == 0); + + dec = bignum_bn2dec(num2); + assert_non_null(dec); + assert_string_equal(nstr, dec); + ssh_crypto_free(dec); + + /* negative test */ + str = ssh_make_padded_bignum_string(num, 2); + if (n > 65535) { + /* larger values need larger padding! */ + assert_null(str); + } else { + assert_non_null(str); + assert_int_equal(2, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else { + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } + ssh_string_free(str); + } + bignum_safe_free(num); bignum_safe_free(num2); + bignum_safe_free(num3); } diff --git a/tests/unittests/torture_bind_config.c b/tests/unittests/torture_bind_config.c index a2f9be06..514727e5 100644 --- a/tests/unittests/torture_bind_config.c +++ b/tests/unittests/torture_bind_config.c @@ -145,6 +145,9 @@ extern LIBSSH_THREAD int ssh_log_level; "KexAlgorithms "KEXALGORITHMS"\n" \ "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE "libssh_test_bind_config_required_rsa_size" +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING "RequiredRsaSize 2233\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" @@ -298,6 +301,9 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE, + LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, "ListenAddress "LISTEN_ADDRESS"\n" "Port 123\n" @@ -305,7 +311,8 @@ static int setup_config_files(void **state) "LogLevel "LOGLEVEL"\n" "Ciphers "CIPHERS"\n" "MACs "MACS"\n" - "KexAlgorithms "KEXALGORITHMS"\n"); + "KexAlgorithms "KEXALGORITHMS"\n" + "RequiredRsaSize 2233\n"); torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE, "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS"\n" @@ -314,7 +321,8 @@ static int setup_config_files(void **state) "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL"\n" "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS"\n" "Include "LIBSSH_TEST_BIND_CONFIG_MACS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE"\n"); torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); @@ -1410,6 +1418,8 @@ static int assert_full_bind_config(void **state) assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); } + assert_int_equal(bind->rsa_min_size, 2233); + SAFE_FREE(fips_ciphers); SAFE_FREE(fips_kex); diff --git a/tests/unittests/torture_buffer.c b/tests/unittests/torture_buffer.c index 45022284..028645fa 100644 --- a/tests/unittests/torture_buffer.c +++ b/tests/unittests/torture_buffer.c @@ -6,6 +6,8 @@ #define DEBUG_BUFFER #include "buffer.c" +#include + #define LIMIT (8*1024*1024) static int setup(void **state) { @@ -154,7 +156,7 @@ static void torture_ssh_buffer_add_format(void **state) { d=0xbadc0de; q=0x13243546acbdcedf; s=ssh_string_from_char("libssh"); - rc=ssh_buffer_pack(buffer, "bwdqSsPt",b,w,d,q,s,"rocks",7,"So much","Fun!"); + rc=ssh_buffer_pack(buffer, "bwdqSsPt",b,w,d,q,s,"rocks",(size_t)7,"So much","Fun!"); assert_int_equal(rc, SSH_OK); len = ssh_buffer_get_len(buffer); @@ -259,17 +261,139 @@ static void torture_buffer_pack_badformat(void **state){ * it could crash the process */ } +static void torture_ssh_buffer_bignum(void **state) +{ + ssh_buffer buffer = NULL; + bignum num = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x00\x00\x00\x04" /* len 4 byte */ + "\x00\x00\x00\xff" /* padded 255 */ + "\x00\x00\x00\x02" /* len 2 byte */ + "\x00\xff"; /* padded 255 */ + + (void)state; + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + num = bignum_new(); + assert_non_null(num); + + rc = bignum_set_word(num, 255); + assert_int_equal(rc, 1); + + rc = ssh_buffer_pack(buffer, "FB", num, (size_t)4, num); + assert_int_equal(rc, SSH_OK); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, sizeof(verif) - 1); + assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) - 1); + + /* negative test -- this number requires 3 bytes */ + rc = bignum_set_word(num, 256 * 256); + assert_int_equal(rc, 1); + + rc = ssh_buffer_pack(buffer, "FB", num, (size_t)2, num); + assert_int_equal(rc, SSH_ERROR); + + bignum_safe_free(num); + + SSH_BUFFER_FREE(buffer); +} + +static void torture_ssh_buffer_dup(void **state) +{ + ssh_buffer buffer = *state; + ssh_buffer dup_buffer = NULL; + ssh_buffer null_buffer = NULL; + const char *test_data = "test data"; + size_t test_data_len = strlen(test_data); + int rc; + + /* test NULL buffer */ + dup_buffer = ssh_buffer_dup(NULL); + assert_null(dup_buffer); + + /* test empty buffer */ + dup_buffer = ssh_buffer_dup(buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), 0); + assert_true(dup_buffer->secure); + SSH_BUFFER_FREE(dup_buffer); + + /* test buffer with data */ + rc = ssh_buffer_add_data(buffer, test_data, test_data_len); + assert_int_equal(rc, SSH_OK); + dup_buffer = ssh_buffer_dup(buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + assert_memory_equal(ssh_buffer_get(dup_buffer), test_data, test_data_len); + assert_true(dup_buffer->secure); + + /* test independence of buffers - modify original buffer */ + rc = ssh_buffer_add_data(buffer, " more data", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + + /* test independence of buffers - modify duplicated buffer */ + rc = ssh_buffer_add_data(dup_buffer, " different", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + + assert_memory_not_equal(ssh_buffer_get(buffer), + ssh_buffer_get(dup_buffer), + test_data_len + 10); + + SSH_BUFFER_FREE(dup_buffer); + + /* test duplicating non-secure buffer */ + null_buffer = ssh_buffer_new(); + assert_non_null(null_buffer); + rc = ssh_buffer_add_data(null_buffer, "non-secure data", 15); + assert_int_equal(rc, SSH_OK); + + dup_buffer = ssh_buffer_dup(null_buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), 15); + assert_memory_equal(ssh_buffer_get(dup_buffer), "non-secure data", 15); + assert_false(dup_buffer->secure); + + SSH_BUFFER_FREE(dup_buffer); + SSH_BUFFER_FREE(null_buffer); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_growing_buffer, setup, teardown), - cmocka_unit_test_setup_teardown(torture_growing_buffer_shifting, setup, teardown), - cmocka_unit_test_setup_teardown(torture_buffer_prepend, setup, teardown), + cmocka_unit_test_setup_teardown(torture_growing_buffer, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_growing_buffer_shifting, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_prepend, + setup, + teardown), cmocka_unit_test(torture_ssh_buffer_get_ssh_string), - cmocka_unit_test_setup_teardown(torture_ssh_buffer_add_format, setup, teardown), - cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format, setup, teardown), - cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format_error, setup, teardown), - cmocka_unit_test_setup_teardown(torture_buffer_pack_badformat, setup, teardown) + cmocka_unit_test_setup_teardown(torture_ssh_buffer_add_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format_error, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_pack_badformat, + setup, + teardown), + cmocka_unit_test(torture_ssh_buffer_bignum), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_dup, + setup, + teardown), }; ssh_init(); diff --git a/tests/unittests/torture_callbacks.c b/tests/unittests/torture_callbacks.c index 25111b2f..ee8b0383 100644 --- a/tests/unittests/torture_callbacks.c +++ b/tests/unittests/torture_callbacks.c @@ -3,9 +3,10 @@ #define LIBSSH_STATIC #include "torture.h" -#include +#include #include #include +#include static int myauthcallback (const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { @@ -20,11 +21,10 @@ static int myauthcallback (const char *prompt, char *buf, size_t len, static int setup(void **state) { - struct ssh_callbacks_struct *cb; + struct ssh_callbacks_struct *cb = NULL; - cb = malloc(sizeof(struct ssh_callbacks_struct)); + cb = calloc(1, sizeof(struct ssh_callbacks_struct)); assert_non_null(cb); - ZERO_STRUCTP(cb); cb->userdata = (void *) 0x0badc0de; cb->auth_function = myauthcallback; @@ -249,11 +249,15 @@ static void torture_callbacks_iterate(void **state){ int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_callbacks_size, setup, teardown), - cmocka_unit_test_setup_teardown(torture_callbacks_exists, setup, teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_size, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_exists, + setup, + teardown), cmocka_unit_test(torture_log_callback), cmocka_unit_test(torture_callbacks_execute_list), - cmocka_unit_test(torture_callbacks_iterate) + cmocka_unit_test(torture_callbacks_iterate), }; ssh_init(); diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index db33152b..7ac9d9dc 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -14,6 +14,7 @@ #include "match.c" #include "config.c" #include "libssh/socket.h" +#include "libssh/misc.h" extern LIBSSH_THREAD int ssh_log_level; @@ -24,7 +25,7 @@ extern LIBSSH_THREAD int ssh_log_level; #define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" #define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" #define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" -#define USER_KNOWN_HOSTS "%d/my_known_hosts" +#define USER_KNOWN_HOSTS "%d/.ssh/my_known_hosts" #define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" #define BIND_ADDRESS "::1" @@ -45,12 +46,16 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" #define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" #define LIBSSH_TESTCONFIG17 "libssh_testconfig17.tmp" +#define LIBSSH_TESTCONFIG18 "libssh_testconfig18.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_MATCH_COMPLEX "libssh_test_match_complex.tmp" +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING "libssh_test_loglevel_missing.tmp" +#define LIBSSH_TESTCONFIG_JUMP "libssh_test_jump.tmp" #define LIBSSH_TESTCONFIG_STRING1 \ "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" @@ -88,7 +93,10 @@ extern LIBSSH_THREAD int ssh_log_level; "\tGSSAPIDelegateCredentials yes\n" \ "\tGSSAPIServerIdentity example.com\n" \ "\tGSSAPIClientIdentity home.sweet\n" \ - "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" + "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" \ + "\tRequiredRSASize 2233\n" \ + "\tGSSAPIKeyExchange yes\n" \ + "\tGSSAPIKexAlgorithms gss-group14-sha256-\n" /* authentication methods */ #define LIBSSH_TESTCONFIG_STRING8 \ @@ -151,6 +159,8 @@ extern LIBSSH_THREAD int ssh_log_level; "\tProxyJump jumpbox:2222\n" \ "Host two-step\n" \ "\tProxyJump u1@first:222,u2@second:33\n" \ + "Host three-step\n" \ + "\tProxyJump u1@first:222,u2@second:33,u3@third:444\n" \ "Host none\n" \ "\tProxyJump none\n" \ "Host only-command\n" \ @@ -184,7 +194,9 @@ extern LIBSSH_THREAD int ssh_log_level; /* 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 */ @@ -216,6 +228,15 @@ extern LIBSSH_THREAD int ssh_log_level; "\tControlMaster yes\n" \ "\tControlPath none\n" +#define LIBSSH_TESTCONFIG_STRING18 \ + "Host simple\n" \ + "Host af\n" \ + "\tAddressFamily any\n" \ + "Host af4\n" \ + "\tAddressFamily inet\n" \ + "Host af6\n" \ + "\tAddressFamily inet6\n" + #define LIBSSH_TEST_PUBKEYTYPES_STRING \ "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" @@ -232,6 +253,33 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ "Include " LIBSSH_TEST_RECURSIVE_INCLUDE +/* Complex match cases */ +#define LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING \ + "Match originalhost \"Foo,Bar\" exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "Match exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "\tForwardAgent yes\n" \ + "\tHostName complex-match\n" + +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING "LogLevel\n" +#define LIBSSH_TESTCONFIG_JUMP_STRING \ + "# The jump host\n" \ + "Host ub-jumphost\n" \ + " HostName 1xxxxxx\n" \ + " User ubuntu\n" \ + " IdentityFile ~/of/temp-libssh.pem\n" \ + " Port 23\n" \ + " LogLevel DEBUG3\n" \ + "\n" \ + "# Cisco Router through Jump Host\n" \ + "Host cisco-router\n" \ + " HostName xx.xxxxxxxxx\n" \ + " User username\n" \ + " ProxyJump ub-jumphost\n" \ + " Port 5555\n" \ + " #RequiredRSASize 512\n" \ + " PasswordAuthentication yes\n" \ + " LogLevel DEBUG3\n" + /** * @brief helper function loading configuration from either file or string */ @@ -277,10 +325,14 @@ static int setup_config_files(void **state) unlink(LIBSSH_TESTCONFIG15); unlink(LIBSSH_TESTCONFIG16); unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); unlink(LIBSSH_TEST_PUBKEYTYPES); unlink(LIBSSH_TEST_PUBKEYALGORITHMS); unlink(LIBSSH_TEST_NONEWLINEEND); unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); torture_write_file(LIBSSH_TESTCONFIG1, LIBSSH_TESTCONFIG_STRING1); @@ -333,6 +385,8 @@ static int setup_config_files(void **state) LIBSSH_TESTCONFIG_STRING16); torture_write_file(LIBSSH_TESTCONFIG17, LIBSSH_TESTCONFIG_STRING17); + torture_write_file(LIBSSH_TESTCONFIG18, + LIBSSH_TESTCONFIG_STRING18); torture_write_file(LIBSSH_TEST_PUBKEYTYPES, LIBSSH_TEST_PUBKEYTYPES_STRING); @@ -346,6 +400,14 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TEST_NONEWLINEONELINE, LIBSSH_TEST_NONEWLINEONELINE_STRING); + /* Match complex combinations */ + torture_write_file(LIBSSH_TESTCONFIG_MATCH_COMPLEX, + LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING); + torture_write_file(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, + LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING); + torture_write_file(LIBSSH_TESTCONFIG_JUMP, + LIBSSH_TESTCONFIG_JUMP_STRING); + return 0; } @@ -369,8 +431,14 @@ static int teardown_config_files(void **state) unlink(LIBSSH_TESTCONFIG15); unlink(LIBSSH_TESTCONFIG16); unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); unlink(LIBSSH_TEST_PUBKEYTYPES); unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); return 0; } @@ -607,9 +675,15 @@ static void torture_config_new(void ** state, assert_int_equal(session->opts.gss_delegate_creds, 1); assert_string_equal(session->opts.gss_server_identity, "example.com"); assert_string_equal(session->opts.gss_client_identity, "home.sweet"); +#ifdef WITH_GSSAPI + assert_true(session->opts.gssapi_key_exchange); + assert_string_equal(session->opts.gssapi_key_exchange_algs, + "gss-group14-sha256-"); +#endif /* WITH_GSSAPI */ assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE); assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); + assert_int_equal(session->opts.rsa_min_size, 2233); } static void torture_config_new_file(void **state) @@ -689,6 +763,34 @@ static void torture_config_auth_methods_string(void **state) torture_config_auth_methods(state, NULL, LIBSSH_TESTCONFIG_STRING8); } +/** + * @brief Helper for checking hostname, username and port of ssh_jump_info_struct + */ +static void +helper_proxy_jump_check(struct ssh_iterator *jump, + const char *hostname, + const char *username, + const char *port) +{ + struct ssh_jump_info_struct *jis = + ssh_iterator_value(struct ssh_jump_info_struct *, jump); + + assert_string_equal(jis->hostname, hostname); + + if (username != NULL) { + assert_string_equal(jis->username, username); + } else { + assert_null(jis->username); + } + + if (port != NULL) { + int iport = strtol(port, NULL, 10); + assert_int_equal(jis->port, iport); + } else { + assert_int_equal(jis->port, 22); + } +} + /** * @brief Verify the configuration parser does not choke on unknown * or unsupported configuration options @@ -700,15 +802,18 @@ static void torture_config_unknown(void **state, int ret = 0; /* test corner cases */ + /* Without libssh proxy jump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); _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"); assert_true(ret == 0); ret = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); assert_true(ret == 0); + torture_unsetenv("OPENSSH_PROXYJUMP"); } /** @@ -738,7 +843,7 @@ static void torture_config_match(void **state, { ssh_session session = *state; char *localuser = NULL; - const char *config; + const char *config = NULL; char config_string[1024]; /* Without any settings we should get all-matched.com hostname */ @@ -860,7 +965,7 @@ static void torture_config_match(void **state, } torture_reset_config(session); _parse_config(session, file, string, SSH_OK); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_string_equal(session->opts.host, "otherhost"); #else @@ -876,7 +981,7 @@ static void torture_config_match(void **state, } torture_reset_config(session); _parse_config(session, file, string, SSH_OK); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_string_equal(session->opts.host, "otherhost"); #else @@ -892,7 +997,7 @@ static void torture_config_match(void **state, } torture_reset_config(session); _parse_config(session, file, string, SSH_OK); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_string_equal(session->opts.host, "otherhost"); #else @@ -909,7 +1014,9 @@ static void torture_config_match(void **state, string = config; } torture_reset_config(session); - _parse_config(session, file, string, SSH_ERROR); + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "unmatched"); /* Missing argument to unsupported option originalhost */ config = "Match originalhost\n" @@ -965,6 +1072,36 @@ static void torture_config_match(void **state, } torture_reset_config(session); _parse_config(session, file, string, SSH_ERROR); + + /* Unknown argument to Match keyword */ + config = "Match tagged tag_name\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); + + /* Missing argument to Match keyword */ + config = "Match\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); } /** @@ -992,7 +1129,105 @@ static void torture_config_proxyjump(void **state, const char *file, const char *string) { ssh_session session = *state; - const char *config; + + const char *config = NULL; + + + /* Tests for libssh based proxyjump */ + /* Simplest version with just a hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user", + NULL); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + "2222"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "first", + "u1", + "222"); + + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "third", + "u3", + "444"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next->next, + "first", + "u1", + "222"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* 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); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + SAFE_FREE(session->opts.ProxyCommand); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.ProxyCommand); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "2620:52:0::fed", + NULL, + NULL); + + torture_reset_config(session); + + /* Tests for proxycommand based proxyjump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); /* Simplest version with just a hostname */ torture_reset_config(session); @@ -1021,6 +1256,13 @@ static void torture_config_proxyjump(void **state, assert_string_equal(session->opts.ProxyCommand, "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33,u3@third:444 -W '[%h]:%p' first"); + /* none */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "none"); @@ -1047,23 +1289,55 @@ static void torture_config_proxyjump(void **state, assert_string_equal(session->opts.ProxyCommand, "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"); + + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" + "\tProxyJump 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, "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"); + torture_unsetenv("OPENSSH_PROXYJUMP"); + + /* Tests for libssh based proxyjump */ + /* 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, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); - /* 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); @@ -1071,7 +1345,28 @@ 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); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); + torture_reset_config(session); + + /* In this part, we try various other config files and strings. */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + + /* 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 */ @@ -1146,18 +1441,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"; @@ -1217,6 +1500,8 @@ static void torture_config_proxyjump(void **state, torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); _parse_config(session, file, string, SSH_ERROR); + + torture_unsetenv("OPENSSH_PROXYJUMP"); } /** @@ -1246,7 +1531,7 @@ static void torture_config_control_path(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.control_path, "/tmp/ssh-%r@%h:%p"); + assert_null(session->opts.control_path); torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "none"); @@ -1281,12 +1566,12 @@ static void torture_config_control_master(void **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); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); 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); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); } /** @@ -1305,6 +1590,79 @@ static void torture_config_control_master_file(void **state) torture_config_control_master(state, LIBSSH_TESTCONFIG17, NULL); } +/** + * @brief Verify we can parse AdressFamily configuration option + */ +static void torture_config_address_family(void **state, + const char *file, + const char *string) +{ + ssh_session session = *state; + + const char *config = NULL; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af4"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af6"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET6); + + /* test for parsing failures */ + config = "Host afmissing\n" + "\tAddressFamily\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afmissing"); + _parse_config(session, file, string, SSH_ERROR); + + config = "Host afinvalid\n" + "\tAddressFamily wurstkäse\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afinvalid"); + _parse_config(session, file, string, SSH_ERROR); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from string + */ +static void torture_config_address_family_string(void **state) +{ + torture_config_address_family(state, NULL, LIBSSH_TESTCONFIG_STRING18); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from file + */ +static void torture_config_address_family_file(void **state) +{ + torture_config_address_family(state, LIBSSH_TESTCONFIG18, NULL); +} + /** * @brief Verify the configuration parser handles all the possible * versions of RekeyLimit configuration option. @@ -1425,7 +1783,7 @@ static void torture_config_plus(void **state, 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 *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; @@ -1552,8 +1910,6 @@ static void torture_config_minus(void **state, 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); @@ -1670,8 +2026,6 @@ static void torture_config_caret(void **state, 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); @@ -1853,7 +2207,7 @@ static void torture_config_parser_get_cmd(void **state) { char *p = NULL, *tok = NULL; char data[256]; -#ifdef __unix__ +#ifdef WITH_EXEC FILE *outfile = NULL, *infile = NULL; int pid; char buffer[256] = {0}; @@ -1897,7 +2251,7 @@ static void torture_config_parser_get_cmd(void **state) assert_string_equal(tok, data); assert_int_equal(*p, '\0'); -#ifdef __unix__ +#ifdef WITH_EXEC /* 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" => */ @@ -1913,10 +2267,10 @@ static void torture_config_parser_get_cmd(void **state) } else if (pid == 0) { ssh_execute_command(tok, fileno(outfile), fileno(outfile)); /* Does not return */ - } else { - /* parent + } else { + /* parent * wait child process */ - wait(NULL); + wait(NULL); infile = fopen("output.log", "r"); assert_non_null(infile); p = fgets(buffer, sizeof(buffer), infile); @@ -1927,13 +2281,14 @@ static void torture_config_parser_get_cmd(void **state) fclose(outfile); assert_string_equal(buffer, "hello libssh"); -#endif +#endif /* WITH_EXEC */ } /* ssh_config_get_token() should behave as expected * * Strip leading whitespace * * Return first token separated by whitespace or equal sign, * respecting quotes! + * * Correctly treat escaped quotes inside of quotes. */ static void torture_config_parser_get_token(void **state) { @@ -2106,6 +2461,28 @@ static void torture_config_parser_get_token(void **state) tok = ssh_config_get_token(&p); assert_string_equal(tok, "value"); assert_int_equal(*p, '\0'); + + /* Escaped quotes */ + strncpy(data, " \"value with \\\"escaped\\\" quotes\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with \"escaped\" quotes"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\\\"value with \\\"escaped\\\" quotes\\\"\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"value"); + assert_int_equal(*p, 'w'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "with"); + assert_int_equal(*p, '\\'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"escaped\\\""); + assert_int_equal(*p, 'q'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "quotes\\\""); + assert_int_equal(*p, '\0'); } /* match_pattern() sanity tests @@ -2117,80 +2494,138 @@ static void torture_config_match_pattern(void **state) (void) state; /* Simple test "a" matches "a" */ - rv = match_pattern("a", "a", MAX_MATCH_RECURSION); + rv = match_pattern("a", "a"); assert_int_equal(rv, 1); /* Simple test "a" does not match "b" */ - rv = match_pattern("a", "b", MAX_MATCH_RECURSION); + rv = match_pattern("a", "b"); assert_int_equal(rv, 0); /* NULL arguments are correctly handled */ - rv = match_pattern("a", NULL, MAX_MATCH_RECURSION); + rv = match_pattern("a", NULL); assert_int_equal(rv, 0); - rv = match_pattern(NULL, "a", MAX_MATCH_RECURSION); + rv = match_pattern(NULL, "a"); assert_int_equal(rv, 0); /* Simple wildcard ? is handled in pattern */ - rv = match_pattern("a", "?", MAX_MATCH_RECURSION); + rv = match_pattern("a", "?"); assert_int_equal(rv, 1); - rv = match_pattern("aa", "?", MAX_MATCH_RECURSION); + rv = match_pattern("aa", "?"); assert_int_equal(rv, 0); /* Wildcard in search string */ - rv = match_pattern("?", "a", MAX_MATCH_RECURSION); + rv = match_pattern("?", "a"); assert_int_equal(rv, 0); - rv = match_pattern("?", "?", MAX_MATCH_RECURSION); + rv = match_pattern("?", "?"); assert_int_equal(rv, 1); /* Simple wildcard * is handled in pattern */ - rv = match_pattern("a", "*", MAX_MATCH_RECURSION); + rv = match_pattern("a", "*"); assert_int_equal(rv, 1); - rv = match_pattern("aa", "*", MAX_MATCH_RECURSION); + rv = match_pattern("aa", "*"); assert_int_equal(rv, 1); /* Wildcard in search string */ - rv = match_pattern("*", "a", MAX_MATCH_RECURSION); + rv = match_pattern("*", "a"); assert_int_equal(rv, 0); - rv = match_pattern("*", "*", MAX_MATCH_RECURSION); + rv = match_pattern("*", "*"); assert_int_equal(rv, 1); /* More complicated patterns */ - rv = match_pattern("a", "*a", MAX_MATCH_RECURSION); + rv = match_pattern("a", "*a"); assert_int_equal(rv, 1); - rv = match_pattern("a", "a*", MAX_MATCH_RECURSION); + rv = match_pattern("a", "a*"); assert_int_equal(rv, 1); - rv = match_pattern("abababc", "*abc", MAX_MATCH_RECURSION); + rv = match_pattern("abababc", "*abc"); assert_int_equal(rv, 1); - rv = match_pattern("ababababca", "*abc", MAX_MATCH_RECURSION); + rv = match_pattern("ababababca", "*abc"); assert_int_equal(rv, 0); - rv = match_pattern("ababababca", "*abc*", MAX_MATCH_RECURSION); + rv = match_pattern("ababababca", "*abc*"); assert_int_equal(rv, 1); /* Multiple wildcards in row */ - rv = match_pattern("aa", "??", MAX_MATCH_RECURSION); + rv = match_pattern("aa", "??"); assert_int_equal(rv, 1); - rv = match_pattern("bba", "??a", MAX_MATCH_RECURSION); + rv = match_pattern("bba", "??a"); assert_int_equal(rv, 1); - rv = match_pattern("aaa", "**a", MAX_MATCH_RECURSION); + rv = match_pattern("aaa", "**a"); assert_int_equal(rv, 1); - rv = match_pattern("bbb", "**a", MAX_MATCH_RECURSION); + rv = match_pattern("bbb", "**a"); assert_int_equal(rv, 0); /* Consecutive asterisks do not make sense and do not need to recurse */ - rv = match_pattern("hostname", "**********pattern", 5); + rv = match_pattern("hostname", "**********pattern"); + assert_int_equal(rv, 0); + rv = match_pattern("hostname", "pattern**********"); assert_int_equal(rv, 0); - rv = match_pattern("hostname", "pattern**********", 5); + rv = match_pattern("pattern", "***********pattern"); + assert_int_equal(rv, 1); + rv = match_pattern("pattern", "pattern***********"); + assert_int_equal(rv, 1); + + rv = match_pattern("hostname", "*p*a*t*t*e*r*n*"); assert_int_equal(rv, 0); - rv = match_pattern("pattern", "***********pattern", 5); + rv = match_pattern("pattern", "*p*a*t*t*e*r*n*"); assert_int_equal(rv, 1); - rv = match_pattern("pattern", "pattern***********", 5); + + /* Regular Expression Denial of Service */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a"); + assert_int_equal(rv, 1); + rv = match_pattern("ababababababababababababababababababababab", + "*a*b*a*b*a*b*a*b*a*b*a*b*a*b*a*b"); + assert_int_equal(rv, 1); + + /* A lot of backtracking */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax", + "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*ax"); + assert_int_equal(rv, 1); + + /* Test backtracking: *a matches first 'a', fails on 'b', must backtrack */ + rv = match_pattern("axaxaxb", "*a*b"); + assert_int_equal(rv, 1); + + /* Test greedy consumption with suffix */ + rv = match_pattern("foo_bar_baz_bar", "*bar"); assert_int_equal(rv, 1); - /* Limit the maximum recursion */ - rv = match_pattern("hostname", "*p*a*t*t*e*r*n*", 5); + /* Test exact suffix requirement (ensure no partial match acceptance) */ + rv = match_pattern("foobar_extra", "*bar"); assert_int_equal(rv, 0); - /* Too much recursion */ - rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5); + + /* Test multiple distinct wildcards */ + rv = match_pattern("a_very_long_string_with_a_pattern", "*long*pattern"); + assert_int_equal(rv, 1); + + /* ? inside a * sequence */ + rv = match_pattern("abcdefg", "a*c?e*g"); + assert_int_equal(rv, 1); + + /* Consecutive mixed wildcards */ + rv = match_pattern("abc", "*?c"); + assert_int_equal(rv, 1); + + /* ? at the very end after * */ + rv = match_pattern("abc", "ab?"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "ab*?"); + assert_int_equal(rv, 1); + + /* Consecutive stars should be collapsed or handled gracefully */ + rv = match_pattern("abc", "a**c"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "***"); + assert_int_equal(rv, 1); + + /* Empty string handling */ + rv = match_pattern("", "*"); + assert_int_equal(rv, 1); + rv = match_pattern("", "?"); assert_int_equal(rv, 0); + rv = match_pattern("", ""); + assert_int_equal(rv, 1); + /* Pattern longer than string */ + rv = match_pattern("short", "short_but_longer"); + assert_int_equal(rv, 0); } /* Identity file can be specified multiple times in the configuration @@ -2198,6 +2633,7 @@ 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; @@ -2214,6 +2650,20 @@ 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 @@ -2223,24 +2673,14 @@ 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"); + char h[256] = {0}; + char *user = NULL; + char *home = NULL; + struct passwd *pw = getpwuid(getuid()); + assert_non_null(pw); + user = strdup(pw->pw_name); + assert_non_null(user); + home = strdup(pw->pw_dir); assert_non_null(home); #endif @@ -2298,6 +2738,8 @@ static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); assert_string_equal(result, h); free(result); + free(home); + free(user); #endif } @@ -2311,101 +2753,362 @@ 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); +} + +/* Complex ssh match configurations + */ +static void torture_config_match_complex(void **state) +{ + ssh_session session = *state; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_MATCH_COMPLEX, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_non_null(v); +#ifndef WITH_EXEC + assert_string_equal(session->opts.host, "Bar"); +#else + assert_string_equal(v, "complex-match"); +#endif + ssh_string_free_char(v); +} + +/* Missing value to LogLevel configuration option + */ +static void torture_config_loglevel_missing_value(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, NULL, SSH_OK); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + char *v = NULL; + int ret; + + (void)user; + + /* During the connection, we force parsing the same configuration file + * (would be normally parsed automatically during the connection itself) + */ + ret = ssh_config_parse_file(jump_session, LIBSSH_TESTCONFIG_JUMP); + assert_return_code(ret, errno); + + /* Test the variable presence */ + ret = ssh_options_get(jump_session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "1xxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(jump_session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "ubuntu"); + ssh_string_free_char(v); + + assert_int_equal(jump_session->opts.port, 23); + + /* Fail the connection -- we are in unit tests so it would fail anyway */ + return 1; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} +/* Reproducer for complex proxy jump + */ +static void torture_config_jump(void **state) +{ + ssh_session session = *state; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "cisco-router"); + + _parse_config(session, LIBSSH_TESTCONFIG_JUMP, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "xx.xxxxxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "username"); + ssh_string_free_char(v); + + assert_int_equal(session->opts.port, 5555); + + /* At this point, the configuration file is not parsed for the jump host so + * we are getting just the the hostname -- the port and username will get + * pulled during the session connecting to this host */ + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 1); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "ub-jumphost", + NULL, + NULL); + + /* Set up the callbacks -- they should verify we are going to connect to the + * right host */ + ret = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, ret); + + ret = ssh_connect(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + printf("%s: EOF\n", __func__); +} + +/* Invalid configuration files + */ +static void torture_config_invalid(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + /* non-regular file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/dev/random", NULL, SSH_OK); + +#ifndef _WIN32 + /* huge file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/proc/kcore", NULL, SSH_OK); +#endif +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_config_include_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_include_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_double_ports_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_glob_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_glob_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_new_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_new_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_auth_methods_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_auth_methods_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_unknown_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_unknown_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_match_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_match_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_proxyjump_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_control_path_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_control_path_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_control_master_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_control_master_string, - setup, teardown), + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_string, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_rekey_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_plus_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_plus_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_minus_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_minus_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_caret_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_caret_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_file, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_string, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_parser_get_cmd, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_parser_get_token, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_match_pattern, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_identity, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_make_absolute, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, - setup_no_sshdir, teardown), + setup_no_sshdir, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_complex, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_jump, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_invalid, + setup, + teardown), }; - ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, diff --git a/tests/unittests/torture_config_match_localnetwork.c b/tests/unittests/torture_config_match_localnetwork.c new file mode 100644 index 00000000..4c20db08 --- /dev/null +++ b/tests/unittests/torture_config_match_localnetwork.c @@ -0,0 +1,752 @@ +#include "config.h" +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "match.c" +#ifdef HAVE_IFADDRS_H +#include +#endif +#include +#include + +/* This list contains common local subnet addresses and more generic ones */ +#define IPV4_LIST \ + "158.46.192.0/18,213.86.215.224/27,61.67.54.0/23,164.155.128.0/21," \ + "171.10.0.0/16,205.59.221.0/24,122.105.209.48/28,10.0.1.0/24," \ + "130.192.28.0/22,172.16.16.0/16,192.168.0.0/24,169.254.0.0/16" + +#define IPV6_LIST "fe80::/64" + +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 +teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_session session, + const char *file, + const char *string, + int expected) +{ + /* + * Initialisation of ret is not needed, but the compiler is not able to + * understand fail() so it will complain about uninitialised use of ret + * below in assert_ssh_return_code_equal() + */ + 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_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +/** + * @brief converts subnet mask to prefix length (IPv4) + */ +static int +subnet_mask_to_prefix_length_4(struct in_addr subnet_mask) +{ + uint32_t mask; + int prefix_length = 0; + + mask = ntohl(subnet_mask.s_addr); + + /* Count the number of consecutive 1 bits */ + while (mask & 0x80000000) { + prefix_length++; + mask <<= 1; + } + return prefix_length; +} + +/** + * @brief converts subnet mask to prefix length (IPv6) + */ +static int +subnet_mask_to_prefix_length_6(struct in6_addr subnet_mask) +{ + uint8_t *mask = NULL, chunk; + int i, j, prefix_length = 0; + + mask = subnet_mask.s6_addr; + + /* Count the number of consecutive 1 bits in each byte chunk */ + for (i = 0; i < 16; i++) { + chunk = mask[i]; + while (chunk) { + for (j = 0; j < 8; j++) { + if (chunk & 0x80) { + prefix_length++; + chunk <<= 1; + } else { + break; + } + } + } + } + return prefix_length; +} + +/** + * @brief helper function returning the IPv4 and IPv6 network ID + * (in CIDR format) corresponding to any of the running local interfaces. + * The network interface corresponding to IPv4 and IPv6 network ID may be + * different. + * + * @note If no non-loopback network interfaces are found for IPv4 or + * IPv6, the function will fall back to using the loopback addresses. + */ +static int +get_network_id(char *net_id_4, char *net_id_6) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + struct in_addr addr, network_id_4, subnet_mask_4; + struct in6_addr addr6, network_id_6, subnet_mask_6; + struct sockaddr_in netmask; + struct sockaddr_in6 netmask6; + char address[NI_MAXHOST], *a = NULL; + char *network_id_str = NULL, network_id_str6[INET6_ADDRSTRLEN], + lo_net_id_4[NI_MAXHOST], lo_net_id_6[NI_MAXHOST]; + int i, prefix_length, rc; + int found_4 = 0, found_lo_4 = 0, found_6 = 0, found_lo_6 = 0; + socklen_t sa_len; + + ZERO_STRUCT(addr); + ZERO_STRUCT(network_id_4); + ZERO_STRUCT(subnet_mask_4); + + ZERO_STRUCT(addr6); + ZERO_STRUCT(network_id_6); + ZERO_STRUCT(subnet_mask_6); + + if (getifaddrs(&ifaddrs) != 0) { + goto out; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (found_4 && found_6) { + break; + } + + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + if (found_4) { + continue; + } + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (found_6) { + continue; + } + sa_len = sizeof(struct sockaddr_in6); + break; + default: + continue; + } + + rc = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (rc != 0) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + + /* Extract subnet mask */ + memcpy(&netmask, ifa->ifa_netmask, sizeof(struct sockaddr_in)); + subnet_mask_4 = netmask.sin_addr; + + rc = inet_pton(AF_INET, address, &addr); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + network_id_4.s_addr = addr.s_addr & subnet_mask_4.s_addr; + + /* Convert network ID to string and compute prefix length */ + network_id_str = inet_ntoa(network_id_4); + if (network_id_str == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_4(subnet_mask_4); + if (prefix_length > 32) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_lo_4 = 1; + } else { + snprintf(net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_4 = 1; + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(address, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* Extract subnet mask */ + memcpy(&netmask6, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); + subnet_mask_6 = netmask6.sin6_addr; + + rc = inet_pton(AF_INET6, address, &addr6); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + for (i = 0; i < 16; i++) { + network_id_6.s6_addr[i] = + addr6.s6_addr[i] & subnet_mask_6.s6_addr[i]; + } + + /* Convert network ID to string and compute prefix length */ + if (inet_ntop(AF_INET6, + &network_id_6, + network_id_str6, + INET6_ADDRSTRLEN) == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_6(subnet_mask_6); + if (prefix_length > 128) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_lo_6 = 1; + } else { + snprintf(net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_6 = 1; + } + } + } + + /* + * Fallback to the loopback network ID (127.0.0.0/8) if no other + * IPv4 network ID has been found. + */ + if (!found_4 && found_lo_4) { + snprintf(net_id_4, NI_MAXHOST, "%s", lo_net_id_4); + found_4 = 1; + } + + /* + * Fallback to the loopback network ID (::1/128) if no other + * IPv6 network ID has been found. + */ + if (!found_6 && found_lo_6) { + snprintf(net_id_6, NI_MAXHOST, "%s", lo_net_id_6); + found_6 = 1; + } + + freeifaddrs(ifaddrs); + +out: + /* if both net_id_4 and net_id_6 are not set then we should fail */ + return (found_4 && found_6) ? 0 : -1; +} + +/** + * @brief Verify the match between a IPv4/IPv6 address and a IPv4/IPv6 subnet + */ +static void +assert_true_match_cidr(const char *try, + const char *match, + unsigned int mask_len, + int af, + int rv) +{ + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + int r1, r2; + + switch (af) { + case AF_INET: + ZERO_STRUCT(try_addr); + ZERO_STRUCT(match_addr); + + r1 = inet_pton(AF_INET, try, &try_addr); + r2 = inet_pton(AF_INET, match, &match_addr); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_4(&try_addr, &match_addr, mask_len), rv); + break; + case AF_INET6: + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr6); + + r1 = inet_pton(AF_INET6, try, &try_addr6); + r2 = inet_pton(AF_INET6, match, &match_addr6); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_6(&try_addr6, &match_addr6, mask_len), rv); + break; + default: + fail(); + } +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + */ +static void +torture_config_match_localnetwork(void **state, bool use_file) +{ + ssh_session session = *state; + const char *config = NULL; + char config_string[2048]; + char network_id_4[NI_MAXHOST], network_id_6[NI_MAXHOST]; + const char *file = NULL, *string = NULL; + + if (use_file == true) { + file = "libssh_testconfig_localnetwork.tmp"; + } + + if (get_network_id(network_id_4, network_id_6) == -1) { + fail(); + } + + /* IPv4 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* IPv6 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_6); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* Test negate condition */ + snprintf(config_string, + sizeof(config_string), + "Match Host station !localnetwork %s\n" + "\tHostName expected.com\n" + "Host station\n" + "\tHostName negate.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "station"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "negate.com"); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration file. + */ +static void +torture_config_match_localnetwork_file(void **state) +{ + torture_config_match_localnetwork(state, true); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration string. + */ +static void +torture_config_match_localnetwork_string(void **state) +{ + torture_config_match_localnetwork(state, false); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv4 addresses + */ +static void +torture_match_cidr_address_list_ipv4(void **state) +{ + int rc; + (void)state; + + /* Test some valid IPv4 addresses */ + rc = match_cidr_address_list("192.158.50.5", "192.158.50.0/28", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.2.200.200", "10.2.128.0/17", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("192.168.175.40", "192.168.175.0/26", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.31.140.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.3.9.50", "10.3.8.0/23", AF_INET); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("158.15.96.13", "158.12.30.0/12", -1); + assert_int_equal(rc, 1); + + /* Test some valid IPv4 addresses against IPV4_LIST */ + rc = match_cidr_address_list("164.155.128.15", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("158.46.223.71", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("205.59.221.160", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.0.1.254", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.16.58.1", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("169.254.20.28", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + + rc = match_cidr_address_list("255.255.255.255", "0.0.0.0/0", AF_INET); + assert_int_equal(rc, 1); + + /* Test some not matching IPv4 addresses */ + rc = match_cidr_address_list("172.21.0.200", "172.20.240.0/20", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.10.14.100", "10.10.10.0/22", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.150.8", "192.168.150.0/29", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.238.16.50", "10.255.0.0/12", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("172.31.160.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.4.98", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("0.0.0.0", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("122.105.210.57", IPV4_LIST, -1); + assert_int_equal(rc, 0); + + /* Test some invalid input */ + rc = match_cidr_address_list("192.168.1.x", "192.168.1.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("0.168.f2.b8", "172.0.0.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/22", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.16.5/abc1", "172.16.16.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.18.251", "172.16.16.0", AF_INET); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("172.67.3.x", IPV4_LIST, -1); + assert_int_equal(rc, -1); + + /* Test invalid CIDR list */ + rc = match_cidr_address_list(NULL, "192.168.1.0/33", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "10.0.0.0/24 , 192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list( + NULL, + "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128junkdata", + -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv6 addresses + */ +static void +torture_match_cidr_address_list_ipv6(void **state) +{ + /* Test link-local addresses against fe80::/64 */ + int i, rc, valid_addr_len, invalid_addr_len; + const char *valid_addr[] = {"fe80::aadf:b119:507a:986a%abcdef", + "fe80::0000:b418:efd4:5160:0a25%abcdef", + "fe80::c7f5:7f94:4bd9:c35c%abcdef", + "fe80::321f:46c2:0cea:ec54%abcdef", + "fe80::906d:b670:86a2:fd68%abc", + "fe80::b1c2:0000:0039:b598%", + "fe80::07e8:39e6:cb49:9cd4", + "fe80::1%abcdef", + "fe80:0:0:0:202:b3ff:fe1e:8329%abcdef", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329"}; + + const char *invalid_addr[] = {"fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + "2001:0db8:85a3::8a2e:0370:7334%abcdef", + "fd00::adf8:7c21:147c:6c97", + "::1%lo", + "fe80::1:4d88:68a8:1200:f3e7%abcdef"}; + + (void)state; + + /* Test valid link-local addresses */ + valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]); + for (i = 0; i < valid_addr_len; i++) { + rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 1); + } + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8328/127", + AF_INET6); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("fe80::aadf:b119:507a:986a%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 1); + + /* Test some invalid input */ + invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]); + for (i = 0; i < invalid_addr_len; i++) { + rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 0); + } + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 0); + + /* Test errors */ + rc = match_cidr_address_list("fe80::be50:09ca::2be3", IPV6_LIST, AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:x:202:b3ff:fe1e:8329", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80::202:ghfc:zzzz:1a49", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329/131", + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329//127", + AF_INET6); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("fe80::ba67:1002:gffx:zz32", IPV6_LIST, -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr_match_4 function works correctly + */ +static void +torture_match_cidr_v4(void **state) +{ + int af = AF_INET; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("192.168.1.20", "192.168.1.0", 24, af, 1); + assert_true_match_cidr("172.31.5.128", "172.31.0.0", 16, af, 1); + assert_true_match_cidr("10.0.0.158", "10.0.0.128", 25, af, 1); + assert_true_match_cidr("192.168.255.250", "192.168.255.248", 29, af, 1); + assert_true_match_cidr("122.105.209.57", "122.105.209.48", 28, af, 1); + assert_true_match_cidr("192.168.100.150", "192.168.64.0", 18, af, 1); + + /* Test some not matching input */ + assert_true_match_cidr("172.16.56.30", "172.16.48.0", 21, af, 0); + assert_true_match_cidr("10.18.5.5", "10.10.4.0", 23, af, 0); + assert_true_match_cidr("172.16.32.50", "172.16.0.0", 19, af, 0); + assert_true_match_cidr("203.0.120.10", "203.0.112.0", 21, af, 0); + assert_true_match_cidr("172.31.112.150", "172.31.96.0", 20, af, 0); + assert_true_match_cidr("198.52.20.200", "198.48.0.0", 14, af, 0); +} + +/** + * @brief Verify the cidr_match_6 function works correctly + */ +static void +torture_match_cidr_v6(void **state) +{ + int af = AF_INET6; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:0db8:85a3:0000::", + 64, + af, + 1); + assert_true_match_cidr("2001:0db8:0000:0042:0000:8a2e:0370:7334", + "2001:0db8:0000::", + 48, + af, + 1); + assert_true_match_cidr("fe80::8a2e:0370:7334", "fe80::", 64, af, 1); + assert_true_match_cidr("fd00::8a2e:0370:7334", "fd00::", 56, af, 1); + assert_true_match_cidr("fe80:0000:0000:0000:0000:0000:fe1e:32ff", + "fe80::", + 96, + af, + 1); + assert_true_match_cidr("2001:0db8:1a2b:3c4d:5e6f:7a8b::18", + "2001:0db8:1a2b:3c4d:5e6f:7a8b::", + 120, + af, + 1); + + /* Test some not matching input */ + assert_true_match_cidr("2001:0db8:1234:5678:9abc:def0:1234:5678", + "2001:0db8:1234:5678::", + 96, + af, + 0); + assert_true_match_cidr("2001:3858:accd::", + "2001:3858:abcd:eaa1::", + 48, + af, + 0); + assert_true_match_cidr("2001:0db8:1234:5678::ff4c", + "2001:0db8:1234:5600::", + 110, + af, + 0); + assert_true_match_cidr("fe80::0001:af12:a1b2:c3d4:e5f7", + "fe80::", + 64, + af, + 0); + assert_true_match_cidr("2001:0db8:84ff:ffff:ffff:ffff:ffff:fffa", + "2001:0db8:8500::", + 80, + af, + 0); + assert_true_match_cidr("::3", "::", 127, af, 0); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_config_match_localnetwork_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_localnetwork_file, + setup, + teardown), + cmocka_unit_test(torture_match_cidr_address_list_ipv4), + cmocka_unit_test(torture_match_cidr_address_list_ipv6), + cmocka_unit_test(torture_match_cidr_v4), + cmocka_unit_test(torture_match_cidr_v6), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup, teardown); + ssh_finalize(); + return rc; +} diff --git a/tests/unittests/torture_forwarded_tcpip_callback.c b/tests/unittests/torture_forwarded_tcpip_callback.c new file mode 100644 index 00000000..cf80c87b --- /dev/null +++ b/tests/unittests/torture_forwarded_tcpip_callback.c @@ -0,0 +1,336 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +struct server_thread_args { + struct hostkey_state *h; + bool should_accept; +}; + +static bool is_server_ready = false; +static pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t server_cond = PTHREAD_COND_INITIALIZER; + +static bool client_callbacks_initialised = false; +static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t client_cond = PTHREAD_COND_INITIALIZER; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = (struct hostkey_state *)malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + /* Reset before every test */ + is_server_ready = false; + client_callbacks_initialised = false; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +static void *server_thread(void *arg) +{ + struct server_thread_args *args = (struct server_thread_args *)arg; + struct hostkey_state *h = args->h; + bool should_accept = args->should_accept; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_channel channel = NULL; + ssh_event event = NULL; + int rc; + + struct ssh_server_callbacks_struct server_cb = { + .auth_password_function = auth_password_accept, + }; + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_set_server_callbacks(server, &server_cb); + assert_int_equal(rc, SSH_OK); + + /* Signal that the server is ready */ + pthread_mutex_lock(&server_mutex); + is_server_ready = true; + pthread_cond_signal(&server_cond); + pthread_mutex_unlock(&server_mutex); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, server); + assert_int_equal(rc, SSH_OK); + + /* Poll until authentication is complete */ + while (server->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + break; + } + } + + /* Cleanup the event */ + ssh_event_free(event); + + /* Wait for client callbacks to be initialized before proceeding */ + pthread_mutex_lock(&client_mutex); + while (!client_callbacks_initialised) { + pthread_cond_wait(&client_cond, &client_mutex); + } + pthread_mutex_unlock(&client_mutex); + + channel = ssh_channel_new(server); + assert_non_null(channel); + + rc = ssh_channel_open_reverse_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_bind_free(sshbind); + ssh_free(server); + + return NULL; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_forwarded_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = (struct channel_data *)userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a forwarded-tcpip request and store the parameters + */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + + return channel; +} + +static void torture_forwarded_tcpip_callback(void **state, bool should_accept) +{ + int rc, event_rc; + pthread_t server_pthread; + ssh_session session = NULL; + ssh_event event = NULL; + struct channel_data channel_data; + unsigned int server_port = TEST_SERVER_PORT; + + struct server_thread_args args = { + .h = (struct hostkey_state *)*state, + .should_accept = should_accept, + }; + + struct ssh_callbacks_struct client_cb = { + .userdata = &channel_data, + .channel_open_request_forwarded_tcpip_function = + channel_forwarded_tcpip_callback, + }; + ssh_callbacks_init(&client_cb); + + memset(&channel_data, 0, sizeof(channel_data)); + channel_data.should_accept = should_accept; + + rc = pthread_create(&server_pthread, NULL, server_thread, &args); + assert_return_code(rc, errno); + + /* Wait for the server to be ready using condition variable */ + pthread_mutex_lock(&server_mutex); + while (!is_server_ready) { + pthread_cond_wait(&server_cond, &server_mutex); + } + pthread_mutex_unlock(&server_mutex); + + session = + torture_ssh_session(NULL, "127.0.0.1", &server_port, "foo", "bar"); + assert_non_null(session); + + rc = ssh_set_callbacks(session, &client_cb); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, session); + assert_int_equal(rc, SSH_OK); + + /* Signal that client callbacks are initialized */ + pthread_mutex_lock(&client_mutex); + client_callbacks_initialised = true; + pthread_cond_signal(&client_cond); + pthread_mutex_unlock(&client_mutex); + + event_rc = SSH_OK; + while (channel_data.req_seen != 1 && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(session); + + rc = pthread_join(server_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify forwarded-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_forwarded_tcpip_callback_success(void **state) +{ + torture_forwarded_tcpip_callback(state, true); +} + +static void torture_forwarded_tcpip_callback_failure(void **state) +{ + torture_forwarded_tcpip_callback(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/tests/unittests/torture_hashes.c b/tests/unittests/torture_hashes.c index a3041409..a243471a 100644 --- a/tests/unittests/torture_hashes.c +++ b/tests/unittests/torture_hashes.c @@ -57,25 +57,22 @@ static void torture_md5_hash(void **state) size_t hlen; int rc = 0; +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ if (ssh_fips_mode()) { skip(); } +#endif rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, (unsigned char **)&hash, &hlen); - if (ssh_fips_mode()) { - /* When in FIPS mode, expect the call to fail */ - assert_int_equal(rc, SSH_ERROR); - } else { - assert_int_equal(rc, SSH_OK); - - hexa = ssh_get_hexa((unsigned char *)hash, hlen); - SSH_STRING_FREE_CHAR(hash); - assert_string_equal(hexa, - "50:15:a0:9b:92:bf:33:1c:01:c5:8c:fe:18:fa:ce:78"); + assert_int_equal(rc, SSH_OK); - SSH_STRING_FREE_CHAR(hexa); - } + hexa = ssh_get_hexa((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "50:15:a0:9b:92:bf:33:1c:01:c5:8c:fe:18:fa:ce:78"); + SSH_STRING_FREE_CHAR(hexa); } static void torture_sha1_hash(void **state) diff --git a/tests/unittests/torture_knownhosts_parsing.c b/tests/unittests/torture_knownhosts_parsing.c index bc79fd0f..e815ae1d 100644 --- a/tests/unittests/torture_knownhosts_parsing.c +++ b/tests/unittests/torture_knownhosts_parsing.c @@ -696,6 +696,82 @@ static void torture_knownhosts_algorithms_global(void **state) ssh_free(session); } +static int setup_bad_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DEFAULT_ED25519, + sizeof(char), + strlen(LOCALHOST_DEFAULT_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_DEFAULT_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +#define LOCALHOST_BAD_LINE "localhost \n" + nwritten = fwrite(LOCALHOST_BAD_LINE, + sizeof(char), + strlen(LOCALHOST_BAD_LINE), + fp); + if (nwritten != strlen(LOCALHOST_BAD_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static void torture_knownhosts_has_entry(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + bool process_config = false; + struct ssh_knownhosts_entry *entry = NULL; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* Error is expected -- this tests the memory is not leaked from this + * test case */ + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + + found = ssh_session_get_known_hosts_entry(session, &entry); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + assert_null(entry); + + ssh_free(session); +} #endif /* _WIN32 There is no /dev/null on Windows */ int torture_run_tests(void) { @@ -738,6 +814,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global, setup_knownhosts_file, teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_has_entry, + setup_bad_knownhosts_file, + teardown_knownhosts_file), #endif }; diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index b8f871a8..88bb7374 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -18,7 +18,14 @@ #include "torture.h" #include "error.c" +#ifdef _WIN32 +#include +#else +#include +#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"; @@ -45,7 +52,7 @@ static void torture_get_user_home_dir(void **state) { (void) state; - user = ssh_get_user_home_dir(); + user = ssh_get_user_home_dir(NULL); assert_non_null(user); #ifndef _WIN32 assert_string_equal(user, pwd->pw_dir); @@ -103,6 +110,120 @@ static void torture_ntohll(void **state) { assert_true(value == check); } +/** + * @brief Compare fields of two (struct tm) type structures. + * + * @param[in] a Pointer to the first structure to compare + * + * @param[in] b Pointer to the second structure to compare + * + * @returns -1 on error + * @returns 0 if the fields of the structures are the same + * @returns 1 if the fields of the structures are not the same + */ +static int tm_cmp(const struct tm *a, const struct tm *b) +{ + if (a == NULL || b == NULL) { + return -1; + } + + return !(a->tm_sec == b->tm_sec && + a->tm_min == b->tm_min && + a->tm_hour == b->tm_hour && + a->tm_mday == b->tm_mday && + a->tm_mon == b->tm_mon && + a->tm_year == b->tm_year && + a->tm_wday == b->tm_wday && + a->tm_yday == b->tm_yday && + a->tm_isdst == b->tm_isdst); +} + +/** + * @brief Validate that localtime_r() works properly. + * + * This test is mainly to check that the libssh implementation of + * localtime_r() on Windows works properly (Windows does not provide + * localtime_r()) + */ +static void torture_localtime_r(UNUSED_PARAM(void **state)) +{ + /* + * The tm_wday and tm_yday fields of tm1 and tm2 would be filled + * appropriately due to the mktime() call further in the test. + */ + + /* Linux release date: 17/09/1991 (random time: 02:01:00) */ + struct tm tm1 = {.tm_sec = 0, + .tm_min = 1, + .tm_hour = 2, + .tm_mday = 17, + .tm_mon = 9 - 1, + .tm_year = 1991 - 1900, + .tm_isdst = 0}; + + /* Windows release date: 20/11/1985 (random time 05:04:03) */ + struct tm tm2 = {.tm_sec = 3, + .tm_min = 4, + .tm_hour = 5, + .tm_mday = 20, + .tm_mon = 11 - 1, + .tm_year = 1985 - 1900, + .tm_isdst = 0}; + + time_t t1, t2; + struct tm *static_tm_ptr = NULL, *tm_ptr = NULL; + struct tm our_tm = {0}; + int cmp; + + /* + * Convert time represented as (struct tm) to time represented as + * a (time_t) + */ + t1 = mktime(&tm1); + assert_int_not_equal(t1, (time_t)-1); + + t2 = mktime(&tm2); + assert_int_not_equal(t2, (time_t)-1); + + /* Test that localtime_r() gives the correct broken down time */ + tm_ptr = localtime_r(&t1, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(&our_tm, &tm1); + assert_int_equal(cmp, 0); + + /* + * Test that localtime_r() does not modify the static structure used by + * localtime(). (This is an attempt to test that the localtime_r() + * implementation does not use localtime() internally) + * + * To test this, we first use localtime() on some time, then use + * localtime_r() on another time and then validate that the time + * corresponding to the pointer (to the static structure) returned by + * the first localtime() call does not change. + */ + static_tm_ptr = localtime(&t1); + assert_non_null(static_tm_ptr); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + tm_ptr = localtime_r(&t2, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + /* + * Ideally, it should be checked that the localtime_r() implementation + * is thread safe by testing it under multiple threads, but we are not + * checking that as of now. This is because we trust localtime_r() provided + * by POSIX systems to be thread safe and the libssh implementation of + * localtime_r() on Windows should be a simple wrapper around Windows's + * localtime_s() which should also be thread safe. + */ +} + #ifdef _WIN32 static void torture_path_expand_tilde_win(void **state) { @@ -123,27 +244,22 @@ static void torture_path_expand_tilde_win(void **state) { #else /* _WIN32 */ static void torture_path_expand_tilde_unix(void **state) { - char h[256]; - char *d; - char *user; - char *home; + char h[256] = {0}; + char *d = NULL; + char *user = NULL; + char *home = NULL; + struct passwd *pw = NULL; (void) state; - user = getenv("USER"); - if (user == NULL){ - user = getenv("LOGNAME"); - } - /* in certain CIs there no such variables */ - if (!user){ - struct passwd *pw = getpwuid(getuid()); - if (pw){ - user = pw->pw_name; - } - } + pw = getpwuid(getuid()); + assert_non_null(pw); - home = getenv("HOME"); + user = pw->pw_name; + assert_non_null(user); + home = pw->pw_dir; assert_non_null(home); + snprintf(h, 256 - 1, "%s/.ssh", home); d = ssh_path_expand_tilde("~/.ssh"); @@ -172,7 +288,8 @@ static void torture_path_expand_escape(void **state) { const char *s = "%d/%h/%p/by/%r"; char *e; - session->opts.sshdir = strdup("guru"); + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("guru"); session->opts.host = strdup("meditation"); session->opts.port = 0; session->opts.username = strdup("root"); @@ -194,9 +311,10 @@ static void torture_path_expand_known_hosts(void **state) { ssh_session session = *state; char *tmp; - session->opts.sshdir = strdup("/home/guru/.ssh"); + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); - tmp = ssh_path_expand_escape(session, "%d/known_hosts"); + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); assert_non_null(tmp); assert_string_equal(tmp, "/home/guru/.ssh/known_hosts"); free(tmp); @@ -206,9 +324,10 @@ static void torture_path_expand_percent(void **state) { ssh_session session = *state; char *tmp; - session->opts.sshdir = strdup("/home/guru/.ssh"); + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); - tmp = ssh_path_expand_escape(session, "%d/config%%1"); + tmp = ssh_path_expand_escape(session, "%d/.ssh/config%%1"); assert_non_null(tmp); assert_string_equal(tmp, "/home/guru/.ssh/config%1"); free(tmp); @@ -222,7 +341,7 @@ static void torture_timeout_elapsed(void **state){ assert_true(ssh_timeout_elapsed(&ts,25)); assert_false(ssh_timeout_elapsed(&ts,30000)); - assert_false(ssh_timeout_elapsed(&ts,75)); + assert_false(ssh_timeout_elapsed(&ts,300)); assert_true(ssh_timeout_elapsed(&ts,0)); assert_false(ssh_timeout_elapsed(&ts,-1)); } @@ -234,7 +353,7 @@ static void torture_timeout_update(void **state){ usleep(50000); assert_int_equal(ssh_timeout_update(&ts,25), 0); assert_in_range(ssh_timeout_update(&ts,30000),29000,29960); - assert_in_range(ssh_timeout_update(&ts,75),1,40); + assert_in_range(ssh_timeout_update(&ts,500),1,460); assert_int_equal(ssh_timeout_update(&ts,0),0); assert_int_equal(ssh_timeout_update(&ts,-1),-1); } @@ -332,6 +451,7 @@ static void torture_ssh_analyze_banner(void **state) { assert_server_banner_accepted("SSH-2.0-OpenSSH"); assert_int_equal(0, session->openssh); + /* OpenSSH banners: big enough to extract major and minor versions */ assert_client_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); @@ -371,6 +491,10 @@ static void torture_ssh_analyze_banner(void **state) { assert_server_banner_accepted("SSH-2.0-OpenSSH-keyscan"); assert_int_equal(0, session->openssh); + /* OpenSSH banners: Double digit in major version */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_10.0p1"); + assert_int_equal(SSH_VERSION_INT(10, 0, 0), session->openssh); + ssh_free(session); } @@ -979,6 +1103,185 @@ static void torture_ssh_writen(void **state) 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); + // IDN + rc = ssh_check_hostname_syntax("xn--bcher-kva.tld"); + 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); + // IDN non-encoded + rc = ssh_check_hostname_syntax("bücher.tld"); + 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\" +#include +#include #include -#include #include +#include +#include +#include #ifdef WITH_SERVER #include #define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config" @@ -57,6 +60,20 @@ 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); @@ -64,22 +81,65 @@ static void torture_options_set_host(void **state) { 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_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* IPv6 hostnames should work without square braces */ + SAFE_FREE(session->opts.username); + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IPv6 hostnames should work also with square braces */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "[fd4d:5449:7400:111:626d:3cff:fedf:4d39]"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IDN need to be in punycode format */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "xn--bcher-kva.tld"); + assert_null(session->opts.username); + + /* IDN in UTF8 won't work */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); } -static void torture_options_set_ciphers(void **state) { +static void torture_options_set_ciphers(void **state) +{ ssh_session session = *state; int rc; /* Test known ciphers */ - rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, "aes128-ctr,aes192-ctr,aes256-ctr"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], @@ -92,7 +152,7 @@ static void torture_options_set_ciphers(void **state) { /* Test one unknown cipher */ rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "aes128-ctr,unknown-crap@example.com,aes256-ctr"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], "aes128-ctr,aes256-ctr"); @@ -103,6 +163,53 @@ static void torture_options_set_ciphers(void **state) { assert_false(rc == 0); } +static void torture_options_get_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "aes256-gcm@openssh.com," + "aes256-ctr," + "aes256-cbc," + "aes128-gcm@openssh.com," + "aes128-ctr," + "aes128-cbc"); + } else { + assert_string_equal(value, + "chacha20-poly1305@openssh.com," + "aes256-gcm@openssh.com," + "aes128-gcm@openssh.com," + "aes256-ctr," + "aes192-ctr," + "aes128-ctr"); + } + ssh_string_free_char(value); + + /* Test explicit ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(value, "aes128-ctr,aes192-ctr,aes256-ctr"); + } + ssh_string_free_char(value); +} + static void torture_options_set_key_exchange(void **state) { ssh_session session = *state; @@ -111,12 +218,14 @@ static void torture_options_set_key_exchange(void **state) /* Test known kexes */ rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," "curve25519-sha256,curve25519-sha256@libssh.org," "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," "diffie-hellman-group18-sha512," "diffie-hellman-group14-sha256," "diffie-hellman-group14-sha1"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_KEX]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_KEX], @@ -125,6 +234,8 @@ static void torture_options_set_key_exchange(void **state) "diffie-hellman-group14-sha256"); } else { assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," "curve25519-sha256,curve25519-sha256@libssh.org," "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," "diffie-hellman-group18-sha512," @@ -138,7 +249,7 @@ static void torture_options_set_key_exchange(void **state) "diffie-hellman-group16-sha512," "unknown-crap@example.com," "diffie-hellman-group18-sha512"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_KEX]); assert_string_equal(session->opts.wanted_methods[SSH_KEX], "diffie-hellman-group16-sha512," @@ -151,7 +262,86 @@ static void torture_options_set_key_exchange(void **state) assert_false(rc == 0); } -static void torture_options_set_hostkey(void **state) { +static void torture_options_get_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256," + "ecdh-sha2-nistp384," + "ecdh-sha2-nistp521," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256," + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + } else { +#ifdef HAVE_MLKEM1024 + assert_string_equal(value, + "mlkem768x25519-sha256," + "mlkem768nistp256-sha256," + "mlkem1024nistp384-sha384," + "sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group18-sha512," + "diffie-hellman-group16-sha512," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256"); +#else + assert_string_equal(value, + "mlkem768x25519-sha256," + "mlkem768nistp256-sha256," + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group18-sha512," + "diffie-hellman-group16-sha512," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256"); +#endif + } + ssh_string_free_char(value); + + /* Test explicit kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(value, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_hostkey(void **state) +{ ssh_session session = *state; int rc; @@ -159,14 +349,14 @@ static void torture_options_set_hostkey(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } else { assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); } /* Test one unknown host key */ @@ -175,7 +365,7 @@ static void torture_options_set_hostkey(void **state) { "ecdsa-sha2-nistp521," "unknown-crap@example.com," "rsa-sha2-256"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], "ecdsa-sha2-nistp521," @@ -188,7 +378,63 @@ static void torture_options_set_hostkey(void **state) { assert_false(rc == 0); } -static void torture_options_set_pubkey_accepted_types(void **state) { +static void torture_options_get_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "rsa-sha2-512," + "rsa-sha2-256"); + } else { + assert_string_equal(value, + "ssh-ed25519-cert-v01@openssh.com," + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com," + "rsa-sha2-512,rsa-sha2-256"); + } + ssh_string_free_char(value); + + /* Test explicit host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_pubkey_accepted_types(void **state) +{ ssh_session session = *state; int rc; enum ssh_digest_e type; @@ -197,7 +443,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); if (ssh_fips_mode()) { assert_string_equal(session->opts.pubkey_accepted_types, @@ -212,7 +458,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,unknown-crap@example.com,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); assert_string_equal(session->opts.pubkey_accepted_types, "ssh-ed25519,ssh-rsa"); @@ -238,7 +484,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "rsa-sha2-256,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); if (ssh_fips_mode()) { assert_string_equal(session->opts.pubkey_accepted_types, @@ -256,37 +502,206 @@ static void torture_options_set_pubkey_accepted_types(void **state) { assert_int_equal(type, SSH_DIGEST_SHA256); } -static void torture_options_set_macs(void **state) { +static void torture_options_get_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_get(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + + +static void torture_options_set_macs(void **state) +{ ssh_session session = *state; int rc; /* Test known MACs */ rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1"); /* Test multiple known MACs */ rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, - "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); - assert_true(rc == 0); + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], - "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); /* Test unknown MACs */ - rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com,hmac-sha1-etm@openssh.com,unknown@example.com"); - assert_true(rc == 0); + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com,hmac-sha1-etm@openssh.com," + "unknown@example.com"); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); - assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1-etm@openssh.com"); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com"); /* Test all unknown MACs */ - rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); assert_false(rc == 0); } -static void torture_options_get_host(void **state) { +static void torture_options_get_macs(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha1," + "hmac-sha2-512"); + } else { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha2-512"); + } + ssh_string_free_char(value); + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, "hmac-sha1"); + ssh_string_free_char(value); +} + +static void torture_options_set_compression(void **state) +{ + ssh_session session = *state; + int rc; + const char *known_value; + const char *multiple; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + known_value = "none"; + multiple = "none,squeeze"; + } else { + known_value = "zlib"; + multiple = "zlib,squeeze"; + } +#else + known_value = "none"; + multiple = "none,squeeze"; +#endif + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, known_value); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test multiple known compression */ + if (!ssh_fips_mode()) { + rc = ssh_options_set(session, + SSH_OPTIONS_COMPRESSION_S_C, + "none,zlib@openssh.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); +#ifdef WITH_ZLIB + 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_S_C], "none"); +#endif + } + + /* Test unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, multiple); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test all unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "squeeze"); + assert_false(rc == 0); +} + +static void torture_options_get_compression(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *test_value = NULL; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + test_value = "none"; + } else { + test_value = "zlib@openssh.com"; + } +#else + test_value = "none"; +#endif + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); +#ifdef WITH_ZLIB + assert_string_equal(value, "none,zlib@openssh.com"); +#else + assert_string_equal(value, "none"); +#endif + ssh_string_free_char(value); + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, test_value); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, test_value); + ssh_string_free_char(value); +} + +static void torture_options_get_host(void **state) +{ ssh_session session = *state; int rc; char* host = NULL; @@ -298,10 +713,11 @@ static void torture_options_get_host(void **state) { assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); assert_string_equal(host, "localhost"); - free(host); + ssh_string_free_char(host); } -static void torture_options_set_port(void **state) { +static void torture_options_set_port(void **state) +{ ssh_session session = *state; int rc; unsigned int port = 42; @@ -322,32 +738,37 @@ static void torture_options_set_port(void **state) { assert_true(rc == -1); } -static void torture_options_get_port(void **state) { - ssh_session session = *state; - unsigned int given_port = 1234; - unsigned int port_container; - int rc; - rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); - assert_true(rc == 0); - rc = ssh_options_get_port(session, &port_container); - assert_true(rc == 0); - assert_int_equal(port_container, 1234); -} - -static void torture_options_get_user(void **state) { - ssh_session session = *state; - char* user = NULL; - int rc; - rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); - assert_int_equal(rc, SSH_OK); - rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); - assert_int_equal(rc, SSH_OK); - assert_non_null(user); - assert_string_equal(user, "magicaltrevor"); - free(user); -} - -static void torture_options_set_fd(void **state) { +static void torture_options_get_port(void **state) +{ + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); +} + +static void torture_options_get_user(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_int_equal(rc, SSH_OK); + assert_non_null(user); + assert_string_equal(user, "magicaltrevor"); + ssh_string_free_char(user); +} + +static void torture_options_set_fd(void **state) +{ ssh_session session = *state; socket_t fd = 42; int rc; @@ -361,7 +782,8 @@ static void torture_options_set_fd(void **state) { assert_true(session->opts.fd == SSH_INVALID_SOCKET); } -static void torture_options_set_user(void **state) { +static void torture_options_set_user(void **state) +{ ssh_session session = *state; int rc; #ifndef _WIN32 @@ -377,6 +799,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"); @@ -390,33 +815,31 @@ static void torture_options_set_user(void **state) { #endif } -/* TODO */ -#if 0 -static voidtorture_options_set_sshdir) +static void torture_options_set_identity(void **state) { -} -END_TEST -#endif - -static void torture_options_set_identity(void **state) { ssh_session session = *state; int rc; rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); assert_true(rc == 0); - assert_string_equal(session->opts.identity_non_exp->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_non_exp->root->data, "identity2"); - assert_string_equal(session->opts.identity_non_exp->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) { +static void torture_options_get_identity(void **state) +{ ssh_session session = *state; char *identity = NULL; int rc; + /* This adds an identity to the head of the list and returns */ rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); assert_true(rc == 0); rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); @@ -427,12 +850,55 @@ 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_non_exp->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); assert_string_equal(identity, "identity2"); - free(identity); + ssh_string_free_char(identity); + + /* Iterate over all of the identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity2"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + /* here are the default identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_rsa"); + ssh_string_free_char(identity); + +#ifdef WITH_FIDO2 + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519_sk"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa_sk"); + ssh_string_free_char(identity); +#endif /* WITH_FIDO2 */ + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_EOF); } static void torture_options_set_global_knownhosts(void **state) @@ -532,7 +998,8 @@ static void torture_options_proxycommand(void **state) { assert_null(session->opts.ProxyCommand); } -static void torture_options_control_master (void **state) { +static void torture_options_control_master (void **state) +{ ssh_session session = *state; int rc, val = SSH_CONTROL_MASTER_NO; @@ -577,13 +1044,16 @@ static void torture_options_control_master (void **state) { assert_int_equal(rc, SSH_ERROR); } -static void torture_options_control_path(void **state) { +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"); + 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"); @@ -600,7 +1070,8 @@ static void torture_options_control_path(void **state) { SSH_STRING_FREE_CHAR(str); } -static void torture_options_config_host(void **state) { +static void torture_options_config_host(void **state) +{ ssh_session session = *state; FILE *config = NULL; @@ -663,7 +1134,7 @@ static void torture_options_config_match(void **state) fclose(config); rv = ssh_options_parse_config(session, "test_config"); - assert_ssh_return_code_equal(session, rv, SSH_ERROR); + assert_ssh_return_code_equal(session, rv, SSH_OK); /* The Match all keyword needs to be the only one (start) */ torture_reset_config(session); @@ -751,7 +1222,7 @@ static void torture_options_config_match(void **state) localuser = ssh_get_local_username(); assert_non_null(localuser); fputs(localuser, config); - free(localuser); + ssh_string_free_char(localuser); fputs("\n" "\tPort 33\n" "Match all\n" @@ -778,7 +1249,7 @@ static void torture_options_config_match(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_int_equal(session->opts.port, 34); #else @@ -800,7 +1271,7 @@ static void torture_options_config_match(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_int_equal(session->opts.port, 34); #else @@ -853,7 +1324,7 @@ static void torture_options_config_match_multi(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); -#ifdef _WIN32 +#ifndef WITH_EXEC /* The match exec is not supported on windows at this moment */ assert_int_equal(session->opts.port, 34); #else @@ -900,12 +1371,15 @@ 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" "GlobalKnownHostsFile /etc/ssh/known_hosts2\n" "UserKnownHostsFile ~/.ssh/known_hosts2\n" - "KexAlgorithms curve25519-sha256,ecdh-sha2-nistp521\n" + "KexAlgorithms curve25519-sha256,sntrup761x25519-sha512@openssh.com,ecdh-sha2-nistp521\n" "Ciphers aes256-ctr\n" "MACs hmac-sha2-256\n" "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" @@ -923,6 +1397,7 @@ static void torture_options_copy(void **state) "GSSAPIDelegateCredentials yes\n" "PubkeyAuthentication yes\n" /* sets flags */ "GSSAPIAuthentication no\n" /* sets flags */ + "AddressFamily inet6\n" "", config); fclose(config); @@ -947,6 +1422,19 @@ static void torture_options_copy(void **state) 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); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + assert_string_equal(session->opts.username, new->opts.username); assert_string_equal(session->opts.host, new->opts.host); assert_string_equal(session->opts.bindaddr, new->opts.bindaddr); @@ -965,7 +1453,7 @@ 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); + assert_null(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); @@ -986,6 +1474,7 @@ static void torture_options_copy(void **state) assert_true(session->opts.config_processed == new->opts.config_processed); assert_memory_equal(session->opts.options_seen, new->opts.options_seen, sizeof(session->opts.options_seen)); + assert_int_equal(session->opts.address_family, new->opts.address_family); ssh_free(new); @@ -1033,6 +1522,8 @@ static void torture_options_getopt(void **state) /* Test with all the supported options */ rc = ssh_options_getopt(session, &argc, (char **)argv); #ifdef _MSC_VER + UNUSED_VAR(new_level); + /* Not supported in windows */ assert_ssh_return_code_equal(session, rc, -1); #else @@ -1172,6 +1663,108 @@ static void torture_options_getopt(void **state) #endif /* _NSC_VER */ } +static void torture_options_getopt_o_option(void **state) +{ +#ifndef _MSC_VER + ssh_session session = *state; + int rc; + enum ssh_config_opcode_e opcode = + ssh_config_get_opcode((char *)"compression"); + const char *argv[6] = {EXECUTABLE_NAME, "-o", "Compression nah", NULL}; + int argc = 3; + + // Test: -o with invalid value (e.g., "-o Compression nah") + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + session->opts.options_seen[opcode] = 0; + + // Test: -o with valid value (e.g., "-o Compression yes") + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(session->opts.options_seen[opcode], 1); + +#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], "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + + // Test: -o with missing value (e.g., "-o =") + argv[1] = "-o"; + argv[2] = "="; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + // Test: -o with only option name, no value (e.g., "-o Compression") + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "Compression"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with empty string (e.g., "-o ") + argv[1] = "-o"; + argv[2] = ""; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with unsupported option on the cli + argv[1] = "-o"; + argv[2] = "match *"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, one invalid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = "-o"; + argv[4] = "enablesshkeysign yes"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, all valid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression no"; + argv[3] = "-o"; + argv[4] = "rekeylimit 1G 1h"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + opcode = ssh_config_get_opcode((char *)"compression"); + assert_int_equal(session->opts.options_seen[opcode], 1); + + opcode = ssh_config_get_opcode((char *)"rekeylimit"); + assert_int_equal(session->opts.options_seen[opcode], 1); +#endif /* _MSC_VER */ +} + static void torture_options_plus_sign(void **state) { ssh_session session = *state; @@ -1389,7 +1982,8 @@ static void torture_options_caret_sign(void **state) free(awaited); } -static void torture_options_apply (void **state) { +static void torture_options_apply (void **state) +{ ssh_session session = *state; struct ssh_list *awaited_list = NULL; struct ssh_iterator *it1 = NULL, *it2 = NULL; @@ -1516,18 +2110,30 @@ static void torture_options_apply (void **state) { 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"); + id = ssh_path_expand_escape(session, "%d/.ssh/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"); + id = ssh_path_expand_escape(session, "%d/.ssh/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"); + id = ssh_path_expand_escape(session, "%d/.ssh/id_rsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519_sk"); rc = ssh_list_append(awaited_list, id); assert_int_equal(rc, SSH_OK); +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa_sk"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ + assert_int_equal(ssh_list_count(awaited_list), ssh_list_count(session->opts.identity)); @@ -1568,6 +2174,44 @@ static void torture_options_set_verbosity (void **state) assert_int_not_equal(new_level, 0); } +static void torture_options_set_rsa_min_size(void **state) +{ + ssh_session session = *state; + int min_allowed = RSA_MIN_KEY_SIZE, key_size, rc; + + /* Check that passing NULL leads to failure */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); +} + #ifdef WITH_SERVER const char template[] = "temp_dir_XXXXXX"; @@ -1665,7 +2309,8 @@ static int sshbind_teardown(void **state) return 0; } -static void torture_bind_options_import_key(void **state) +static void +torture_bind_options_import_key(void **state) { struct bind_st *test_state; ssh_bind bind; @@ -1687,6 +2332,24 @@ static void torture_bind_options_import_key(void **state) assert_int_equal(rc, -1); SSH_KEY_FREE(key); + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(key); + } else { + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + /* set rsa key */ base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); @@ -1707,6 +2370,55 @@ static void torture_bind_options_import_key(void **state) #endif } +static void +torture_bind_options_import_key_str(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc; + const char *base64_key = ""; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, -1); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#endif +} + static void torture_bind_options_hostkey(void **state) { struct bind_st *test_state; @@ -1731,9 +2443,14 @@ static void torture_bind_options_hostkey(void **state) rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, LIBSSH_ED25519_TESTKEY); - assert_int_equal(rc, 0); - assert_non_null(bind->ed25519key); - assert_string_equal(bind->ed25519key, LIBSSH_ED25519_TESTKEY); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(bind->ed25519key); + } else { + assert_int_equal(rc, 0); + assert_non_null(bind->ed25519key); + assert_string_equal(bind->ed25519key, LIBSSH_ED25519_TESTKEY); + } #ifdef HAVE_ECC /* Test ECDSA key */ @@ -1881,6 +2598,51 @@ static void torture_bind_options_rsakey(void **state) assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); } +static void torture_bind_options_set_rsa_min_size(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc, min_allowed = RSA_MIN_KEY_SIZE, key_size; + + 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; + + /* Check that passing NULL leads to failure */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); +} + #ifdef HAVE_ECC static void torture_bind_options_ecdsakey(void **state) { @@ -2077,13 +2839,16 @@ static void torture_bind_options_set_macs(void **state) /* Test unknown MACs */ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, - "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); /* Test all unknown MACs */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); assert_int_not_equal(rc, 0); /* Test known MACs */ @@ -2104,13 +2869,16 @@ static void torture_bind_options_set_macs(void **state) /* Test unknown MACs */ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, - "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); /* Test all unknown MACs */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "unknown-crap@example.com"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com"); assert_int_not_equal(rc, 0); } @@ -2137,7 +2905,8 @@ static void torture_bind_options_parse_config(void **state) assert_non_null(bind->config_dir); assert_string_equal(bind->config_dir, cwd); - rc = ssh_bind_options_parse_config(bind, "%d/"LIBSSH_CUSTOM_BIND_CONFIG_FILE); + rc = ssh_bind_options_parse_config(bind, + "%d/" LIBSSH_CUSTOM_BIND_CONFIG_FILE); assert_int_equal(rc, 0); assert_int_equal(bind->bindport, 42); @@ -2186,8 +2955,9 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) bind = test_state->bind; /* Test known Pubkey Types */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); if (ssh_fips_mode()) { @@ -2201,8 +2971,9 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) SAFE_FREE(bind->pubkey_accepted_key_types); /* Test with some unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); assert_string_equal(bind->pubkey_accepted_key_types, @@ -2211,26 +2982,27 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) SAFE_FREE(bind->pubkey_accepted_key_types); /* Test with only unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); assert_int_equal(rc, -1); assert_null(bind->pubkey_accepted_key_types); /* Test with something set and then try unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); - assert_string_equal(bind->pubkey_accepted_key_types, - "ecdsa-sha2-nistp384"); - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "unknown-type"); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); assert_int_equal(rc, -1); /* Check that nothing changed */ assert_non_null(bind->pubkey_accepted_key_types); - assert_string_equal(bind->pubkey_accepted_key_types, - "ecdsa-sha2-nistp384"); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); } static void torture_bind_options_set_hostkey_algorithms(void **state) @@ -2246,136 +3018,248 @@ static void torture_bind_options_set_hostkey_algorithms(void **state) bind = test_state->bind; /* Test known Pubkey Types */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } else { assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); } SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with some unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "ecdsa-sha2-nistp384,unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384,unknown-type"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with only unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); assert_int_equal(rc, -1); assert_null(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with something set and then try unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "unknown-type"); + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); assert_int_equal(rc, -1); /* Check that nothing changed */ assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } #endif /* WITH_SERVER */ - -int torture_run_tests(void) { +int +torture_run_tests(void) +{ int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_options_set_host, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_host, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_port, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_port, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_fd, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_user, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_user, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_identity, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_identity, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_proxycommand, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_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), - cmocka_unit_test_setup_teardown(torture_options_set_pubkey_accepted_types, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_macs, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_fd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_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_get_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_set_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_get_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_compression, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_host, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_config_match, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_config_match_multi, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_getopt, - setup, teardown), + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt_o_option, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_plus_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_minus_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_caret_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_verbosity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_rsa_min_size, + setup, + teardown), }; #ifdef WITH_SERVER struct CMUnitTest sshbind_tests[] = { cmocka_unit_test_setup_teardown(torture_bind_options_import_key, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_import_key_str, + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindaddr, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindport, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindport_str, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity_str, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_rsa_min_size, + sshbind_setup, + sshbind_teardown), #ifdef HAVE_ECC cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), #endif cmocka_unit_test_setup_teardown(torture_bind_options_banner, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_ciphers, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_key_exchange, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_macs, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_parse_config, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, - sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_options_set_pubkey_accepted_key_types, - sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_options_set_hostkey_algorithms, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_hostkey_algorithms, + sshbind_setup, + sshbind_teardown), }; #endif /* WITH_SERVER */ diff --git a/tests/unittests/torture_packet.c b/tests/unittests/torture_packet.c index faf78116..51fa2e90 100644 --- a/tests/unittests/torture_packet.c +++ b/tests/unittests/torture_packet.c @@ -12,10 +12,11 @@ #include "socket.c" -uint8_t test_data[]="AThis is test data. Use it to check the validity of packet functions" - "AThis is test data. Use it to check the validity of packet functions" - "AThis is test data. Use it to check the validity of packet functions" - "AThis is test data. Use it to check the validity of packet functions"; +uint8_t test_data[]="\x02" + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions."; uint8_t key[]="iekaeshoa7ooCie2shai8shahngee3ONsee3xoishooj0ojei6aeChieth1iraPh"; uint8_t iv[]="eixaxughoomah4ui7Aew3ohxuolaifuu"; uint8_t mac[]="thook2Jai0ahmahyae7ChuuruoPhee8Y"; @@ -57,7 +58,7 @@ torture_packet(const char *cipher, const char *mac_type, size_t encrypted_packet_len; ssh_packet_callback callbacks[]={copy_packet_data}; struct ssh_packet_callbacks_struct cb = { - .start='A', + .start=2, .n_callbacks=1, .callbacks=callbacks, .user=response @@ -129,7 +130,7 @@ torture_packet(const char *cipher, const char *mac_type, assert_int_equal(rc, encrypted_packet_len); ssh_packet_set_callbacks(session, &cb); - explicit_bzero(response, sizeof(response)); + ssh_burn(response, sizeof(response)); rc = ssh_packet_socket_callback(buffer, encrypted_packet_len, session); assert_int_not_equal(rc, SSH_ERROR); if(payload_len > 0){ @@ -333,6 +334,7 @@ static void torture_packet_aes256_gcm(void **state) } } +#ifdef WITH_ZLIB static void torture_packet_compress_zlib(void **state) { int i; @@ -350,6 +352,7 @@ static void torture_packet_compress_zlib_openssh(void **state) torture_packet("aes256-ctr", "hmac-sha1", "zlib@openssh.com", i); } } +#endif /* WITH_ZLIB */ int torture_run_tests(void) { int rc; @@ -371,8 +374,10 @@ int torture_run_tests(void) { cmocka_unit_test(torture_packet_chacha20), cmocka_unit_test(torture_packet_aes128_gcm), cmocka_unit_test(torture_packet_aes256_gcm), +#ifdef WITH_ZLIB cmocka_unit_test(torture_packet_compress_zlib), cmocka_unit_test(torture_packet_compress_zlib_openssh), +#endif /* WITH_ZLIB */ #ifdef WITH_INSECURE_NONE cmocka_unit_test(torture_packet_none_sha1), cmocka_unit_test(torture_packet_aes128_ctr_none), diff --git a/tests/unittests/torture_pki_ecdsa.c b/tests/unittests/torture_pki_ecdsa.c index d8a1432b..8149ca19 100644 --- a/tests/unittests/torture_pki_ecdsa.c +++ b/tests/unittests/torture_pki_ecdsa.c @@ -1,4 +1,5 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC @@ -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); @@ -333,21 +367,95 @@ static void torture_pki_ecdsa_publickey_from_privatekey(void **state) static void torture_pki_ecdsa_import_cert_file(void **state) { int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; ssh_key cert = NULL; - enum ssh_keytypes_e type; + enum ssh_keytypes_e type, exp_cert_type; struct pki_st *test_state = *((struct pki_st **)state); + exp_cert_type = test_state->type + 3; + + /* 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); + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + type = ssh_key_type(cert); - assert_true(type == test_state->type+3); + assert_int_equal(type, exp_cert_type); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == test_state->type); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + 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_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_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == exp_cert_type); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + 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_key(test_state->type, NULL, &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); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); } static void torture_pki_ecdsa_publickey_base64(void **state) @@ -369,7 +477,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 +486,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 +514,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,20 +524,20 @@ 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(len, torture_pubkey_len(pubkey_generated)); assert_memory_equal(pubkey_original, pubkey_generated, len); @@ -451,11 +559,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, @@ -463,27 +571,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); @@ -540,7 +648,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); @@ -548,9 +656,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -562,7 +670,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); @@ -570,9 +678,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -583,7 +691,7 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); @@ -591,9 +699,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp384"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -605,7 +713,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); @@ -613,9 +721,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp384"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -626,7 +734,7 @@ static void torture_pki_generate_key_ecdsa(void **state) 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); @@ -634,9 +742,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp521"); etype_char =ssh_pki_key_ecdsa_name(key); @@ -648,7 +756,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); @@ -656,9 +764,9 @@ 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_string_equal(type_char, "ecdsa-sha2-nistp521"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -685,11 +793,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 */ @@ -698,7 +806,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); @@ -823,8 +931,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; @@ -837,28 +946,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); @@ -869,16 +979,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, @@ -886,7 +997,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, @@ -894,15 +1005,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) @@ -914,10 +1058,10 @@ 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); + etype_char = ssh_pki_key_ecdsa_name(key); assert_string_equal(etype_char, expected_name); SSH_KEY_FREE(key); @@ -950,60 +1094,77 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, setup_ecdsa_key_521, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_from_openssh_privkey, - setup_openssh_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_from_openssh_privkey, - setup_openssh_ecdsa_key_384, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_384, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, setup_openssh_ecdsa_key_521, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, - setup_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, - setup_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, - setup_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, - setup_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, - setup_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, - setup_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_521, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, - setup_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, - setup_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, - setup_ecdsa_key_521, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_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), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_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), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, setup_ecdsa_key_256, teardown), @@ -1031,15 +1192,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, setup_ecdsa_key_521, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, - setup_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, - setup_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_generate_pubkey_from_privkey, - setup_ecdsa_key_521, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, setup_ecdsa_key_256, teardown), @@ -1059,7 +1223,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), @@ -1069,6 +1232,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 fe38c6c6..7e6cb8bf 100644 --- a/tests/unittests/torture_pki_ecdsa_uri.c +++ b/tests/unittests/torture_pki_ecdsa_uri.c @@ -27,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 { @@ -561,7 +561,15 @@ int torture_run_tests(void) { cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521), }; ssh_session session = ssh_new(); - int verbosity = SSH_LOG_FUNCTIONS; + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); ssh_init(); ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index 764df04e..6a8d04e8 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -128,6 +128,11 @@ static void torture_pki_ed25519_import_privkey_base64(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); assert_non_null(key_str); @@ -160,6 +165,11 @@ static void torture_pki_ed25519_import_privkey_base64_comment(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); assert_non_null(key_str); @@ -199,6 +209,11 @@ static void torture_pki_ed25519_import_privkey_base64_whitespace(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); assert_non_null(key_str); @@ -237,6 +252,11 @@ static void torture_pki_ed25519_import_export_privkey_base64(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + rc = ssh_pki_import_privkey_base64(torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, false), passphrase, @@ -247,10 +267,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 +290,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); @@ -289,6 +309,11 @@ static void torture_pki_ed25519_publickey_from_privatekey(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); rc = ssh_pki_import_privkey_base64(keystring, passphrase, @@ -312,22 +337,101 @@ static void torture_pki_ed25519_publickey_from_privatekey(void **state) static void torture_pki_ed25519_import_cert_file(void **state) { int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; ssh_key cert = NULL; enum ssh_keytypes_e type; (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_return_code(rc, errno); assert_non_null(cert); + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + type = ssh_key_type(cert); assert_true(type == SSH_KEYTYPE_ED25519_CERT01); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + SSH_KEY_FREE(cert); + SSH_KEY_FREE(pubkey); + skip(); + } + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_ED25519); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + 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_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_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_ED25519_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + 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_key(SSH_KEYTYPE_ED25519, NULL, &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); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); } static void torture_pki_ed25519_publickey_base64(void **state) @@ -340,6 +444,11 @@ static void torture_pki_ed25519_publickey_base64(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); assert_non_null(key_buf); @@ -359,11 +468,11 @@ static void torture_pki_ed25519_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); @@ -381,7 +490,12 @@ static void torture_pki_ed25519_generate_pubkey_from_privkey(void **state) int rc; int len; - (void) state; /* unused */ + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } /* remove the public key, generate it from the private key and write it. */ unlink(LIBSSH_ED25519_TESTKEY ".pub"); @@ -398,12 +512,12 @@ static void torture_pki_ed25519_generate_pubkey_from_privkey(void **state) assert_non_null(pubkey); rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ED25519_TESTKEY ".pub"); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = torture_read_one_line(LIBSSH_ED25519_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_ED25519)); assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), @@ -485,17 +599,17 @@ static void torture_pki_ed25519_cert_verify(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(cert); sign = pki_do_sign(privkey, HASH, 20, SSH_DIGEST_AUTO); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, cert, HASH, 20); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(privkey); SSH_KEY_FREE(cert); @@ -503,70 +617,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_return_code(rc, errno); 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_return_code(rc, errno); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &privkey); - assert_true(rc == 0); + NULL, + NULL, + NULL, + &privkey); + 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); 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_return_code(rc, errno); - 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_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); unlink(LIBSSH_ED25519_TESTKEY); SSH_KEY_FREE(origkey); @@ -578,16 +705,17 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_return_code(rc, errno); 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_return_code(rc, errno); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, @@ -595,23 +723,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; @@ -793,7 +941,7 @@ static void torture_pki_ed25519_verify(void **state){ *ptr = '\0'; } rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); @@ -802,7 +950,7 @@ static void torture_pki_ed25519_verify(void **state){ assert_non_null(sig); rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); /* Alter signature and expect verification error */ #ifdef HAVE_LIBCRYPTO @@ -880,6 +1028,11 @@ static void torture_pki_ed25519_import_privkey_base64_passphrase(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + /* same for ED25519 */ testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); rc = ssh_pki_import_privkey_base64(testkey, @@ -915,6 +1068,11 @@ static void torture_pki_ed25519_privkey_dup(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); rc = ssh_pki_import_privkey_base64(testkey, passphrase, @@ -945,6 +1103,11 @@ static void torture_pki_ed25519_pubkey_dup(void **state) (void) state; /* unused */ + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + pub_str = strdup(p + 1); assert_non_null(pub_str); @@ -978,33 +1141,40 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_file, setup_ed25519_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_from_openssh_privkey, - setup_ed25519_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64, - setup_ed25519_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64_comment, - setup_ed25519_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_privkey_base64_whitespace, - setup_ed25519_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_export_privkey_base64, - setup_ed25519_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_from_privatekey, - setup_ed25519_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_pubkey_from_openssh_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_comment, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_whitespace, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_export_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_publickey_from_privatekey, + setup_ed25519_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_cert_file, setup_ed25519_key, teardown), cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_base64, setup_ed25519_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ed25519_generate_pubkey_from_privkey, - setup_ed25519_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_generate_pubkey_from_privkey, + setup_ed25519_key, + teardown), cmocka_unit_test(torture_pki_ed25519_generate_key), cmocka_unit_test_setup_teardown(torture_pki_ed25519_cert_verify, setup_ed25519_key, @@ -1018,6 +1188,13 @@ int torture_run_tests(void) { #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_ed25519_uri.c b/tests/unittests/torture_pki_ed25519_uri.c new file mode 100644 index 00000000..09ccab4f --- /dev/null +++ b/tests/unittests/torture_pki_ed25519_uri.c @@ -0,0 +1,357 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * Sahana Prasad + * + * 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 "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_EDDSA_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_EDDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" +#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[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + char *pub_uri; + char *priv_uri; + char *priv_uri_invalid_object; + char *priv_uri_invalid_token; + char *pub_uri_invalid_object; + char *pub_uri_invalid_token; +}; + +static int setup_tokens(void **state) +{ + char keys_path[1024] = {0}; + char keys_path_pub[1024] = {0}; + char *cwd = NULL; + struct pki_st *test_state = *state; + char obj_tempname[] = "label_XXXXXX"; + char pub_uri[1024] = {0}; + char priv_uri[1024] = {0}; + char pub_uri_invalid_object[1024] = {0}; + char priv_uri_invalid_object[1024] = {0}; + char pub_uri_invalid_token[1024] = {0}; + char priv_uri_invalid_token[1024] = {0}; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + ssh_tmpname(obj_tempname); + + snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, obj_tempname, obj_tempname); + + snprintf(priv_uri, + sizeof(priv_uri), + PRIV_URI_FMT, + obj_tempname, + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_token, + sizeof(pub_uri_invalid_token), + PUB_URI_FMT, + "invalid", + obj_tempname); + + snprintf(priv_uri_invalid_token, + sizeof(priv_uri_invalid_token), + PRIV_URI_FMT, + "invalid", + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_object, + sizeof(pub_uri_invalid_object), + PUB_URI_FMT, + obj_tempname, + "invalid"); + + snprintf(priv_uri_invalid_object, + sizeof(priv_uri_invalid_object), + PRIV_URI_FMT, + obj_tempname, + "invalid", + "1234"); + + snprintf(keys_path, sizeof(keys_path), "%s/%s", cwd, LIBSSH_EDDSA_TESTKEY); + + snprintf(keys_path_pub, + sizeof(keys_path_pub), + "%s/%s.pub", + cwd, + LIBSSH_EDDSA_TESTKEY); + + test_state->pub_uri = strdup(pub_uri); + test_state->priv_uri = strdup(priv_uri); + test_state->pub_uri_invalid_token = strdup(pub_uri_invalid_token); + test_state->pub_uri_invalid_object = strdup(pub_uri_invalid_object); + test_state->priv_uri_invalid_token = strdup(priv_uri_invalid_token); + test_state->priv_uri_invalid_object = strdup(priv_uri_invalid_object); + + torture_write_file(keys_path, torture_get_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_ED25519)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir = NULL; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + free(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + rc = setup_tokens(state); + assert_int_equal(rc, 0); + + return 0; +} + +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); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->priv_uri); + SAFE_FREE(test_state->pub_uri); + SAFE_FREE(test_state->priv_uri_invalid_object); + SAFE_FREE(test_state->pub_uri_invalid_object); + SAFE_FREE(test_state->priv_uri_invalid_token); + SAFE_FREE(test_state->pub_uri_invalid_token); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_uri(void **state) +{ + ssh_key pubkey = NULL; + int rc; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sign_verify_uri(void **state) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = ssh_new(); + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_publickey_from_privatekey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_uri_invalid_configurations(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_object, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_token, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, + 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); + assert_int_not_equal(rc, 0); + assert_null(privkey); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ed25519_import_pubkey_uri), + cmocka_unit_test(torture_pki_ed25519_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_ed25519_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_ed25519_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Skip test FIPS mode altogether. */ + if (ssh_fips_mode()) { + return 0; + } + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + 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); + + ssh_free(session); + + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c index 96fcb4e7..44d445fc 100644 --- a/tests/unittests/torture_pki_rsa.c +++ b/tests/unittests/torture_pki_rsa.c @@ -1,5 +1,6 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC @@ -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); @@ -329,48 +373,87 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) ssh_key pubkey = NULL; ssh_key privkey = NULL; ssh_key cert = NULL; + enum ssh_keytypes_e type; - (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); + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), passphrase, NULL, NULL, &privkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(privkey); + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_RSA); + /* 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); + assert_true(privkey->cert_type == SSH_KEYTYPE_RSA_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); /* 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); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); @@ -385,14 +468,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 +500,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 +509,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 +540,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 +577,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 +589,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); @@ -536,7 +619,13 @@ static void torture_pki_rsa_duplicate_key(void **state) SSH_STRING_FREE_CHAR(b64_key_gen); } -static void torture_pki_rsa_generate_key(void **state) +/** + * @brief Test RSA key generation using the deprecated ssh_pki_generate API. + * + * This test is kept for backward compatibility testing of the legacy API. + * For testing the new context-based API, see torture_pki_generate_key_rsa(). + */ +static void torture_pki_generate_rsa_deprecated(void **state) { int rc; ssh_key key = NULL, pubkey = NULL; @@ -550,7 +639,7 @@ static void torture_pki_rsa_generate_key(void **state) 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); @@ -558,7 +647,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); @@ -567,7 +656,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); @@ -575,7 +664,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); @@ -583,7 +672,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); @@ -591,7 +680,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); @@ -613,11 +702,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 */ @@ -680,7 +769,7 @@ static void torture_pki_rsa_key_size(void **state) (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); @@ -696,7 +785,7 @@ static void torture_pki_rsa_key_size(void **state) /* the verification should fail now */ rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -707,6 +796,68 @@ static void torture_pki_rsa_key_size(void **state) ssh_free(session); } +/** + * @brief Test RSA key generation using the new context-based + * ssh_pki_generate_key API. + * + * This test validates the new ssh_pki_ctx-based key generation API with + * both positive and negative test cases. + * + * For testing the old/deprecated API, see torture_pki_generate_rsa(). + */ +static void torture_pki_generate_key_rsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_pki_ctx ctx = NULL; + int desired = 4096; + int invalid_size = 512; + + (void)state; + + /* Test with NULL context - should use default size */ + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), RSA_DEFAULT_KEY_SIZE); + SSH_KEY_FREE(key); + + /* Test with NULL key pointer - should fail */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Test with invalid RSA key size (too small) - should fail */ + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_RSA_KEY_SIZE, + &invalid_size); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), desired); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_PKI_CTX_FREE(ctx); +} + static int test_sign_verify_data(ssh_key key, enum ssh_digest_e hash_type, const unsigned char *input, @@ -807,8 +958,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; @@ -821,28 +973,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); @@ -853,16 +1006,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, @@ -870,7 +1024,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, @@ -878,15 +1032,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) @@ -907,7 +1093,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); @@ -917,7 +1103,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 @@ -928,7 +1114,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 } @@ -955,7 +1141,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); @@ -965,7 +1151,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 */ @@ -975,7 +1161,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); } @@ -985,32 +1171,41 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_from_openssh_privkey, - setup_openssh_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_key, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_str, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_comment, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_whitespace, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, - setup_openssh_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_pubkey_from_openssh_privkey, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_comment, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_whitespace, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), - cmocka_unit_test(torture_pki_rsa_import_openssh_privkey_base64_passphrase), + cmocka_unit_test( + torture_pki_rsa_import_openssh_privkey_base64_passphrase), cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, setup_rsa_key, teardown), @@ -1020,18 +1215,34 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_generate_pubkey_from_privkey, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_generate_pubkey_from_privkey, + setup_rsa_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, setup_rsa_key, teardown), - cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test(torture_pki_generate_rsa_deprecated), cmocka_unit_test(torture_pki_rsa_key_size), -#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test(torture_pki_generate_key_rsa), 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 a13e470c..645f5465 100644 --- a/tests/unittests/torture_pki_rsa_uri.c +++ b/tests/unittests/torture_pki_rsa_uri.c @@ -16,7 +16,7 @@ #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 { @@ -89,7 +89,7 @@ static int setup_tokens(void **state) static int setup_directory_structure(void **state) { struct pki_st *test_state = NULL; - char *temp_dir; + char *temp_dir = NULL; int rc; test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); @@ -103,6 +103,7 @@ static int setup_directory_structure(void **state) rc = torture_change_dir(temp_dir); assert_int_equal(rc, 0); + free(temp_dir); test_state->temp_dir = torture_get_current_working_dir(); assert_non_null(test_state->temp_dir); @@ -282,9 +283,18 @@ torture_run_tests(void) }; ssh_session session = ssh_new(); - int verbosity = SSH_LOG_FUNCTIONS; + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_init(); torture_filter_tests(tests); diff --git a/tests/unittests/torture_pki_sk.c b/tests/unittests/torture_pki_sk.c new file mode 100644 index 00000000..10fadc24 --- /dev/null +++ b/tests/unittests/torture_pki_sk.c @@ -0,0 +1,536 @@ +/* + * torture_pki_sk.c - Torture tests for PKI security key functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * 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 "pki.c" +#include "sk_common.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_sk.h" + +#include + +/** + * These tests can also be configured to run with the sk-usbhid callbacks + * instead of the default sk-dummy callbacks which can run in a CI + * environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID and + * TORTURE_SK_PIN must be set. + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +/* Test constants */ + +/* Default PIN value which will be overridden with the PIN set in the + * environment variable. */ +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; +static const unsigned char test_message[] = "Test signing data for SK keys"; + +static const char test_challenge[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global keys for testing */ +static ssh_key g_ecdsa_key = NULL; +static ssh_key g_ed25519_key = NULL; + +static const struct ssh_sk_callbacks_struct *g_sk_callbacks = NULL; +bool valid_sk_callbacks = false; + +static int test_pin_callback(UNUSED_PARAM(const char *prompt), + char *buf, + size_t len, + UNUSED_PARAM(int echo), + UNUSED_PARAM(int verify), + UNUSED_PARAM(void *userdata)) +{ + size_t pin_len; + + if (test_pin == NULL) { + return SSH_ERROR; + } + + pin_len = strlen(test_pin); + if (pin_len + 1 > len) { + return -1; /* buffer too small */ + } + + memcpy(buf, test_pin, pin_len); + buf[pin_len] = '\0'; + return SSH_OK; +} + +static void torture_pki_sk_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_key pubkey = NULL, reimported_privkey = NULL, reimported_pubkey = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *privkey_filename = NULL; + const char *pubkey_filename = NULL; + const char *test_user_id = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_filename = "test_sk_ecdsa_private.key"; + pubkey_filename = "test_sk_ecdsa_public.pub"; + test_user_id = "libssh_test_ecdsa_sk"; + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_filename = "test_sk_ed25519_private.key"; + pubkey_filename = "test_sk_ed25519_public.pub"; + test_user_id = "libssh_test_ed25519_sk"; + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, ptr_to_g_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(*ptr_to_g_key, key_type, true); + + /* Export private key to file */ + rc = ssh_pki_export_privkey_file(*ptr_to_g_key, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + privkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Extract public key from private key */ + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Export public key to file */ + rc = ssh_pki_export_pubkey_file(pubkey, pubkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Verify exported files by importing them back */ + rc = ssh_pki_import_privkey_file(privkey_filename, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &reimported_privkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_privkey); + + rc = ssh_pki_import_pubkey_file(pubkey_filename, &reimported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_pubkey); + + /* Verify keys match */ + rc = ssh_key_cmp(*ptr_to_g_key, reimported_privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(pubkey, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(*ptr_to_g_key, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(reimported_privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + /* Cleanup */ + unlink(privkey_filename); + unlink(pubkey_filename); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(reimported_privkey); + SSH_KEY_FREE(reimported_pubkey); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void +torture_pki_sk_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + ssh_key resident_key = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *test_user_id = NULL; + ssh_auth_callback pin_callback = NULL; + int rc, flags; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + test_user_id = "libssh_test_ecdsa_sk"; + break; + + case SSH_KEYTYPE_SK_ED25519: + test_user_id = "libssh_test_ed25519_sk"; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + flags, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, &resident_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(resident_key, key_type, true); + + SSH_KEY_FREE(resident_key); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void +torture_pki_sk_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_sign_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_signature signature = NULL; + ssh_key public_key = NULL; + ssh_pki_ctx sign_ctx = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Select the appropriate global key based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(*ptr_to_g_key); + + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &public_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(public_key); + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + sign_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + NULL, + g_sk_callbacks); + assert_non_null(sign_ctx); + + signature = pki_sk_do_sign(sign_ctx, + *ptr_to_g_key, + test_message, + sizeof(test_message) - 1); + assert_non_null(signature); + assert_sk_signature_valid(signature, + key_type, + public_key, + test_message, + sizeof(test_message) - 1); + + SSH_SIGNATURE_FREE(signature); + SSH_KEY_FREE(public_key); + SSH_PKI_CTX_FREE(sign_ctx); +} + +static void torture_pki_sk_sign_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_sign_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_load_resident_keys(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx load_ctx = NULL; + ssh_key *resident_keys = NULL; + size_t num_keys = 0; + size_t i; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks || torture_sk_is_using_sk_dummy()) { + skip(); + } + + load_ctx = ssh_pki_ctx_new(); + assert_non_null(load_ctx); + + assert_non_null(test_pin); + rc = ssh_pki_ctx_set_sk_pin_callback(load_ctx, test_pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (g_sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(load_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + g_sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_sk_resident_keys_load(load_ctx, &resident_keys, &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (i = 0; i < num_keys; i++) { + ssh_key key = resident_keys[i]; + assert_non_null(key); + + assert_true(key->type == SSH_KEYTYPE_SK_ECDSA || + key->type == SSH_KEYTYPE_SK_ED25519); + + assert_true(key->sk_flags & SSH_SK_RESIDENT_KEY); + + assert_true(key->sk_flags & SSH_SK_USER_PRESENCE_REQD); + + if (key->type == SSH_KEYTYPE_SK_ECDSA) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + } else if (key->type == SSH_KEYTYPE_SK_ED25519) { + if (!ssh_fips_mode()) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + } + } + } + + for (i = 0; i < num_keys; i++) { + SSH_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + + SSH_PKI_CTX_FREE(load_ctx); +} + +static void +torture_pki_ctx_sk_callbacks_options_clear(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx ctx = NULL; + int rc; + + /* Test with NULL context - should return SSH_ERROR */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Create a new PKI context */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + /* Test clearing options on a context with no options set - should succeed + */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Add some options to the context */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw0", + false); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + "test_user", + true); + assert_int_equal(rc, SSH_OK); + + /* Clear all options - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Verify that we can add options again after clearing */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw1", + false); + assert_int_equal(rc, SSH_OK); + + /* Clear options again */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Test multiple clears on same context - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + SSH_PKI_CTX_FREE(ctx); +} + +/* Setup function to run before all tests */ +static int setup_global_state(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + const char *test_pin_env = NULL; + + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + g_sk_callbacks = sk_callbacks; + valid_sk_callbacks = true; + } + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +/* Teardown function to run after all tests */ +static int teardown_global_state(UNUSED_PARAM(void **state)) +{ + + /* Clean up global keys */ + SSH_KEY_FREE(g_ecdsa_key); + SSH_KEY_FREE(g_ed25519_key); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_key), + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_resident_key), + cmocka_unit_test(torture_pki_sk_sign_ecdsa_key), + cmocka_unit_test(torture_pki_sk_sign_ed25519_key), + cmocka_unit_test(torture_pki_sk_load_resident_keys), + cmocka_unit_test(torture_pki_ctx_sk_callbacks_options_clear), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_global_state, + teardown_global_state); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_pki_sk_ecdsa.c b/tests/unittests/torture_pki_sk_ecdsa.c new file mode 100644 index 00000000..83e4e4c3 --- /dev/null +++ b/tests/unittests/torture_pki_sk_ecdsa.c @@ -0,0 +1,487 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * 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 "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ECDSA_TESTKEY "libssh_testkey.id_ecdsa_sk" +#define LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ecdsa_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ecdsa_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ECDSA); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = + ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ecdsa_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ecdsa_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ecdsa_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ECDSA, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ecdsa_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_import_pubkey_file, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_comment, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_whitespace, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_export_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_publickey_from_privatekey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_passphrase, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_duplicate_key, + setup_sk_ecdsa_key, + teardown), + + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_pubkey_blob, + setup_sk_ecdsa_key, + teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_pki_sk_ed25519.c b/tests/unittests/torture_pki_sk_ed25519.c new file mode 100644 index 00000000..8641b5f5 --- /dev/null +++ b/tests/unittests/torture_pki_sk_ed25519.c @@ -0,0 +1,543 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * 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 "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ED25519_TESTKEY "libssh_testkey.id_ed25519_sk" +#define LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ed25519_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ED25519); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, + &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ed25519_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ed25519_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ed25519_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ed25519_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ED25519, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ed25519_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_file, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_comment, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_whitespace, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_export_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_publickey_from_privatekey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_passphrase, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_duplicate_key, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_pubkey_blob, + setup_sk_ed25519_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_pki_sshsig.c b/tests/unittests/torture_pki_sshsig.c new file mode 100644 index 00000000..85b07a22 --- /dev/null +++ b/tests/unittests/torture_pki_sshsig.c @@ -0,0 +1,831 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/pki.h" +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#ifdef WITH_FIDO2 +#include "libssh/libssh.h" +#include "torture_sk.h" +#endif + +#include +#include +#include + +/** + * The tests for the sk-type keys can also be configured to run with + * the sk-usbhid callbacks instead of the default sk-dummy callbacks which can + * run in a CI environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID must be + * set. + */ + +static const char template[] = "tmp_XXXXXX"; +static const char input[] = "Test input\0string with null byte"; +static const size_t input_len = sizeof(input) - 1; /* -1 to exclude final \0 */ +static const char *test_namespace = "file"; + +struct key_hash_combo { + enum ssh_keytypes_e key_type; + enum sshsig_digest_e hash_alg; + const char *key_name; +}; + +struct sshsig_st { + /* + * The original current working directory at the start of the test. + * + * During setup, the current working directory is changed to a newly + * created temporary directory (temp_dir). + * + * During cleanup, the current working directory is restored back + * to original_cwd. + */ + char *original_cwd; + char *temp_dir; + ssh_key rsa_key; + ssh_key ed25519_key; + ssh_key ecdsa_key; + +#ifdef WITH_FIDO2 + ssh_pki_ctx pki_ctx; + ssh_key sk_ecdsa_key; + ssh_key sk_ed25519_key; +#endif + + const char *ssh_keygen_path; + const struct key_hash_combo *test_combinations; + size_t num_combinations; +}; + +static struct key_hash_combo test_combinations[] = { + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_256, "rsa"}, + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_512, "rsa"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_256, "ed25519"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_512, "ed25519"}, +#ifdef HAVE_ECC + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_256, "ecdsa"}, + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_512, "ecdsa"}, +# ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_256, "sk_ecdsa"}, + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_512, "sk_ecdsa"}, +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_256, "sk_ed25519"}, + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_512, "sk_ed25519"}, +#endif +}; + +static ssh_key get_test_key(struct sshsig_st *test_state, + enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + return test_state->rsa_key; + case SSH_KEYTYPE_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->ed25519_key; + } +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + return test_state->ecdsa_key; +# ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ECDSA: + return test_state->sk_ecdsa_key; +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->sk_ed25519_key; + } +#endif + default: + return NULL; + } +} + +static int setup_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = NULL; + char *original_cwd = NULL; + char *temp_dir = NULL; + int rc = 0; + +#ifdef WITH_FIDO2 + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; +#endif + + test_state = calloc(1, sizeof(struct sshsig_st)); + assert_non_null(test_state); + + original_cwd = torture_get_current_working_dir(); + assert_non_null(original_cwd); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + test_state->original_cwd = original_cwd; + test_state->temp_dir = temp_dir; + test_state->test_combinations = test_combinations; + test_state->num_combinations = + sizeof(test_combinations) / sizeof(test_combinations[0]); + + *state = test_state; + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + /* Check if openssh is available and supports SSH signatures */ +#ifdef OPENSSH_SUPPORTS_SSHSIG + test_state->ssh_keygen_path = SSH_KEYGEN_EXECUTABLE; +#else + test_state->ssh_keygen_path = NULL; + printf("OpenSSH version does not support SSH signatures (requires " + "8.1+), skipping compatibility tests\n"); +#endif /* OPENSSH_SUPPORTS_SSHSIG */ + + /* Load pre-generated test keys using torture functions */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + NULL, + NULL, + NULL, + &test_state->rsa_key); + assert_int_equal(rc, SSH_OK); + + /* Skip ed25519 if in FIPS mode */ + if (!ssh_fips_mode()) { + /* mbedtls and libgcrypt don't fully support PKCS#8 PEM */ + /* thus parse the key with OpenSSH */ + rc = ssh_pki_import_privkey_base64( + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0), + NULL, + NULL, + NULL, + &test_state->ed25519_key); + assert_int_equal(rc, SSH_OK); + } + +#ifdef HAVE_ECC + rc = ssh_pki_import_privkey_base64( + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0), + NULL, + NULL, + NULL, + &test_state->ecdsa_key); + assert_int_equal(rc, SSH_OK); +#endif + +#ifdef WITH_FIDO2 + /* Create and configure PKI context for SK operations */ + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + test_state->pki_ctx = ssh_pki_ctx_new(); + assert_non_null(test_state->pki_ctx); + + rc = ssh_pki_ctx_options_set(test_state->pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + +# ifdef HAVE_ECC + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ECDSA, + test_state->pki_ctx, + &test_state->sk_ecdsa_key); + assert_int_equal(rc, SSH_OK); +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + test_state->pki_ctx, + &test_state->sk_ed25519_key); + assert_int_equal(rc, SSH_OK); + } + } + +#endif /* WITH_FIDO2 */ + + /* Write keys to files for openssh compatibility testing */ + if (test_state->ssh_keygen_path != NULL) { + torture_write_file("test_rsa", torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file("test_rsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + if (!ssh_fips_mode()) { + torture_write_file( + "test_ed25519", + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file("test_ed25519.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + } + +#ifdef HAVE_ECC + torture_write_file("test_ecdsa", + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + torture_write_file("test_ecdsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P256)); +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 +# ifdef HAVE_ECC + /* Write SK keys to files if they were successfully generated */ + if (test_state->sk_ecdsa_key != NULL) { + char *sk_ecdsa_priv = NULL; + char *sk_ecdsa_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ecdsa_key, + NULL, + NULL, + NULL, + &sk_ecdsa_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ecdsa_key, + &sk_ecdsa_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ecdsa", sk_ecdsa_priv); + torture_write_file("test_sk_ecdsa.pub", sk_ecdsa_pub); + + SAFE_FREE(sk_ecdsa_priv); + SAFE_FREE(sk_ecdsa_pub); + } +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + char *sk_ed25519_priv = NULL; + char *sk_ed25519_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ed25519_key, + NULL, + NULL, + NULL, + &sk_ed25519_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ed25519_key, + &sk_ed25519_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ed25519", sk_ed25519_priv); + torture_write_file("test_sk_ed25519.pub", sk_ed25519_pub); + + SAFE_FREE(sk_ed25519_priv); + SAFE_FREE(sk_ed25519_pub); + } +#endif /* WITH_FIDO2 */ + + rc = chmod("test_rsa", 0600); + assert_return_code(rc, errno); + if (!ssh_fips_mode()) { + rc = chmod("test_ed25519", 0600); + assert_return_code(rc, errno); + } +#ifdef HAVE_ECC + rc = chmod("test_ecdsa", 0600); + assert_return_code(rc, errno); +#endif + +#ifdef WITH_FIDO2 + /* Set permissions for SK key files */ +# ifdef HAVE_ECC + if (test_state->sk_ecdsa_key != NULL) { + rc = chmod("test_sk_ecdsa", 0600); + assert_return_code(rc, errno); + } +# endif /* HAVE_ECC */ + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + rc = chmod("test_sk_ed25519", 0600); + assert_return_code(rc, errno); + } +#endif /* WITH_FIDO2 */ + } + + return 0; +} + +static int teardown_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = *state; + int rc = 0; + + assert_non_null(test_state); + + ssh_key_free(test_state->rsa_key); + ssh_key_free(test_state->ed25519_key); + ssh_key_free(test_state->ecdsa_key); + +#ifdef WITH_FIDO2 + SSH_PKI_CTX_FREE(test_state->pki_ctx); + ssh_key_free(test_state->sk_ecdsa_key); + ssh_key_free(test_state->sk_ed25519_key); +#endif + + rc = torture_change_dir(test_state->original_cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->original_cwd); + SAFE_FREE(test_state); + + return 0; +} + +static int run_openssh_command(const char *cmd) +{ + char full_cmd[2048]; + int rc; + +#if defined(WITH_FIDO2) && defined(SK_DUMMY_LIBRARY_PATH) + /* Set SSH_SK_PROVIDER to sk-dummy library when using sk-dummy callbacks */ + if (torture_sk_is_using_sk_dummy()) { + snprintf(full_cmd, + sizeof(full_cmd), + "SSH_SK_PROVIDER=\"%s\" %s", + SK_DUMMY_LIBRARY_PATH, + cmd); + } else { + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); + } +#else + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); +#endif + + rc = system(full_cmd); + return WIFEXITED(rc) ? WEXITSTATUS(rc) : -1; +} + +static void torture_pki_sshsig_armor_dearmor(UNUSED_PARAM(void **state)) +{ + ssh_buffer test_buffer = NULL; + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + const char test_data[] = "test signature data"; + int rc; + + test_buffer = ssh_buffer_new(); + assert_non_null(test_buffer); + + rc = ssh_buffer_add_data(test_buffer, test_data, strlen(test_data)); + assert_int_equal(rc, SSH_OK); + + rc = sshsig_armor(test_buffer, &armored_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(armored_sig); + + /* Test with NULL armored_sig */ + rc = sshsig_armor(test_buffer, NULL); + assert_int_equal(rc, SSH_ERROR); + + assert_non_null(strstr(armored_sig, SSHSIG_BEGIN_SIGNATURE)); + assert_non_null(strstr(armored_sig, SSHSIG_END_SIGNATURE)); + + /* Test with NULL dearmored_buffer */ + rc = sshsig_dearmor(armored_sig, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = sshsig_dearmor(armored_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(dearmored_buffer); + + assert_int_equal(ssh_buffer_get_len(test_buffer), + ssh_buffer_get_len(dearmored_buffer)); + assert_memory_equal(ssh_buffer_get(test_buffer), + ssh_buffer_get(dearmored_buffer), + ssh_buffer_get_len(test_buffer)); + + ssh_buffer_free(test_buffer); + ssh_buffer_free(dearmored_buffer); + free(armored_sig); +} + +static void torture_pki_sshsig_armor_dearmor_invalid(UNUSED_PARAM(void **state)) +{ + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + int rc; + const char *invalid_sig = "-----BEGIN INVALID SIGNATURE-----\n" + "data\n" + "-----END INVALID SIGNATURE-----\n"; + + const char *incomplete_sig = "-----BEGIN SSH SIGNATURE----\n" + "U1NIU0lH\n"; + + /* Test with NULL buffer */ + rc = sshsig_armor(NULL, &armored_sig); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with invalid signature */ + rc = sshsig_dearmor(invalid_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with NULL input */ + rc = sshsig_dearmor(NULL, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with missing end marker */ + rc = sshsig_dearmor(incomplete_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); +} + +static void test_libssh_sign_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *signature = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = + sshsig_verify(input, input_len, signature, test_namespace, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + rc = ssh_key_cmp(test_key, verify_key, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_key_free(verify_key); + free(signature); +} + +static void +test_openssh_sign_libssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char cmd[1024]; + char *openssh_sig = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + FILE *fp = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y sign -f test_%s -n %s test_message.txt", + test_state->ssh_keygen_path, + combo->key_name, + test_namespace); + rc = run_openssh_command(cmd); + + assert_int_equal(rc, 0); + openssh_sig = torture_pki_read_file("test_message.txt.sig"); + assert_non_null(openssh_sig); + + rc = sshsig_verify(input, + input_len, + openssh_sig, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + ssh_key_free(verify_key); + free(openssh_sig); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void +test_libssh_sign_openssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *libssh_sig = NULL; + char cmd[1024]; + FILE *fp = NULL; + int rc; + char *pubkey_b64 = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + printf("Testing key type: %s\n", combo->key_name); + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &libssh_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(libssh_sig); + + fp = fopen("test_message.txt.sig", "w"); + assert_non_null(fp); + rc = fputs(libssh_sig, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = ssh_pki_export_pubkey_base64(test_key, &pubkey_b64); + assert_int_equal(rc, SSH_OK); + + fp = fopen("allowed_signers", "w"); + assert_non_null(fp); + rc = fprintf(fp, "test %s %s\n", test_key->type_c, pubkey_b64); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y verify -f allowed_signers -I test -n %s -s " + "test_message.txt.sig < test_message.txt", + test_state->ssh_keygen_path, + test_namespace); + rc = run_openssh_command(cmd); + assert_int_equal(rc, 0); + + free(libssh_sig); + free(pubkey_b64); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("allowed_signers"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void torture_sshsig_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_verify_combo(test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_openssh_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_openssh_sign_libssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_libssh_openssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_openssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_error_cases_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + char *signature = NULL; + ssh_key verify_key = NULL; + int rc; + size_t i; + char tampered_data[] = "Tampered\0data"; + + for (i = 0; i < test_state->num_combinations; i++) { + const struct key_hash_combo *combo = &test_state->test_combinations[i]; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + continue; + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not + * available */ + continue; + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + "", /* Test empty string namespace */ + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_ERROR); + assert_null(signature); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = sshsig_verify(input, + input_len, + signature, + "wrong_namespace", + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(input, + input_len, + signature, + "", /* Test empty string namespace */ + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(tampered_data, + sizeof(tampered_data) - 1, + signature, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + free(signature); + signature = NULL; + } + + /* Test invalid hash algorithm */ + rc = sshsig_sign(input, + input_len, + test_state->rsa_key, + NULL, /* pki_context */ + test_namespace, + 2, + &signature); + assert_int_equal(rc, SSH_ERROR); + + /* Test NULL parameters */ + rc = sshsig_sign(input, + input_len, + NULL, + NULL, /* pki_context */ + test_namespace, + SSHSIG_DIGEST_SHA2_256, + &signature); + assert_int_equal(rc, SSH_ERROR); + + rc = + sshsig_verify(input, input_len, "invalid", test_namespace, &verify_key); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sshsig_armor_dearmor), + cmocka_unit_test(torture_pki_sshsig_armor_dearmor_invalid), + /* Comprehensive combination tests */ + cmocka_unit_test_setup_teardown(torture_sshsig_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_openssh_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_libssh_openssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + + /* Comprehensive error case testing */ + cmocka_unit_test_setup_teardown( + torture_sshsig_error_cases_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_server_direct_tcpip.c b/tests/unittests/torture_server_direct_tcpip.c new file mode 100644 index 00000000..bad0896b --- /dev/null +++ b/tests/unittests/torture_server_direct_tcpip.c @@ -0,0 +1,265 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session = NULL; + ssh_channel channel = NULL; + bool should_accept = *(bool *)arg; + + session = + torture_ssh_session(NULL, TEST_SERVER_HOST, &test_port, "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + /* Open a direct-tcpip channel instead of a session channel */ + rc = ssh_channel_open_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + /* Close the channel and session */ + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_free(session); + + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_direct_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a direct-tcpip request and store the parameters */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + return channel; +} + +static void torture_ssh_channel_direct_tcpip(void **state, int should_accept) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_event event = NULL; + + struct channel_data channel_data; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_data, + .auth_password_function = auth_password_accept, + .channel_open_request_direct_tcpip_function = + channel_direct_tcpip_callback, + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + channel_data.should_accept = should_accept; + + /* Get client to connect */ + rc = pthread_create(&client_pthread, + NULL, + client_thread, + &channel_data.should_accept); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify direct-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_ssh_channel_direct_tcpip_success(void **state) +{ + torture_ssh_channel_direct_tcpip(state, true); +} + +static void torture_ssh_channel_direct_tcpip_failure(void **state) +{ + torture_ssh_channel_direct_tcpip(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/tests/unittests/torture_server_x11.c b/tests/unittests/torture_server_x11.c index 72314c5a..98423cf7 100644 --- a/tests/unittests/torture_server_x11.c +++ b/tests/unittests/torture_server_x11.c @@ -1,3 +1,5 @@ +#include "config.h" + #define LIBSSH_STATIC #include @@ -6,9 +8,11 @@ #include #include -#include #include "torture.h" +#include "torture_key.h" +#include +#define TEST_SERVER_HOST "127.0.0.1" #define TEST_SERVER_PORT 2222 struct hostkey_state { @@ -76,7 +80,7 @@ static void *client_thread(void *arg) { (void)arg; usleep(200); - session = torture_ssh_session(NULL, "localhost", + session = torture_ssh_session(NULL, TEST_SERVER_HOST, &test_port, "foo", "bar"); assert_non_null(session); @@ -175,7 +179,7 @@ static void test_ssh_channel_request_x11(void **state) { ssh_callbacks_init(&server_cb); /* Create server */ - sshbind = torture_ssh_bind("localhost", + sshbind = torture_ssh_bind(TEST_SERVER_HOST, TEST_SERVER_PORT, h->key_type, h->hostkey_path); diff --git a/tests/unittests/torture_session_keys.c b/tests/unittests/torture_session_keys.c index 11993ee0..1ae38312 100644 --- a/tests/unittests/torture_session_keys.c +++ b/tests/unittests/torture_session_keys.c @@ -30,11 +30,11 @@ uint8_t eK[24] = uint8_t dK[24] = "\xf8\xdd\xc3\xea\x5a\x59\x98\xb9\x86\xaa\x77\x29\x67\x51\x46" "\x21\x73\xc2\x6a\x6b\xed\xf2\x49\x98"; -uint8_t eMAC[32] = +uint8_t encrypt_MAC[32] = "\x0f\xbd\x1f\xe9\x2a\xaa\x84\xdc\xb5\xfc\xfb\x68\x2c\xa5\xe0" "\xba\xf2\x6f\xe5\x80\xee\x8f\x5c\x5b\x30\x55\x25\xb3\x7b\x21" "\xdc\xe5"; -uint8_t dMAC[32] = +uint8_t decrypt_MAC[32] = "\xa3\x52\x6e\x72\xa8\x8b\xde\xc5\x68\x66\x89\xae\x0a\xd2\x83" "\x23\x21\x4b\x3f\x04\x2e\x7f\x86\x04\x0f\xa8\x04\x3c\x62\xad" "\x74\x91"; @@ -82,8 +82,8 @@ static void torture_session_keys(UNUSED_PARAM(void **state)) assert_memory_equal(test_crypto.decryptIV, dIV, 32); assert_memory_equal(test_crypto.encryptkey, eK, 24); assert_memory_equal(test_crypto.decryptkey, dK, 24); - assert_memory_equal(test_crypto.encryptMAC, eMAC, 32); - assert_memory_equal(test_crypto.decryptMAC, dMAC, 32); + assert_memory_equal(test_crypto.encryptMAC, encrypt_MAC, 32); + assert_memory_equal(test_crypto.decryptMAC, decrypt_MAC, 32); bignum_safe_free(test_crypto.shared_secret); SAFE_FREE(test_crypto.encryptIV); diff --git a/tests/unittests/torture_sk_usbhid.c b/tests/unittests/torture_sk_usbhid.c new file mode 100644 index 00000000..19ab720d --- /dev/null +++ b/tests/unittests/torture_sk_usbhid.c @@ -0,0 +1,383 @@ +/* + * torture_sk_usbhid.c - Torture tests for security key USB-HID + * callbacks. + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * 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 "libssh/sk_common.h" +#include "torture.h" +#include "torture_sk.h" + +/** + * These tests require at least one FIDO2 device to be connected + * and the environment variables TORTURE_SK_USBHID and TORTURE_SK_PIN to be set. + * + * If TORTURE_SK_USBHID is not set, these tests will be skipped. + * To enable these tests, set both environment variables before running: + * + * export TORTURE_SK_USBHID=1 + * export TORTURE_SK_PIN=your_device_pin + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; + +static const uint8_t dummy_data[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global variables to store key handles for signing tests */ +static uint8_t *ecdsa_key_handle = NULL; +static size_t ecdsa_key_handle_len = 0; +static uint8_t *ed25519_key_handle = NULL; +static size_t ed25519_key_handle_len = 0; + +/* Check if tests should run */ +static bool should_run_tests(void) +{ + char *env = getenv("TORTURE_SK_USBHID"); + return (env != NULL && env[0] != '\0'); +} + +static struct sk_option **create_user_id_option(const char *user_id) +{ + struct sk_option **array = NULL, *option = NULL; + + array = calloc(2, sizeof(struct sk_option *)); + assert_non_null(array); + + option = calloc(1, sizeof(struct sk_option)); + assert_non_null(option); + + option->name = strdup(SSH_SK_OPTION_NAME_USER_ID); + assert_non_null(option->name); + option->value = strdup(user_id); + assert_non_null(option->value); + option->required = 0; + + array[0] = option; + array[1] = NULL; + + return array; +} + +static void torture_sk_usbhid_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + uint8_t **key_handle_out = NULL; + size_t *key_handle_len_out = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + key_handle_out = &ecdsa_key_handle; + key_handle_len_out = &ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + key_handle_out = &ed25519_key_handle; + key_handle_len_out = &ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll non-resident key */ + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + + /* Store the non-resident key handle for signing tests */ + *key_handle_out = calloc(response->key_handle_len, 1); + assert_non_null(*key_handle_out); + memcpy(*key_handle_out, response->key_handle, response->key_handle_len); + *key_handle_len_out = response->key_handle_len; + + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void +torture_sk_usbhid_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll first resident key */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + + /* Try to enroll same resident key again - should fail with + * SSH_SK_ERR_CREDENTIAL_EXISTS */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_SK_ERR_CREDENTIAL_EXISTS); + SK_ENROLL_RESPONSE_FREE(response); + + /* The force operation flag should overwrite the existing resident key with + * new one */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void torture_sk_usbhid_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ED25519); +} + +static void +torture_sk_usbhid_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ECDSA); +} + +static void +torture_sk_usbhid_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_sign_generic(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_sign_response *response = NULL; + uint8_t *key_handle = NULL; + size_t key_handle_len = 0; + int rc, flags; + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + key_handle = ecdsa_key_handle; + key_handle_len = ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + key_handle = ed25519_key_handle; + key_handle_len = ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(key_handle); + assert_true(key_handle_len > 0); + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, sign)); + + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->sign(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + key_handle, + key_handle_len, + flags, + test_pin, + NULL, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_sign_response(response, key_type); + + SK_SIGN_RESPONSE_FREE(response); +} + +static void torture_sk_usbhid_sign_ecdsa(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_sign_ed25519(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_load_resident_keys(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_resident_key **resident_keys = NULL; + size_t num_keys = 0; + int rc; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, load_resident_keys)); + + rc = callbacks->load_resident_keys(test_pin, + NULL, + &resident_keys, + &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (size_t i = 0; i < num_keys; i++) { + assert_sk_resident_key(resident_keys[i]); + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + + free(resident_keys); +} + +static int setup(UNUSED_PARAM(void **state)) +{ + const char *test_pin_env = NULL; + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +static int cleanup(UNUSED_PARAM(void **state)) +{ + SAFE_FREE(ecdsa_key_handle); + SAFE_FREE(ed25519_key_handle); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + bool should_run; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_resident_key), + cmocka_unit_test(torture_sk_usbhid_sign_ecdsa), + cmocka_unit_test(torture_sk_usbhid_sign_ed25519), + cmocka_unit_test(torture_sk_usbhid_load_resident_keys), + }; + + /* + * Only run tests if TORTURE_SK_USBHID environment variable is set + * and we expect a FIDO2 device to be available. + */ + should_run = should_run_tests(); + if (!should_run) { + printf("Skipping sk_usbhid tests: TORTURE_SK_USBHID not set\n"); + return 0; /* Success, but no tests run */ + } + + ssh_init(); + rc = cmocka_run_group_tests(tests, setup, cleanup); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_string.c b/tests/unittests/torture_string.c new file mode 100644 index 00000000..404676cb --- /dev/null +++ b/tests/unittests/torture_string.c @@ -0,0 +1,424 @@ +/* + * torture_string.c - torture tests for ssh_string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#define LIBSSH_STATIC + +#include "libssh/string.h" +#include "string.c" +#include "torture.h" + +static void torture_ssh_string_new(void **state) +{ + struct ssh_string_struct *str = NULL; + + (void)state; + + /* Test normal allocation */ + str = ssh_string_new(100); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 100); + ssh_string_free(str); + + /* Test zero size */ + str = ssh_string_new(0); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test maximum size */ + str = ssh_string_new(STRING_SIZE_MAX - 1); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), STRING_SIZE_MAX - 1); + ssh_string_free(str); + + /* Test size too large - should fail */ + str = ssh_string_new(STRING_SIZE_MAX + 1); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Hello, World!"; + const char *empty_string = ""; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), strlen(test_string)); + assert_memory_equal(ssh_string_data(str), test_string, strlen(test_string)); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(empty_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test NULL input */ + str = ssh_string_from_char(NULL); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_data(void **state) +{ + ssh_string s; + const unsigned char raw[] = {0x00, 0x01, 0x00, 0x42, 0xFF}; + + (void)state; + + /* Basic: copy arbitrary binary data (with embedded NUL) */ + s = ssh_string_from_data(raw, sizeof(raw)); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), sizeof(raw)); + assert_memory_equal(ssh_string_data(s), raw, sizeof(raw)); + ssh_string_free(s); + + /* Empty: len == 0 with NULL data returns empty string */ + s = ssh_string_from_data(NULL, 0); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 0); + ssh_string_free(s); + + /* Invalid: len > 0 with NULL data fails and sets errno */ + errno = 0; + s = ssh_string_from_data(NULL, 42); + assert_null(s); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_fill(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_data = "Test data"; + int rc; + + (void)state; + + /* Test normal fill */ + str = ssh_string_new(20); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + assert_memory_equal(ssh_string_data(str), test_data, strlen(test_data)); + ssh_string_free(str); + + /* Test fill with exact size */ + str = ssh_string_new(strlen(test_data)); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + ssh_string_free(str); + + /* Test NULL data */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, NULL, 5); + assert_int_equal(rc, -1); + ssh_string_free(str); + + /* Test zero length */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, 0); + assert_int_equal(rc, -1); + ssh_string_free(str); +} + +static void torture_ssh_string_to_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Convert to char"; + char *result = NULL; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, test_string); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, ""); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test NULL string */ + result = ssh_string_to_char(NULL); + assert_null(result); +} + +static void torture_ssh_string_copy(void **state) +{ + struct ssh_string_struct *str = NULL, *copy = NULL; + const char *test_string = "Copy me!"; + + (void)state; + + /* Test normal copy */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), ssh_string_len(str)); + assert_memory_equal(ssh_string_data(copy), + ssh_string_data(str), + ssh_string_len(str)); + + /* Ensure they are different objects */ + assert_ptr_not_equal(str, copy); + assert_ptr_not_equal(ssh_string_data(str), ssh_string_data(copy)); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test copy of empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), 0); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test NULL string */ + copy = ssh_string_copy(NULL); + assert_null(copy); +} + +static void torture_ssh_string_burn(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Secret data"; + void *data = NULL; + size_t len; + int i; + + (void)state; + + /* Test burning a string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + data = ssh_string_data(str); + len = ssh_string_len(str); + + /* Verify data is there initially */ + assert_memory_equal(data, test_string, len); + + /* Burn the string */ + ssh_string_burn(str); + + /* Verify data is zeroed out */ + for (i = 0; i < (int)len; i++) { + assert_int_equal(((unsigned char *)data)[i], 0); + } + + ssh_string_free(str); + + /* Test burning NULL string (should not crash) */ + ssh_string_burn(NULL); + + /* Test burning zero-size string */ + str = ssh_string_new(0); + assert_non_null(str); + ssh_string_burn(str); + ssh_string_free(str); +} + +static void torture_ssh_string_cmp(void **state) +{ + struct ssh_string_struct *str1 = NULL, *str2 = NULL; + const char *test_string1 = "Hello, World!"; + const char *test_string2 = "Hello, libssh"; + const char *test_string3 = "Hello"; + const char *test_string4 = "Apple"; + + const char data1[] = "Hello\x00World!"; + const char data2[] = "Hello\x00libssh"; + const char data3[] = "Hello"; + + int rc; + (void)state; + + /* Test comparing two NULL strings - should be equal */ + assert_int_equal(ssh_string_cmp(NULL, NULL), 0); + + /* Test comparing NULL with non-NULL string - NULL should be less */ + str1 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_true(ssh_string_cmp(NULL, str1) < 0); + assert_true(ssh_string_cmp(str1, NULL) > 0); + ssh_string_free(str1); + + /* Test comparing empty strings */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char(""); + assert_non_null(str1); + assert_non_null(str2); + + /* Both empty strings should be equal */ + assert_int_equal(ssh_string_cmp(str1, str2), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing empty string with non-empty string */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char("test"); + assert_non_null(str1); + assert_non_null(str2); + + /* Empty string should be less than non-empty string */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings where one is a prefix of another */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string3); /* "Hello" - prefix */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello" is shorter and a prefix, so it should be < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing different strings with same length */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string2); /* "Hello, libssh" */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello, World!" vs "Hello, libssh" - 'W' < 'l' */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with different lengths and different characters */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string4); /* "Apple" */ + assert_non_null(str1); + assert_non_null(str2); + + /* 'A' < 'H' so "Apple" < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing identical strings - should be equal */ + str1 = ssh_string_from_char(test_string1); + str2 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_non_null(str2); + assert_int_equal(ssh_string_cmp(str1, str2), 0); + assert_int_equal(ssh_string_cmp(str2, str1), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with embedded null characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data3)); /* "Hello" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data3, sizeof(data3)); + assert_int_equal(rc, 0); + + /* "Hello\x00World!" > "Hello" because its length is greater */ + assert_true(ssh_string_cmp(str1, str2) > 0); /* data1 > data3 */ + assert_true(ssh_string_cmp(str2, str1) < 0); /* data3 < data1 */ + ssh_string_free(str1); + ssh_string_free(str2); + + /* Comparing binary strings with same length, but different characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data2)); /* "Hello\x00libssh" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data2, sizeof(data2)); + assert_int_equal(rc, 0); + + /* 'W' < 'l' so str1 < str2 */ + assert_true(ssh_string_cmp(str1, str2) < 0); /* data1 < data2 */ + assert_true(ssh_string_cmp(str2, str1) > 0); /* data2 > data1 */ + ssh_string_free(str1); + ssh_string_free(str2); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_string_new), + cmocka_unit_test(torture_ssh_string_from_char), + cmocka_unit_test(torture_ssh_string_from_data), + cmocka_unit_test(torture_ssh_string_fill), + cmocka_unit_test(torture_ssh_string_to_char), + cmocka_unit_test(torture_ssh_string_copy), + cmocka_unit_test(torture_ssh_string_burn), + cmocka_unit_test(torture_ssh_string_cmp), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/tests/unittests/torture_threads_buffer.c b/tests/unittests/torture_threads_buffer.c index de9728f2..dfd9c571 100644 --- a/tests/unittests/torture_threads_buffer.c +++ b/tests/unittests/torture_threads_buffer.c @@ -119,6 +119,8 @@ static void *thread_growing_buffer_shifting(void *threadid) buffer = ssh_buffer_new(); if (buffer == NULL) { pthread_exit((void *)-1); + /* dummy analyzers ... */ + return NULL; } ssh_buffer_set_secure(buffer); @@ -307,7 +309,7 @@ static void *thread_ssh_buffer_add_format(void *threadid) q, s, "rocks", - 7, + (size_t)7, "So much", "Fun!"); assert_int_equal(rc, SSH_OK); diff --git a/tests/unittests/torture_threads_pki_rsa.c b/tests/unittests/torture_threads_pki_rsa.c index 5ec8055f..79674843 100644 --- a/tests/unittests/torture_threads_pki_rsa.c +++ b/tests/unittests/torture_threads_pki_rsa.c @@ -58,14 +58,8 @@ static int run_on_threads(void *(*func)(void *)) } for (i = 0; i < NUM_THREADS; ++i) { - void *p = NULL; - uint64_t *result; - - rc = pthread_join(threads[i], &p); + rc = pthread_join(threads[i], NULL); assert_int_equal(rc, 0); - - result = (uint64_t *)p; - assert_null(result); } return rc; @@ -133,10 +127,9 @@ static int teardown(void **state) { return 0; } -static int disable_secmem(void **state) +static void +disable_secmem(void) { - (void) state; /*unused*/ - #if defined(HAVE_LIBGCRYPT) /* gcrypt currently is configured to use only 4kB of locked secmem * (see ssh_crypto_init() in src/libcrypt.c) @@ -145,23 +138,10 @@ static int disable_secmem(void **state) * To avoid the expected warning, disable the secure memory. * */ - gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); #endif - - return 0; -} - -static int enable_secmem(void **state) -{ - (void) state; /*unused*/ - -#if defined(HAVE_LIBGCRYPT) - /* Re-enable secmem */ - gcry_control(GCRYCTL_INIT_SECMEM, 4096); - gcry_control(GCRYCTL_RESUME_SECMEM_WARN); -#endif - return 0; } static void *thread_pki_rsa_import_pubkey_file(void *threadid) @@ -178,7 +158,7 @@ static void *thread_pki_rsa_import_pubkey_file(void *threadid) SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_pubkey_file(void **state) @@ -211,8 +191,7 @@ static void *thread_pki_rsa_import_privkey_base64_NULL_key(void *threadid) NULL, NULL); assert_true(rc == -1); - - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state){ @@ -239,7 +218,8 @@ static void *thread_pki_rsa_import_privkey_base64_NULL_str(void *threadid) assert_true(rc == -1); SSH_KEY_FREE(key); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state){ @@ -281,7 +261,7 @@ static void *thread_pki_rsa_import_privkey_base64(void *threadid) free(key_str); SSH_KEY_FREE(key); - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_privkey_base64(void **state) @@ -324,7 +304,8 @@ static void *thread_pki_rsa_publickey_from_privatekey(void *threadid) SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_publickey_from_privatekey(void **state) @@ -397,7 +378,8 @@ static void *thread_pki_rsa_copy_cert_to_privkey(void *threadid) SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_copy_cert_to_privkey(void **state) @@ -430,7 +412,8 @@ static void *thread_pki_rsa_import_cert_file(void *threadid) assert_true(rc == 1); SSH_KEY_FREE(cert); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_cert_file(void **state) @@ -481,7 +464,8 @@ static void *thread_pki_rsa_publickey_base64(void *threadid) free(b64_key); free(key_buf); SSH_KEY_FREE(key); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_publickey_base64(void **state) @@ -545,7 +529,8 @@ static void *thread_pki_rsa_duplicate_key(void *threadid) SSH_KEY_FREE(privkey_dup); SSH_STRING_FREE_CHAR(b64_key); SSH_STRING_FREE_CHAR(b64_key_gen); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_duplicate_key(void **state) @@ -628,7 +613,8 @@ static void *thread_pki_rsa_generate_key(void *threadid) SSH_KEY_FREE(pubkey); ssh_free(session); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_generate_key(void **state) @@ -686,7 +672,8 @@ static void *thread_pki_rsa_import_privkey_base64_passphrase(void *threadid) assert_true(rc == -1); SSH_KEY_FREE(key); #endif - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) @@ -737,14 +724,8 @@ static void torture_mixed(void **state) for (f = 0; f < NUM_TESTS; f++) { for (i = 0; i < NUM_THREADS; ++i) { - void *p = NULL; - uint64_t *result = NULL; - - rc = pthread_join(threads[f][i], &p); + rc = pthread_join(threads[f][i], NULL); assert_int_equal(rc, 0); - - result = (uint64_t *)p; - assert_null(result); } } } @@ -756,18 +737,21 @@ int torture_run_tests(void) cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_key, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_str, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, setup_rsa_key, @@ -781,12 +765,8 @@ int torture_run_tests(void) cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_generate_key, - disable_secmem, - enable_secmem), - cmocka_unit_test_setup_teardown(torture_mixed, - setup_rsa_key, - teardown), + cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test_setup_teardown(torture_mixed, setup_rsa_key, teardown), }; /* @@ -801,6 +781,7 @@ int torture_run_tests(void) * If the library is statically linked, ssh_init() is not called * automatically */ + disable_secmem(); ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/unittests/torture_unit_server.c b/tests/unittests/torture_unit_server.c index 3e7e69f7..2fd4be72 100644 --- a/tests/unittests/torture_unit_server.c +++ b/tests/unittests/torture_unit_server.c @@ -10,11 +10,13 @@ #include #include +#include #include "torture.h" #include "torture_key.h" #define TEST_SERVER_PORT 2222 +#if 0 struct test_state { const char *hostkey; char *hostkey_path; @@ -107,7 +109,7 @@ 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; + ssh_bind sshbind = NULL; ssh_session server; /* Create server */ @@ -145,14 +147,47 @@ static void test_ssh_accept_interrupt(void **state) 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) + teardown) */ }; rc = cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/unittests/torture_unit_sftp.c b/tests/unittests/torture_unit_sftp.c new file mode 100644 index 00000000..12940039 --- /dev/null +++ b/tests/unittests/torture_unit_sftp.c @@ -0,0 +1,86 @@ +#include "config.h" + +#include "sftp_common.c" +#include "torture.h" + +#define LIBSSH_STATIC + +static void test_sftp_parse_longname(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Valid example from SFTP draft, page 18: + * https://datatracker.ietf.org/doc/draft-spaghetti-sshm-filexfer/ + */ + lname = "-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_OWNER); + assert_string_equal(value, "mjos"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_GROUP); + assert_string_equal(value, "staff"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_SIZE); + assert_string_equal(value, "348911"); + free(value); + /* This function is broken further as the date contains space which breaks + * the parsing altogether */ + value = sftp_parse_longname(lname, SFTP_LONGNAME_DATE); + assert_string_equal(value, "Mar"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_TIME); + assert_string_equal(value, "25"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_string_equal(value, "14:29"); + free(value); +} + +static void test_sftp_parse_longname_invalid(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Invalid inputs should not crash + */ + lname = NULL; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_null(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = ""; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, ""); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = "-rwxr-xr-x 1"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sftp_parse_longname), + cmocka_unit_test(test_sftp_parse_longname_invalid), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/tests/valgrind.supp b/tests/valgrind.supp index 7289d90c..05b7b9b8 100644 --- a/tests/valgrind.supp +++ b/tests/valgrind.supp @@ -123,13 +123,6 @@ Memcheck:Cond fun:SHA1_* } - -{ - openssl_CRYPTO_leak - Memcheck:Leak - fun:*alloc - fun:CRYPTO_* -} { openssl_CRYPTO_leak Memcheck:Cond @@ -147,6 +140,40 @@ fun:FIPS_mode_set fun:OPENSSL_init_library } +{ + Threads + Failed PEM decoder do not play well openssl/openssl#29077 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:CRYPTO_malloc + fun:CRYPTO_zalloc + fun:ossl_rcu_read_lock + fun:module_find + fun:module_run + fun:CONF_modules_load + fun:CONF_modules_load_file_ex + fun:ossl_config_int + fun:ossl_config_int + fun:ossl_init_config + fun:ossl_init_config_ossl_ + fun:__pthread_once_slow.isra.0 + fun:pthread_once@@GLIBC_2.34 + fun:CRYPTO_THREAD_run_once + fun:OPENSSL_init_crypto + fun:ossl_provider_doall_activated + fun:ossl_algorithm_do_all + fun:ossl_method_construct.constprop.0 + fun:inner_evp_generic_fetch.constprop.0 + fun:evp_generic_do_all + fun:EVP_KEYMGMT_do_all_provided + fun:ossl_decoder_ctx_setup_for_pkey + fun:OSSL_DECODER_CTX_new_for_pkey + fun:pem_read_bio_key_decoder + fun:pem_read_bio_key + fun:PEM_read_bio_PrivateKey_ex + fun:pki_private_key_from_base64 + ... +} # Cmocka { This looks like leak from cmocka when the forked server is not properly terminated @@ -158,3 +185,239 @@ fun:torture_run_tests fun:main } + +## libgcrypt +{ + Reachable allocations from libgcrypt + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_check_version + fun:ssh_crypto_init + fun:_ssh_init + fun:libssh_constructor + ... +} +{ + randomize in libgcrypt keeps some memory around + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:ssh_get_random + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_sign + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_verify + ... +} +{ + EC key generation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_genkey + ... +} +# NSS +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:__nss_module_get_function + ... + fun:getaddrinfo + ... + fun:torture_* + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} +## libkrb5 +# krb5_mcc_generate_new allocates a hashtab on a static global variable +# It doesn't get freed. +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from libkrb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:k5_hashtab_create + ... + fun:krb5_mcc_generate_new* +} +{ + Error string from acquire creds in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:acquire_cred_context.isra.0 + fun:acquire_cred_from.isra.0 + fun:gss_add_cred_from + fun:gss_acquire_cred_from +} +{ + error string from gss init sec context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:krb5_gss_init_sec_context_ext + fun:krb5_gss_init_sec_context + fun:gss_init_sec_context +} + + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +{ + sk-dummy.so memory leak in sk_enroll + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:sk_enroll + fun:pki_sk_enroll_key + ... +} + + +{ + malloc inside expand_hostname + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:expand_hostname + fun:canonicalize_princ + fun:krb5_sname_to_principal + fun:krb5_gss_import_name + fun:gssint_import_internal_name + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:malloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect,definite + fun:malloc + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + calloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:calloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +}