From cc52708c2a577518a65fff82bc654d6595af8ea2 Mon Sep 17 00:00:00 2001 From: enkore Date: Tue, 22 Nov 2022 11:47:48 +0100 Subject: [PATCH] libssh 0.10.4 (#59) * fix gitignore paths * libssh 0.10.4 Co-authored-by: marx --- .gitignore | 16 +- ci/docker/manylinux/Dockerfile | 2 +- ci/docker/manylinux/Dockerfile.2014_x86_64 | 2 +- ci/docker/manylinux/Dockerfile.aarch64 | 2 +- ci/docker/manylinux/Dockerfile.aarch64_2_24 | 2 +- ci/docker/manylinux/Dockerfile.aarch64_2_28 | 2 +- .../Dockerfile.manylinux_2_24_x86_64 | 2 +- .../Dockerfile.manylinux_2_28_x86_64 | 2 +- ci/docker/manylinux/libssh-0.10.4.tar.xz | Bin 0 -> 554920 bytes ci/docker/manylinux/libssh-0.10.4.tar.xz.asc | 16 + ci/docker/manylinux/libssh-0.9.6.tar.xz | 3 - ci/docker/manylinux/libssh-0.9.6.tar.xz.asc | 16 - libssh/.editorconfig | 18 + libssh/.gitlab-ci.yml | 826 ++++---- libssh/{ChangeLog => CHANGELOG} | 124 +- libssh/CMakeLists.txt | 26 +- .../{README.CodingStyle => CONTRIBUTING.md} | 195 +- libssh/CPackConfig.cmake | 2 +- libssh/CompilerChecks.cmake | 8 +- libssh/ConfigureChecks.cmake | 93 +- libssh/DefineOptions.cmake | 9 +- libssh/INSTALL | 4 +- libssh/README | 2 +- libssh/README.md | 3 +- libssh/SubmittingPatches | 118 -- .../cmake/Modules/DefineCompilerFlags.cmake | 4 +- libssh/cmake/Modules/Findsofthsm.cmake | 36 + libssh/config.h.cmake | 34 +- libssh/doc/README.gitlab.freebsd.md | 101 + libssh/doc/authentication.dox | 3 + libssh/doc/forwarding.dox | 12 +- libssh/doc/mainpage.dox | 8 +- libssh/doc/pkcs11.dox | 67 + libssh/doc/shell.dox | 33 +- libssh/examples/CMakeLists.txt | 29 +- libssh/examples/exec.c | 2 +- libssh/examples/keygen2.c | 505 +++++ libssh/examples/libssh_scp.c | 17 +- libssh/examples/proxy.c | 8 +- libssh/examples/samplesftp.c | 12 +- libssh/examples/samplesshd-cb.c | 66 +- libssh/examples/samplesshd-kbdint.c | 8 +- libssh/examples/scp_download.c | 6 +- libssh/examples/senddata.c | 2 +- libssh/examples/ssh_X11_client.c | 866 +++++++++ libssh/examples/ssh_client.c | 50 +- .../{ssh_server_fork.c => ssh_server.c} | 167 +- libssh/examples/sshd_direct-tcpip.c | 11 +- libssh/examples/sshnetcat.c | 7 +- libssh/include/libssh/agent.h | 2 - libssh/include/libssh/bind.h | 2 + libssh/include/libssh/bind_config.h | 10 + libssh/include/libssh/buffer.h | 6 +- libssh/include/libssh/callbacks.h | 22 +- libssh/include/libssh/chacha.h | 1 - .../include/libssh/chacha20-poly1305-common.h | 54 + libssh/include/libssh/channels.h | 2 +- libssh/include/libssh/config.h | 4 +- libssh/include/libssh/crypto.h | 15 +- libssh/include/libssh/dh.h | 10 + libssh/include/libssh/keys.h | 8 + libssh/include/libssh/legacy.h | 18 +- libssh/include/libssh/libcrypto.h | 8 +- libssh/include/libssh/libssh.h | 65 +- libssh/include/libssh/libsshpp.hpp | 12 +- libssh/include/libssh/messages.h | 1 + libssh/include/libssh/misc.h | 4 +- libssh/include/libssh/options.h | 1 + libssh/include/libssh/packet.h | 4 +- libssh/include/libssh/pki.h | 38 +- libssh/include/libssh/pki_priv.h | 12 + libssh/include/libssh/poly1305.h | 4 +- libssh/include/libssh/priv.h | 28 +- libssh/include/libssh/server.h | 49 +- libssh/include/libssh/session.h | 11 +- libssh/include/libssh/sftp.h | 18 +- libssh/include/libssh/sftp_priv.h | 2 +- libssh/include/libssh/socket.h | 4 +- libssh/include/libssh/wrapper.h | 40 +- libssh/src/ABI/current | 2 +- ...ssh-4.8.2.symbols => libssh-4.9.0.symbols} | 6 + ...ssh-4.8.3.symbols => libssh-4.9.1.symbols} | 6 + ...ssh-4.8.4.symbols => libssh-4.9.2.symbols} | 6 + ...ssh-4.8.5.symbols => libssh-4.9.3.symbols} | 6 + libssh/src/ABI/libssh-4.9.4.symbols | 427 +++++ libssh/src/CMakeLists.txt | 63 +- libssh/src/agent.c | 68 +- libssh/src/auth.c | 194 +- libssh/src/bignum.c | 2 +- libssh/src/bind.c | 95 +- libssh/src/bind_config.c | 109 +- libssh/src/buffer.c | 37 +- libssh/src/chachapoly.c | 14 +- libssh/src/channels.c | 500 +++-- libssh/src/client.c | 277 +-- libssh/src/config.c | 373 +++- libssh/src/config_parser.c | 53 +- libssh/src/connect.c | 18 +- libssh/src/connector.c | 27 +- libssh/src/crypto_common.c | 36 + libssh/src/curve25519.c | 10 +- libssh/src/dh-gex.c | 31 +- libssh/src/dh.c | 33 +- libssh/src/dh_crypto.c | 326 +++- libssh/src/ecdh_crypto.c | 352 +++- libssh/src/ecdh_mbedcrypto.c | 54 +- libssh/src/error.c | 2 +- libssh/src/external/bcrypt_pbkdf.c | 30 +- libssh/src/getpass.c | 9 +- libssh/src/getrandom_crypto.c | 54 + .../test_pcap.c => src/getrandom_gcrypt.c} | 36 +- libssh/src/getrandom_mbedcrypto.c | 52 + libssh/src/gssapi.c | 34 +- libssh/src/gzip.c | 10 +- libssh/src/init.c | 27 +- libssh/src/kdf.c | 7 +- libssh/src/kex.c | 123 +- libssh/src/known_hosts.c | 45 +- libssh/src/knownhosts.c | 99 +- libssh/src/legacy.c | 67 +- libssh/src/libcrypto-compat.c | 114 +- libssh/src/libcrypto-compat.h | 12 +- libssh/src/libcrypto.c | 1219 ++++++++---- libssh/src/libgcrypt.c | 492 +++-- libssh/src/libmbedcrypto.c | 692 ++++--- libssh/src/libssh.map | 14 +- libssh/src/log.c | 48 +- libssh/src/match.c | 123 +- libssh/src/mbedcrypto-compat.h | 39 + libssh/src/md_crypto.c | 225 +++ libssh/src/md_gcrypt.c | 167 ++ libssh/src/md_mbedcrypto.c | 308 +++ libssh/src/messages.c | 19 +- libssh/src/misc.c | 467 +++-- libssh/src/options.c | 266 ++- libssh/src/packet.c | 85 +- libssh/src/packet_crypt.c | 181 +- libssh/src/pcap.c | 11 +- libssh/src/pki.c | 510 +++-- libssh/src/pki_container_openssh.c | 2 +- libssh/src/pki_crypto.c | 1377 ++++++++++++-- libssh/src/pki_ed25519.c | 2 +- libssh/src/pki_gcrypt.c | 98 +- libssh/src/pki_mbedcrypto.c | 759 ++++++-- libssh/src/poll.c | 259 +-- libssh/src/scp.c | 97 +- libssh/src/server.c | 41 +- libssh/src/session.c | 36 +- libssh/src/sftp.c | 104 +- libssh/src/socket.c | 97 +- libssh/src/string.c | 8 +- libssh/src/threads/libcrypto.c | 12 - libssh/src/wrapper.c | 19 +- libssh/tests/CMakeLists.txt | 90 +- libssh/tests/authentication.c | 74 - libssh/tests/benchmarks/CMakeLists.txt | 2 +- libssh/tests/client/CMakeLists.txt | 6 + libssh/tests/client/torture_auth.c | 330 +++- libssh/tests/client/torture_auth_pkcs11.c | 257 +++ libssh/tests/client/torture_forward.c | 4 +- libssh/tests/client/torture_proxycommand.c | 17 +- libssh/tests/client/torture_rekey.c | 28 +- libssh/tests/client/torture_scp.c | 9 + libssh/tests/client/torture_session.c | 298 +++ libssh/tests/client/torture_sftp_read.c | 2 +- libssh/tests/connection.c | 31 - libssh/tests/etc/pam_matrix_passdb.in | 1 + libssh/tests/etc/passwd.in | 1 + libssh/tests/etc/shadow.in | 1 + libssh/tests/external_override/CMakeLists.txt | 165 ++ .../external_override/chacha20_override.c | 80 + .../external_override/chacha20_override.h | 51 + .../external_override/curve25519_override.c | 58 + .../external_override/curve25519_override.h | 31 + .../external_override/ed25519_override.c | 71 + .../external_override/ed25519_override.h | 39 + .../external_override/poly1305_override.c | 54 + .../external_override/poly1305_override.h | 35 + .../external_override/torture_override.c | 320 ++++ libssh/tests/fuzz/CMakeLists.txt | 39 +- libssh/tests/fuzz/README.md | 133 ++ libssh/tests/fuzz/fuzzer.c | 39 + libssh/tests/fuzz/ssh_bind_config_fuzzer.c | 52 + libssh/tests/fuzz/ssh_client_config_fuzzer.c | 55 + libssh/tests/fuzz/ssh_client_fuzzer.c | 170 ++ .../0f9d75a6c1d365115772a502d42b6e48f453198a | Bin 0 -> 2055 bytes libssh/tests/fuzz/ssh_known_hosts_fuzzer.c | 82 + .../d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 | Bin 0 -> 189 bytes libssh/tests/fuzz/ssh_server_fuzzer.c | 223 +++ libssh/tests/fuzz/ssh_server_fuzzer.cpp | 101 - .../fd7bd24a85e712fb59159a512b69d34ca21c8383 | Bin 0 -> 2055 bytes libssh/tests/keys/certauth/id_rsa | 54 +- libssh/tests/keys/certauth/id_rsa-cert.pub | 2 +- libssh/tests/keys/certauth/id_rsa.pub | 2 +- libssh/tests/keys/id_rsa_protected | 28 + libssh/tests/keys/id_rsa_protected.pub | 1 + libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_256 | 5 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub | 4 + .../pkcs11/id_pkcs11_ecdsa_256_openssh.pub | 2 + libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_384 | 6 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub | 5 + .../pkcs11/id_pkcs11_ecdsa_384_openssh.pub | 2 + libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_521 | 7 + .../tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub | 6 + .../pkcs11/id_pkcs11_ecdsa_521_openssh.pub | 2 + libssh/tests/keys/pkcs11/id_pkcs11_ed25519 | 3 + .../tests/keys/pkcs11/id_pkcs11_ed25519.pub | 3 + libssh/tests/keys/pkcs11/id_pkcs11_rsa | 27 + libssh/tests/keys/pkcs11/id_pkcs11_rsa.pub | 9 + .../keys/pkcs11/id_pkcs11_rsa_openssh.pub | 2 + libssh/tests/pkcs11/setup-softhsm-tokens.sh | 83 + libssh/tests/pkd/CMakeLists.txt | 15 +- libssh/tests/pkd/pkd_daemon.c | 43 +- libssh/tests/pkd/pkd_util.c | 1 + libssh/tests/server/CMakeLists.txt | 1 + libssh/tests/server/test_server/default_cb.c | 35 + libssh/tests/server/test_server/default_cb.h | 2 + libssh/tests/server/test_server/main.c | 31 + libssh/tests/server/torture_server.c | 263 +-- .../tests/server/torture_server_algorithms.c | 364 ++++ libssh/tests/server/torture_server_config.c | 195 +- libssh/tests/sftp_stress/main.c | 174 -- libssh/tests/ssh_ping.c | 23 + libssh/tests/suppressions/lsan.supp | 1 + libssh/tests/test_exec.c | 62 - libssh/tests/test_ssh_bind_accept_fd.c | 147 -- libssh/tests/test_tunnel.c | 76 - libssh/tests/tests.h | 8 - libssh/tests/tests_config.h.cmake | 6 +- libssh/tests/torture.c | 364 +++- libssh/tests/torture.h | 21 +- libssh/tests/torture_key.c | 1530 ++++++++------- libssh/tests/torture_key.h | 2 + libssh/tests/unittests/CMakeLists.txt | 31 +- libssh/tests/unittests/torture_bind_config.c | 1298 +++++++++---- libssh/tests/unittests/torture_callbacks.c | 2 +- libssh/tests/unittests/torture_config.c | 1677 +++++++++++++---- libssh/tests/unittests/torture_crypto.c | 214 +++ .../unittests/torture_knownhosts_parsing.c | 81 +- libssh/tests/unittests/torture_misc.c | 116 +- libssh/tests/unittests/torture_options.c | 252 ++- libssh/tests/unittests/torture_packet.c | 60 +- libssh/tests/unittests/torture_pki.c | 15 +- libssh/tests/unittests/torture_pki_dsa.c | 2 +- libssh/tests/unittests/torture_pki_ecdsa.c | 2 +- .../tests/unittests/torture_pki_ecdsa_uri.c | 560 ++++++ libssh/tests/unittests/torture_pki_ed25519.c | 2 +- libssh/tests/unittests/torture_pki_rsa.c | 47 +- libssh/tests/unittests/torture_pki_rsa_uri.c | 302 +++ .../tests/unittests/torture_threads_pki_rsa.c | 2 +- libssh/tests/unittests/torture_unit_server.c | 160 ++ 251 files changed, 20738 insertions(+), 6276 deletions(-) create mode 100644 ci/docker/manylinux/libssh-0.10.4.tar.xz create mode 100644 ci/docker/manylinux/libssh-0.10.4.tar.xz.asc delete mode 100644 ci/docker/manylinux/libssh-0.9.6.tar.xz delete mode 100644 ci/docker/manylinux/libssh-0.9.6.tar.xz.asc create mode 100644 libssh/.editorconfig rename libssh/{ChangeLog => CHANGELOG} (88%) rename libssh/{README.CodingStyle => CONTRIBUTING.md} (58%) delete mode 100644 libssh/SubmittingPatches create mode 100644 libssh/cmake/Modules/Findsofthsm.cmake create mode 100644 libssh/doc/README.gitlab.freebsd.md create mode 100644 libssh/doc/pkcs11.dox create mode 100644 libssh/examples/keygen2.c create mode 100644 libssh/examples/ssh_X11_client.c rename libssh/examples/{ssh_server_fork.c => ssh_server.c} (81%) create mode 100644 libssh/include/libssh/chacha20-poly1305-common.h rename libssh/src/ABI/{libssh-4.8.2.symbols => libssh-4.9.0.symbols} (98%) rename libssh/src/ABI/{libssh-4.8.3.symbols => libssh-4.9.1.symbols} (98%) rename libssh/src/ABI/{libssh-4.8.4.symbols => libssh-4.9.2.symbols} (98%) rename libssh/src/ABI/{libssh-4.8.5.symbols => libssh-4.9.3.symbols} (98%) create mode 100644 libssh/src/ABI/libssh-4.9.4.symbols create mode 100644 libssh/src/crypto_common.c create mode 100644 libssh/src/getrandom_crypto.c rename libssh/{tests/test_pcap.c => src/getrandom_gcrypt.c} (53%) create mode 100644 libssh/src/getrandom_mbedcrypto.c create mode 100644 libssh/src/mbedcrypto-compat.h create mode 100644 libssh/src/md_crypto.c create mode 100644 libssh/src/md_gcrypt.c create mode 100644 libssh/src/md_mbedcrypto.c delete mode 100644 libssh/tests/authentication.c create mode 100644 libssh/tests/client/torture_auth_pkcs11.c delete mode 100644 libssh/tests/connection.c create mode 100644 libssh/tests/external_override/CMakeLists.txt create mode 100644 libssh/tests/external_override/chacha20_override.c create mode 100644 libssh/tests/external_override/chacha20_override.h create mode 100644 libssh/tests/external_override/curve25519_override.c create mode 100644 libssh/tests/external_override/curve25519_override.h create mode 100644 libssh/tests/external_override/ed25519_override.c create mode 100644 libssh/tests/external_override/ed25519_override.h create mode 100644 libssh/tests/external_override/poly1305_override.c create mode 100644 libssh/tests/external_override/poly1305_override.h create mode 100644 libssh/tests/external_override/torture_override.c create mode 100644 libssh/tests/fuzz/README.md create mode 100644 libssh/tests/fuzz/fuzzer.c create mode 100644 libssh/tests/fuzz/ssh_bind_config_fuzzer.c create mode 100644 libssh/tests/fuzz/ssh_client_config_fuzzer.c create mode 100644 libssh/tests/fuzz/ssh_client_fuzzer.c create mode 100644 libssh/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a create mode 100644 libssh/tests/fuzz/ssh_known_hosts_fuzzer.c create mode 100644 libssh/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 create mode 100644 libssh/tests/fuzz/ssh_server_fuzzer.c delete mode 100644 libssh/tests/fuzz/ssh_server_fuzzer.cpp create mode 100644 libssh/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 create mode 100644 libssh/tests/keys/id_rsa_protected create mode 100644 libssh/tests/keys/id_rsa_protected.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_256 create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_384 create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_521 create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ed25519 create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_ed25519.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_rsa create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_rsa.pub create mode 100644 libssh/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub create mode 100755 libssh/tests/pkcs11/setup-softhsm-tokens.sh create mode 100644 libssh/tests/server/torture_server_algorithms.c delete mode 100644 libssh/tests/sftp_stress/main.c create mode 100644 libssh/tests/suppressions/lsan.supp delete mode 100644 libssh/tests/test_exec.c delete mode 100644 libssh/tests/test_ssh_bind_accept_fd.c delete mode 100644 libssh/tests/test_tunnel.c delete mode 100644 libssh/tests/tests.h create mode 100644 libssh/tests/unittests/torture_pki_ecdsa_uri.c create mode 100644 libssh/tests/unittests/torture_pki_rsa_uri.c create mode 100644 libssh/tests/unittests/torture_unit_server.c diff --git a/.gitignore b/.gitignore index 805dd33..42fda46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ *.egg-info *.pyc -dist -build +/dist +/build *~ *.so* -src -local +/src +/local libssh/compile_commands.json -wheelhouse -.idea -tests/unit_test_cert_key-cert.pub -doc/_build +/wheelhouse +/.idea +/tests/unit_test_cert_key-cert.pub +/doc/_build diff --git a/ci/docker/manylinux/Dockerfile b/ci/docker/manylinux/Dockerfile index eadd91e..7d1d823 100644 --- a/ci/docker/manylinux/Dockerfile +++ b/ci/docker/manylinux/Dockerfile @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux2010_x86_64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.2014_x86_64 b/ci/docker/manylinux/Dockerfile.2014_x86_64 index e5c452b..3090212 100644 --- a/ci/docker/manylinux/Dockerfile.2014_x86_64 +++ b/ci/docker/manylinux/Dockerfile.2014_x86_64 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux2014_x86_64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.aarch64 b/ci/docker/manylinux/Dockerfile.aarch64 index f97a51b..fa00ac7 100644 --- a/ci/docker/manylinux/Dockerfile.aarch64 +++ b/ci/docker/manylinux/Dockerfile.aarch64 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux2014_aarch64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.aarch64_2_24 b/ci/docker/manylinux/Dockerfile.aarch64_2_24 index 930c825..98c4884 100644 --- a/ci/docker/manylinux/Dockerfile.aarch64_2_24 +++ b/ci/docker/manylinux/Dockerfile.aarch64_2_24 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux_2_24_aarch64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.aarch64_2_28 b/ci/docker/manylinux/Dockerfile.aarch64_2_28 index 69092ea..8497705 100644 --- a/ci/docker/manylinux/Dockerfile.aarch64_2_28 +++ b/ci/docker/manylinux/Dockerfile.aarch64_2_28 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux_2_28_aarch64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.manylinux_2_24_x86_64 b/ci/docker/manylinux/Dockerfile.manylinux_2_24_x86_64 index a1b8db4..1342fbe 100644 --- a/ci/docker/manylinux/Dockerfile.manylinux_2_24_x86_64 +++ b/ci/docker/manylinux/Dockerfile.manylinux_2_24_x86_64 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux_2_24_x86_64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/Dockerfile.manylinux_2_28_x86_64 b/ci/docker/manylinux/Dockerfile.manylinux_2_28_x86_64 index 0b95972..83aa268 100644 --- a/ci/docker/manylinux/Dockerfile.manylinux_2_28_x86_64 +++ b/ci/docker/manylinux/Dockerfile.manylinux_2_28_x86_64 @@ -1,7 +1,7 @@ FROM quay.io/pypa/manylinux_2_28_x86_64 ENV OPENSSL openssl-1.1.1q -ENV LIBSSH 0.9.6 +ENV LIBSSH 0.10.4 ENV KRB 1.18.4 ENV SYSTEM_LIBSSH 1 ENV CFLAGS "-g0 -s" diff --git a/ci/docker/manylinux/libssh-0.10.4.tar.xz b/ci/docker/manylinux/libssh-0.10.4.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..89b82d6d56df44de4c1eac8065d9d20121e32c83 GIT binary patch literal 554920 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

XFy9WgQhlzQKJ8Uro+)Z!Zxu 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

Fd7NYje(h95SSDd(=H{ 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 z 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%0DeY9C0>F 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 /etc/system-fips - - update-crypto-policies --set FIPS - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_BLOWFISH_CIPHER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. && - make -j$(nproc) && OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake .. && make docs -fedora/openssl_1.1.x/x86_64/minimal: +fedora/ninja: + extends: .fedora image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=OFF -DWITH_SERVER=OFF -DWITH_ZLIB=OFF -DWITH_PCAP=OFF - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DWITH_GEX=OFF .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake -G Ninja $CMAKE_OPTIONS ../ && ninja && ninja test + +fedora/openssl_3.0.x/x86_64: + extends: .fedora + +fedora/openssl_3.0.x/x86_64/fips: + extends: .fedora + before_script: + - echo "# userspace fips" > /etc/system-fips + # We do not need the kernel part, but in case we ever do: + # mkdir -p /var/tmp/userspace-fips + # echo 1 > /var/tmp/userspace-fips/fips_enabled + # mount --bind /var/tmp/userspace-fips/fips_enabled \ + # /proc/sys/crypto/fips_enabled + - update-crypto-policies --show + - update-crypto-policies --set FIPS + - update-crypto-policies --show + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_BLOWFISH_CIPHER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON + -DWITH_DSA=ON + -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. + script: + - cmake $CMAKE_OPTIONS .. && + make -j$(nproc) && + OPENSSL_FORCE_FIPS_MODE=1 ctest --output-on-failure + +fedora/openssl_3.0.x/x86_64/minimal: + extends: .fedora + variables: + script: + - cmake $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=OFF + -DWITH_SERVER=OFF + -DWITH_ZLIB=OFF + -DWITH_PCAP=OFF + -DWITH_DSA=OFF + -DUNIT_TESTING=ON + -DCLIENT_TESTING=ON + -DWITH_GEX=OFF .. && + make -j$(nproc) # Address sanitizer doesn't mix well with LD_PRELOAD used in the testsuite # so, this is only enabled for unit tests right now. # TODO: add -DCLIENT_TESTING=ON -DSERVER_TESTING=ON fedora/address-sanitizer: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + extends: .fedora + stage: analysis script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=AddressSanitizer - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake + -DCMAKE_BUILD_TYPE=AddressSanitizer + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + $CMAKE_BUILD_OPTIONS + -DUNIT_TESTING=ON + -DFUZZ_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure # This is disabled as it report OpenSSL issues -# It also has ethe same issues with cwrap as AddressSanitizer +# It also has the same issues with cwrap as AddressSanitizer .fedora/memory-sanitizer: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + extends: .fedora + stage: analysis script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=MemorySanitizer - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. - && make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake + -DCMAKE_BUILD_TYPE=MemorySanitizer + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + $CMAKE_BUILD_OPTIONS + -DUNIT_TESTING=ON + -DFUZZ_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure fedora/undefined-sanitizer: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=UndefinedSanitizer - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON .. - && make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ - -fedora/csbuild: - variables: - GIT_DEPTH: "100" - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + extends: .fedora + stage: analysis script: - - | - if [[ -z "$CI_COMMIT_BEFORE_SHA" ]]; then - export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") - fi - - # Check if the commit exists in this branch - # This is not the case for a force push - git branch --contains $CI_COMMIT_BEFORE_SHA 2>/dev/null || export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") - - export CI_COMMIT_RANGE="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA" - - - csbuild - --build-dir=obj-csbuild - --build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON @SRCDIR@ && make clean && make -j$(nproc)" - --git-commit-range $CI_COMMIT_RANGE - --color - --print-current --print-fixed - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj-csbuild/ - -# That is a specific runner that we cannot enable universally. -# We restrict it to builds under the $BUILD_IMAGES_PROJECT project. -freebsd/x86_64: - image: - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make && ctest --output-on-failure - tags: - - freebsd - - private - except: - - tags - only: - - branches@libssh/libssh-mirror - - branches@cryptomilk/libssh-mirror - - branches@jjelen/libssh-mirror - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake + -DCMAKE_BUILD_TYPE=UndefinedSanitizer + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + $CMAKE_BUILD_OPTIONS + -DUNIT_TESTING=ON + -DFUZZ_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure fedora/libgcrypt/x86_64: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON - -DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + extends: .fedora + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON" fedora/mbedtls/x86_64: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON - -DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + extends: .fedora + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DSA=OFF" # 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 - - mkdir -p obj && cd obj && mingw64-cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make -j$(nproc) && - ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - 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 # 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 - - mkdir -p obj && cd obj && mingw32-cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make -j$(nproc) && - ctest --output-on-failure + - 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 + + +############################################################################### +# Fedora csbuild # +############################################################################### +.csbuild: + stage: analysis + variables: + GIT_DEPTH: "100" + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + before_script: + - | + if [[ -z "$CI_COMMIT_BEFORE_SHA" ]]; then + export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") + fi + + # Check if the commit exists in this branch + # This is not the case for a force push + git branch --contains $CI_COMMIT_BEFORE_SHA 2>/dev/null || export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") + + export CI_COMMIT_RANGE="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA" tags: - - shared + - shared except: - - tags + - tags artifacts: expire_in: 1 week when: on_failure paths: - - obj/ + - obj-csbuild/ -tumbleweed/openssl_1.1.x/x86_64/gcc: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD +fedora/csbuild/openssl_3.0.x: + extends: .csbuild script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config - -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - csbuild + --build-dir=obj-csbuild + --build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON -DWITH_DSA=ON @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed + +fedora/csbuild/libgcrypt: + extends: .csbuild + script: + - csbuild + --build-dir=obj-csbuild + --build-cmd "rm -rf CMakeFiles CMakeCache.txt && cmake -DCMAKE_BUILD_TYPE=Debug -DPICKY_DEVELOPER=ON -DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DFUZZ_TESTING=ON -DWITH_GCRYPT=ON -DWITH_DSA=ON @SRCDIR@ && make clean && make -j$(nproc)" + --git-commit-range $CI_COMMIT_RANGE + --color + --print-current --print-fixed + +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 + + +############################################################################### +# Ubuntu builds # +############################################################################### +ubuntu/openssl_1.1.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$UBUNTU_BUILD + extends: .tests + + +############################################################################### +# Alpine builds # +############################################################################### +alpine/musl: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$ALPINE_BUILD + extends: .tests + script: + - cmake $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON + -DWITH_SERVER=ON + -DWITH_ZLIB=ON + -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure + + +############################################################################### +# Tumbleweed builds # +############################################################################### +tumbleweed/openssl_1.1.x/x86_64/gcc: + extends: .tumbleweed + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" tumbleweed/openssl_1.1.x/x86/gcc: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + extends: .tumbleweed script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake + -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake + $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON + -DWITH_SERVER=ON + -DWITH_ZLIB=ON + -DWITH_PCAP=ON + -DWITH_DSA=ON + -DUNIT_TESTING=ON .. tumbleweed/openssl_1.1.x/x86_64/gcc7: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config - -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + extends: .tumbleweed + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" tumbleweed/openssl_1.1.x/x86/gcc7: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD + extends: .tumbleweed script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake - -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + - cmake + -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-cross-m32.cmake + -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 + $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DWITH_DSA=ON + -DUNIT_TESTING=ON .. && + make -j$(nproc) && + ctest --output-on-failure tumbleweed/openssl_1.1.x/x86_64/clang: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config - -DUNIT_TESTING=ON - -DSERVER_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ + extends: .tumbleweed + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" -tumbleweed/docs: +tumbleweed/static-analysis: + extends: .tests + stage: analysis image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD script: - - mkdir -p obj && cd obj && cmake .. && make docs - tags: - - shared - except: - - tags + - export CCC_CC=clang + - export CCC_CXX=clang++ + - scan-build cmake + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DPICKY_DEVELOPER=ON + $CMAKE_BUILD_OPTIONS + $CMAKE_TEST_OPTIONS .. && + scan-build --status-bugs -o scan make -j$(nproc) artifacts: expire_in: 1 week when: on_failure paths: - - obj/ + - obj/scan -tumbleweed/undefined-sanitizer: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD - script: - - mkdir -p obj && cd obj && cmake - -DCMAKE_BUILD_TYPE=UndefinedSanitizer - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && - make -j$(nproc) && ctest --output-on-failure - tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/ -tumbleweed/static-analysis: - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$TUMBLEWEED_BUILD +############################################################################### +# FreeBSD builds # +############################################################################### +# That is a specific runner that we cannot enable universally. +# We restrict it to builds under the $BUILD_IMAGES_PROJECT project. +freebsd/x86_64: + image: + extends: .tests + before_script: + - mkdir -p obj && cd obj && cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. script: - - export CCC_CC=clang - - export CCC_CXX=clang++ - - mkdir -p obj && cd obj && scan-build cmake - -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON -DSERVER_TESTING=ON .. && - scan-build --status-bugs -o scan make -j$(nproc) + - cmake $CMAKE_DEFAULT_OPTIONS + -DWITH_SFTP=ON + -DWITH_SERVER=ON + -DWITH_ZLIB=ON + -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. && + make && + ctest --output-on-failure tags: - - shared - except: - - tags - artifacts: - expire_in: 1 week - when: on_failure - paths: - - obj/scan + - private + - freebsd + only: + - branches@libssh/libssh-mirror + - branches@cryptomilk/libssh-mirror + - branches@jjelen/libssh-mirror + - branches@marco.fortina/libssh-mirror + ############################################################################### # Visual Studio builds # @@ -469,47 +442,36 @@ tumbleweed/static-analysis: variables: ErrorActionPreference: STOP script: - - $env:VCPKG_DEFAULT_TRIPLET="x64-windows" - - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} - - cmake - -A x64 - -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_TOOLCHAIN_FILE" - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. - - cmake --build . - - ctest --output-on-failure + - cmake --build . + - ctest --output-on-failure tags: - - windows - - shared-windows + - windows + - shared-windows except: - - tags + - tags artifacts: expire_in: 1 week when: on_failure paths: - obj/ before_script: - - choco install --no-progress -y cmake - - $env:Path += ';C:\Program Files\CMake\bin' - - If (!(test-path .vcpkg\archives)) { mkdir -p .vcpkg\archives } - - $env:VCPKG_DEFAULT_BINARY_CACHE="$PWD\.vcpkg\archives" - - echo $env:VCPKG_DEFAULT_BINARY_CACHE - - $env:VCPKG_DEFAULT_TRIPLET="$TRIPLET-windows" - - vcpkg install cmocka - - vcpkg install openssl - - vcpkg install zlib - - vcpkg integrate install - - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} - - cmake - -A $PLATFORM - -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake - -DPICKY_DEVELOPER=ON - -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON - -DUNIT_TESTING=ON .. - # The Windows runners are broken for last month - # https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/gcp/windows-containers/-/issues/40 - allow_failure: true + - choco install --no-progress -y cmake + - $env:Path += ';C:\Program Files\CMake\bin' + - If (!(test-path .vcpkg\archives)) { mkdir -p .vcpkg\archives } + - $env:VCPKG_DEFAULT_BINARY_CACHE="$PWD\.vcpkg\archives" + - echo $env:VCPKG_DEFAULT_BINARY_CACHE + - $env:VCPKG_DEFAULT_TRIPLET="$TRIPLET-windows" + - vcpkg install cmocka + - vcpkg install openssl + - vcpkg install zlib + - vcpkg integrate install + - mkdir -p obj; if ($?) {cd obj}; if (! $?) {exit 1} + - cmake + -A $PLATFORM + -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake + -DPICKY_DEVELOPER=ON + -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON + -DUNIT_TESTING=ON .. visualstudio/x86_64: extends: .vs @@ -522,3 +484,45 @@ visualstudio/x86: variables: PLATFORM: "win32" TRIPLET: "x86" + +############################################################################### +# Coverity # +############################################################################### +# +# git push -o ci.variable="COVERITY_SCAN_TOKEN=XXXXXX" \ +# -o ci.variable="COVERITY_SCAN_PROJECT_NAME=XXXXXX" \ +# -o ci.variable="COVERITY_SCAN_EMAIL=XXXXXX" \ +# -f gitlab + +coverity: + stage: analysis + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$COVERITY_BUILD + script: + - mkdir obj && cd obj + - wget https://scan.coverity.com/download/linux64 --post-data "token=$COVERITY_SCAN_TOKEN&project=$COVERITY_SCAN_PROJECT_NAME" -O /tmp/coverity_tool.tgz + - tar xf /tmp/coverity_tool.tgz + - cmake -DCMAKE_BUILD_TYPE=Debug $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS .. + - cov-analysis-linux64-*/bin/cov-build --dir cov-int make -j$(nproc) + - tar czf cov-int.tar.gz cov-int + - curl + --form token=$COVERITY_SCAN_TOKEN + --form email=$COVERITY_SCAN_EMAIL + --form file=@cov-int.tar.gz + --form version="`git describe --tags`" + --form description="CI build" + https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME + tags: + - shared + only: + refs: + - master + - schedules + variables: + - $COVERITY_SCAN_TOKEN != null + - $COVERITY_SCAN_PROJECT_NAME != null + - $COVERITY_SCAN_EMAIL != null + artifacts: + expire_in: 1 week + when: on_failure + paths: + - obj/cov-int/*.txt diff --git a/libssh/ChangeLog b/libssh/CHANGELOG similarity index 88% rename from libssh/ChangeLog rename to libssh/CHANGELOG index 1f3aac9..5d05bca 100644 --- a/libssh/ChangeLog +++ b/libssh/CHANGELOG @@ -1,5 +1,48 @@ -ChangeLog -========== +CHANGELOG +========= + +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) + * Added support for chacha20-poly1305@openssh.com with libgcrypt + * Added support ed25519 keys in PEM files + * Added support for sk-ecdsa and sk-ed25519 (server side) + * Added support for limiting RSA key sizes and not accepting small one by + default + * Added support for ssh-agent on Windows + * Added ssh_userauth_publickey_auto_get_current_identity() API + * Added ssh_vlog() API + * Added ssh_send_issue_banner() API + * Added ssh_session_set_disconnect_message() API + * Added new configuration options: + + IdentityAgent + + ModuliFile + * Provided X11 client example + * Disabled DSA support at build time by default (will be removed in the next + release) + * Deprecated the SCP API! + * Deprecated old pubkey, privatekey API + * Avoided some needless large stack buffers to minimize memory footprint + * Removed support for OpenSSL < 1.0.1 + * Fixed parsing username@host in login name + * Free global init mutex in the destructor on Windows + * Fixed PEM parsing in mbedtls to support both legacy and new PKCS8 formats version 0.9.6 (released 2021-08-26) * CVE-2021-3634: Fix possible heap-buffer overflow when rekeying with @@ -68,7 +111,7 @@ version 0.9.1 (released 2019-10-25) * Fixed deprecation issues (T165) * Fixed known_hosts directory creation (T166) -version 0.9.0 (released 2019-06-28) +version 0.9.0 (released 2019-02-xx) * Added support for AES-GCM * Added improved rekeying support * Added performance improvements @@ -83,71 +126,6 @@ version 0.9.0 (released 2019-06-28) * Improved documentation * Improved OpenSSL API usage for KEX, DH, and signatures -version 0.8.7 (released 2019-02-25) - * Fixed handling extension flags in the server implementation - * Fixed exporting ed25519 private keys - * Fixed corner cases for rsa-sha2 signatures - * Fixed some issues with connector - -version 0.8.6 (released 2018-12-24) - * Fixed compilation issues with different OpenSSL versions - * Fixed StrictHostKeyChecking in new knownhosts API - * Fixed ssh_send_keepalive() with packet filter - * Fixed possible crash with knownhosts options - * Fixed issus with rekeying - * Fixed strong ECDSA keys - * Fixed some issues with rsa-sha2 extentions - * Fixed access violation in ssh_init() (static linking) - * Fixed ssh_channel_close() handling - -version 0.8.5 (released 2018-10-29) - * Added support to get known_hosts locations with ssh_options_get() - * Fixed preferred algorithm for known hosts negotiations - * Fixed KEX with some server implementations (e.g. Cisco) - * Fixed issues with MSVC - * Fixed keyboard-interactive auth in server mode - (regression from CVE-2018-10933) - * Fixed gssapi auth in server mode (regression from CVE-2018-10933) - * Fixed socket fd handling with proxy command - * Fixed a memory leak with OpenSSL - -version 0.8.4 (released 2018-10-16) - * Fixed CVE-2018-10933 - * Fixed building without globbing support - * Fixed possible memory leaks - * Avoid SIGPIPE on sockets - -version 0.8.3 (released 2018-09-21) - * Added support for rsa-sha2 - * Added support to parse private keys in openssh container format - (other than ed25519) - * Added support for diffie-hellman-group18-sha512 and - diffie-hellman-group16-sha512 - * Added ssh_get_fingerprint_hash() - * Added ssh_pki_export_privkey_base64() - * Added support for Match keyword in config file - * Improved performance and reduced memory footprint for sftp - * Fixed ecdsa publickey auth - * Fixed reading a closed channel - * Added support to announce posix-rename@openssh.com and - hardlink@openssh.com in the sftp server - -version 0.8.2 (released 2018-08-30) - * Added sha256 fingerprints for pubkeys - * Improved compiler flag detection - * Fixed race condition in reading sftp messages - * Fixed doxygen generation and added modern style - * Fixed library initialization on Windows - * Fixed __bounded__ attribute detection - * Fixed a bug in the options parser - * Fixed documentation for new knwon_hosts API - -version 0.8.1 (released 2018-08-13) - * Fixed version number in the header - * Fixed version number in pkg-config and cmake config - * Fixed library initialization - * Fixed attribute detection - version 0.8.0 (released 2018-08-10) * Removed support for deprecated SSHv1 protocol * Added new connector API for clients @@ -482,14 +460,6 @@ version 0.3.2 (released 2009-08-05) * Fixed compilation on Solaris. * Fixed SSHv1 compilation. -version 0.3.1 (released 2009-07-14) - * Added return code SSH_SERVER_FILE_NOT_FOUND. - * Fixed compilation of SSHv1. - * Fixed several memory leaks. - * Fixed possible infinite loops. - * Fixed a possible crash bug. - * Fixed build warnings. - * Fixed cmake on BSD. version 0.3.1 (released 2009-07-14) * Added return code SSH_SERVER_FILE_NOT_FOUND. * Fixed compilation of SSHv1. @@ -539,7 +509,7 @@ version 0.2 (released 2007-11-29) version 0.11-dev * Server implementation development. * Small bug corrected when connecting to sun ssh servers. - * Channel wierdness corrected (writing huge data packets) + * Channel weirdness corrected (writing huge data packets) * Channel_read_nonblocking added * Channel bug where stderr wasn't correctly read fixed. * Added sftp_file_set_nonblocking(), which is nonblocking SFTP IO diff --git a/libssh/CMakeLists.txt b/libssh/CMakeLists.txt index a993012..7bc3331 100644 --- a/libssh/CMakeLists.txt +++ b/libssh/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.3.0) cmake_policy(SET CMP0048 NEW) -# Specify search path for CMake modules to be loaded by include() +# Specify search path for CMake modules to be loaded by include() # and find_package() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") @@ -10,7 +10,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") include(DefineCMakeDefaults) include(DefineCompilerFlags) -project(libssh VERSION 0.9.6 LANGUAGES C) +project(libssh VERSION 0.10.4 LANGUAGES C) # global needed variable set(APPLICATION_NAME ${PROJECT_NAME}) @@ -22,7 +22,7 @@ set(APPLICATION_NAME ${PROJECT_NAME}) # Increment AGE. Set REVISION to 0 # If the source code was changed, but there were no interface changes: # Increment REVISION. -set(LIBRARY_VERSION "4.8.7") +set(LIBRARY_VERSION "4.9.4") set(LIBRARY_SOVERSION "4") # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked @@ -58,7 +58,7 @@ elseif(WITH_MBEDTLS) message(FATAL_ERROR "Could not find mbedTLS") endif (NOT MBEDTLS_FOUND) else (WITH_GCRYPT) - find_package(OpenSSL) + find_package(OpenSSL 1.0.1) if (OPENSSL_FOUND) # On CMake < 3.16, OPENSSL_CRYPTO_LIBRARIES is usually a synonym for OPENSSL_CRYPTO_LIBRARY, but is not defined # when building on Windows outside of Cygwin. We provide the synonym here, if FindOpenSSL didn't define it already. @@ -89,6 +89,13 @@ if (WITH_GSSAPI) find_package(GSSAPI) endif (WITH_GSSAPI) +if (WITH_PKCS11_URI) + find_package(softhsm) + if (NOT SOFTHSM_FOUND) + message(SEND_ERROR "Could not find softhsm module!") + endif (NOT SOFTHSM_FOUND) +endif (WITH_PKCS11_URI) + if (WITH_NACL) find_package(NaCl) if (NOT NACL_FOUND) @@ -96,9 +103,9 @@ if (WITH_NACL) endif (NOT NACL_FOUND) endif (WITH_NACL) -if (BSD OR SOLARIS OR OSX) +if (BSD OR SOLARIS OR OSX OR CYGWIN) find_package(Argp) -endif (BSD OR SOLARIS OR OSX) +endif (BSD OR SOLARIS OR OSX OR CYGWIN) # Disable symbol versioning in non UNIX platforms if (UNIX) @@ -117,7 +124,7 @@ add_subdirectory(include) add_subdirectory(src) # pkg-config file -if (UNIX) +if (UNIX OR MINGW) configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc) install( FILES @@ -127,7 +134,7 @@ install( COMPONENT pkgconfig ) -endif (UNIX) +endif (UNIX OR MINGW) # CMake config files include(CMakePackageConfigHelpers) @@ -227,11 +234,14 @@ message(STATUS "SFTP support: ${WITH_SFTP}") message(STATUS "Server support : ${WITH_SERVER}") message(STATUS "GSSAPI support : ${WITH_GSSAPI}") message(STATUS "GEX support : ${WITH_GEX}") +message(STATUS "Support insecure none cipher and MAC : ${WITH_INSECURE_NONE}") 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 "PKCS #11 URI support: ${WITH_PKCS11_URI}") +message(STATUS "DSA support: ${WITH_DSA}") set(_SERVER_TESTING OFF) if (WITH_SERVER) set(_SERVER_TESTING ${SERVER_TESTING}) diff --git a/libssh/README.CodingStyle b/libssh/CONTRIBUTING.md similarity index 58% rename from libssh/README.CodingStyle rename to libssh/CONTRIBUTING.md index 7489cce..4f5bb42 100644 --- a/libssh/README.CodingStyle +++ b/libssh/CONTRIBUTING.md @@ -1,9 +1,126 @@ -Coding conventions in the libssh tree -====================================== +# How to contribute a patch to libssh -=========== -Quick Start -=========== +Please checkout the libssh source code using git. + +For contributions we prefer Merge Requests on Gitlab: + +https://gitlab.com/libssh/libssh-mirror/ + +This way you get continuous integration which runs the complete libssh +testsuite for you. + +For larger code changes, breaking the changes up into a set of simple +patches, each of which does a single thing, are much easier to review. +Patch sets like that will most likely have an easier time being merged +into the libssh code than large single patches that make lots of +changes in one large diff. + +Also bugfixes and new features should be covered by tests. We use the cmocka +and cwrap framework for our testing and you can simply run it locally by +calling `make test`. + +## Ownership of the contributed code + +libssh is a project with distributed copyright ownership, which means +we prefer the copyright on parts of libssh to be held by individuals +rather than corporations if possible. There are historical legal +reasons for this, but one of the best ways to explain it is that it's +much easier to work with individuals who have ownership than corporate +legal departments if we ever need to make reasonable compromises with +people using and working with libssh. + +We track the ownership of every part of libssh via https://git.libssh.org, +our source code control system, so we know the provenance of every piece +of code that is committed to libssh. + +So if possible, if you're doing libssh changes on behalf of a company +who normally owns all the work you do please get them to assign +personal copyright ownership of your changes to you as an individual, +that makes things very easy for us to work with and avoids bringing +corporate legal departments into the picture. + +If you can't do this we can still accept patches from you owned by +your employer under a standard employment contract with corporate +copyright ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux +Kernel community, so it should be very easy to get a sign off from +your corporate legal department. The only changes we've made are to +accommodate the license we use, which is LGPLv2 (or later) whereas the +Linux kernel uses GPLv2. + +The process is called signing. + +## How to sign your work + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + + +``` +libssh Developer's Certificate of Origin. Version 1.0 + + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project under the terms of the + GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of + the License, or (at the option of the project) any later version. + + https://www.gnu.org/licenses/lgpl-2.1.html +``` + +We will maintain a copy of that email as a record that you have the +rights to contribute code to libssh under the required licenses whilst +working for the company where the email came from. + +Then when sending in a patch via the normal mechanisms described +above, add a line that states: + + Signed-off-by: Random J Developer + +using your real name and the email address you sent the original email +you used to send the libssh Developer's Certificate of Origin to us +(sorry, no pseudonyms or anonymous contributions.) + +That's it! Such code can then quite happily contain changes that have +copyright messages such as: + + (c) Example Corporation. + +and can be merged into the libssh codebase in the same way as patches +from any other individual. You don't need to send in a copy of the +libssh Developer's Certificate of Origin for each patch, or inside each +patch. Just the sign-off message is all that is required once we've +received the initial email. + + +# Coding conventions in the libssh tree + +## Quick Start Coding style guidelines are about reducing the number of unnecessary reformatting patches and making things easier for developers to work together. @@ -36,31 +153,28 @@ are the highlights. have a copy of "The C Programming Language" anyways right? -============= -Editor Hints -============= +## Editor Hints + +### Emacs -Emacs ------- Add the follow to your $HOME/.emacs file: - (add-hook 'c-mode-hook - (lambda () - (c-set-style "linux") - (c-toggle-auto-state))) + (add-hook 'c-mode-hook + (lambda () + (c-set-style "linux") + (c-toggle-auto-state))) -Vim ----- +## Neovim/VIM For the basic vi editor included with all variants of \*nix, add the -following to $HOME/.vimrc: +following to ~/.config/nvim/init.rc or ~/.vimrc: set ts=4 sw=4 et cindent You can use the Vim gitmodline plugin to store this in the git config: - https://git.cryptomilk.org/projects/vim-gitmodeline.git/ +https://git.cryptomilk.org/projects/vim-gitmodeline.git/ For Vim, the following settings in $HOME/.vimrc will also deal with displaying trailing whitespace: @@ -81,12 +195,9 @@ displaying trailing whitespace: autocmd BufNewFile,BufRead *.c,*.h exec 'match Todo /\%>' . &textwidth . 'v.\+/' -========================== -FAQ & Statement Reference -========================== +## FAQ & Statement Reference -Comments ---------- +### Comments Comments should always use the standard C syntax. C++ style comments are not currently allowed. @@ -163,8 +274,7 @@ This is bad: * This is a multi line comment, * with some more words...*/ -Indention & Whitespace & 80 columns ------------------------------------- +### Indention & Whitespace & 80 columns To avoid confusion, indentations have to be 4 spaces. Do not use tabs!. When wrapping parameters for function calls, align the parameter list with the first @@ -180,8 +290,7 @@ splitting. Never split a line before columns 70 - 79 unless you have a really good reason. Be smart about formatting. -If, switch, & Code blocks --------------------------- +### If, switch, & Code blocks Always follow an 'if' keyword with a space but don't include additional spaces following or preceding the parentheses in the conditional. @@ -207,7 +316,7 @@ invoking functions. Braces for code blocks used by for, if, switch, while, do..while, etc. should begin on the same line as the statement keyword and end on a line of their own. You should always include braces, even if the block only contains one -statement. NOTE: Functions are different and the beginning left brace should +statement. **NOTE**: Functions are different and the beginning left brace should be located in the first column on the next line. If the beginning statement has to be broken across lines due to length, the @@ -254,8 +363,7 @@ Bad examples: print("I should be in braces.\n"); -Goto ------ +### Goto While many people have been academically taught that "goto"s are fundamentally evil, they can greatly enhance readability and reduce memory leaks when used as @@ -287,14 +395,13 @@ Good Examples: return rc; } -Initialize pointers -------------------- +### Initialize pointers -All pointer variables MUST be initialized to NULL. History has +All pointer variables **MUST** be initialized to `NULL`. History has demonstrated that uninitialized pointer variables have lead to various bugs and security issues. -Pointers MUST be initialized even if the assignment directly follows +Pointers **MUST** be initialized even if the assignment directly follows the declaration, like pointer2 in the example below, because the instructions sequence may change over time. @@ -309,15 +416,13 @@ Good Example: pointer1 = some_func1(); -Typedefs ---------- +### Typedefs -libssh tries to avoid "typedef struct { .. } x_t;" so we do always try to use -"struct x { .. };". We know there are still such typedefs in the code, but for +libssh tries to avoid `typedef struct { .. } x_t;` so we do always try to use +`struct x { .. };`. We know there are still such typedefs in the code, but for new code, please don't do that anymore. -Make use of helper variables ------------------------------ +### Make use of helper variables Please try to avoid passing function calls as function parameters in new code. This makes the code much easier to read and it's also easier to use the "step" @@ -367,9 +472,13 @@ an iterator style: But in general, please try to avoid this pattern. -Control-Flow changing macros ------------------------------ +### Control-Flow changing macros -Macros like STATUS_NOT_OK_RETURN that change control flow (return/goto/etc) +Macros like `STATUS_NOT_OK_RETURN` that change control flow (return/goto/etc) from within the macro are considered bad, because they look like function calls that never change control flow. Please do not introduce them. + + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/libssh/CPackConfig.cmake b/libssh/CPackConfig.cmake index c4a3598..5bd52c5 100644 --- a/libssh/CPackConfig.cmake +++ b/libssh/CPackConfig.cmake @@ -10,7 +10,7 @@ set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) # SOURCE GENERATOR set(CPACK_SOURCE_GENERATOR "TXZ") -set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]git/;/[.]clangd/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]git/;/[.]clangd/;/[.]cache/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") ### NSIS INSTALLER diff --git a/libssh/CompilerChecks.cmake b/libssh/CompilerChecks.cmake index 5bdc05c..9acae28 100644 --- a/libssh/CompilerChecks.cmake +++ b/libssh/CompilerChecks.cmake @@ -85,9 +85,11 @@ if (UNIX) endif() endif (WITH_STACK_PROTECTOR_STRONG) - check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) - if (WITH_STACK_CLASH_PROTECTION) - list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") + if (NOT WINDOWS AND NOT CYGWIN) + check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) + if (WITH_STACK_CLASH_PROTECTION) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") + endif() endif() if (PICKY_DEVELOPER) diff --git a/libssh/ConfigureChecks.cmake b/libssh/ConfigureChecks.cmake index 5d43b36..7103f30 100644 --- a/libssh/ConfigureChecks.cmake +++ b/libssh/ConfigureChecks.cmake @@ -102,31 +102,11 @@ if (OPENSSL_FOUND) set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(EVP_aes_128_ctr HAVE_OPENSSL_EVP_AES_CTR) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(EVP_aes_128_cbc HAVE_OPENSSL_EVP_AES_CBC) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(EVP_aes_128_gcm HAVE_OPENSSL_EVP_AES_GCM) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(CRYPTO_THREADID_set_callback HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(CRYPTO_ctr128_encrypt HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) - check_function_exists(EVP_CIPHER_CTX_new HAVE_OPENSSL_EVP_CIPHER_CTX_NEW) + 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_id HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID) + 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}) @@ -150,6 +130,14 @@ if (OPENSSL_FOUND) set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_symbol_exists(EVP_PKEY_ED25519 "openssl/evp.h" FOUND_OPENSSL_ED25519) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) + check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) + + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) + check_symbol_exists(EVP_PKEY_POLY1305 "openssl/evp.h" HAVE_OPENSSL_EVP_POLY1305) + if (HAVE_OPENSSL_EVP_DIGESTSIGN AND HAVE_OPENSSL_EVP_DIGESTVERIFY AND FOUND_OPENSSL_ED25519) set(HAVE_OPENSSL_ED25519 1) @@ -175,11 +163,18 @@ if (NOT WITH_GCRYPT AND NOT WITH_MBEDTLS) if (HAVE_OPENSSL_ECC) set(HAVE_ECC 1) endif (HAVE_OPENSSL_ECC) + + if (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + set(HAVE_OPENSSL_EVP_KDF_CTX 1) + endif (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + endif () -if (NOT WITH_MBEDTLS) - set(HAVE_DSA 1) -endif (NOT WITH_MBEDTLS) +if (WITH_DSA) + if (NOT WITH_MBEDTLS) + set(HAVE_DSA 1) + endif (NOT WITH_MBEDTLS) +endif() # FUNCTIONS @@ -278,11 +273,20 @@ if (GCRYPT_FOUND) set(HAVE_GCRYPT_ECC 1) set(HAVE_ECC 1) endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") + if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + set(HAVE_GCRYPT_CHACHA_POLY 1) + endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") endif (GCRYPT_FOUND) if (MBEDTLS_FOUND) set(HAVE_LIBMBEDCRYPTO 1) set(HAVE_ECC 1) + + 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) + unset(CMAKE_REQUIRED_INCLUDES) + endif (MBEDTLS_FOUND) if (CMAKE_USE_PTHREADS_INIT) @@ -371,6 +375,23 @@ int main(void) { return 0; }" HAVE_FALLTHROUGH_ATTRIBUTE) +check_c_source_compiles(" +#define WEAK __attribute__((weak)) + +WEAK int sum(int a, int b) +{ + return a + b; +} + +int main(void) +{ + int i = sum(2, 2); + + (void)i; + + return 0; +}" HAVE_WEAK_ATTRIBUTE) + if (NOT WIN32) check_c_source_compiles(" #define __unused __attribute__((unused)) @@ -464,6 +485,28 @@ if (WITH_GSSAPI AND NOT GSSAPI_FOUND) set(WITH_GSSAPI 0) endif (WITH_GSSAPI AND NOT GSSAPI_FOUND) +if (WITH_PKCS11_URI) + if (WITH_GCRYPT) + message(FATAL_ERROR "PKCS #11 is not supported for gcrypt.") + set(WITH_PKCS11_URI 0) + endif() + if (WITH_MBEDTLS) + message(FATAL_ERROR "PKCS #11 is not supported for mbedcrypto") + set(WITH_PKCS11_URI 0) + endif() + if (HAVE_OPENSSL AND NOT OPENSSL_VERSION VERSION_GREATER_EQUAL "1.1.1") + message(FATAL_ERROR "PKCS #11 requires at least OpenSSL 1.1.1") + set(WITH_PKCS11_URI 0) + endif() +endif() + +if (WITH_MBEDTLS) + if (WITH_DSA) + message(FATAL_ERROR "DSA is not supported with mbedTLS crypto") + set(HAVE_DSA 0) + endif() +endif() + # ENDIAN if (NOT WIN32) test_big_endian(WORDS_BIGENDIAN) diff --git a/libssh/DefineOptions.cmake b/libssh/DefineOptions.cmake index b82a501..068db98 100644 --- a/libssh/DefineOptions.cmake +++ b/libssh/DefineOptions.cmake @@ -5,12 +5,14 @@ option(WITH_SERVER "Build with SSH server support" ON) option(WITH_DEBUG_CRYPTO "Build with cryto debug output" OFF) option(WITH_DEBUG_PACKET "Build with packet debug output" OFF) option(WITH_DEBUG_CALLTRACE "Build with calltrace debug output" ON) +option(WITH_DSA "Build with DSA" OFF) option(WITH_GCRYPT "Compile against libgcrypt" OFF) option(WITH_MBEDTLS "Compile against libmbedtls" OFF) option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) 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(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) @@ -20,7 +22,8 @@ option(WITH_NACL "Build with libnacl (curve25519)" ON) option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) option(WITH_ABI_BREAK "Allow ABI break" OFF) option(WITH_GEX "Enable DH Group exchange mechanisms" ON) -option(FUZZ_TESTING "Build with fuzzer for the server" OFF) +option(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(PICKY_DEVELOPER "Build with picky developer flags" OFF) if (WITH_ZLIB) @@ -53,3 +56,7 @@ endif (NOT GLOBAL_BIND_CONFIG) if (NOT GLOBAL_CLIENT_CONFIG) set(GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config") endif (NOT GLOBAL_CLIENT_CONFIG) + +if (FUZZ_TESTING) + set(WITH_INSECURE_NONE ON) +endif (FUZZ_TESTING) diff --git a/libssh/INSTALL b/libssh/INSTALL index 0f0cebf..c2eae34 100644 --- a/libssh/INSTALL +++ b/libssh/INSTALL @@ -7,8 +7,8 @@ In order to build libssh, you need to install several components: - A C compiler -- [CMake](https://www.cmake.org) >= 2.6.0. -- [openssl](https://www.openssl.org) >= 0.9.8 +- [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 - [libz](https://www.zlib.net) >= 1.2 diff --git a/libssh/README b/libssh/README index 44100e7..09a7e56 100644 --- a/libssh/README +++ b/libssh/README @@ -36,7 +36,7 @@ https://www.libssh.org 4* Contributing -_-_-_-_-_-_-_-_-_ -Please read the file 'SubmittingPatches' next to this README file. It explains +Please read the file 'CONTRIBUTING.md' next to this README file. It explains our copyright policy and how you should send patches for upstream inclusion. Have fun and happy libssh hacking! diff --git a/libssh/README.md b/libssh/README.md index 450b67c..cd6b9ea 100644 --- a/libssh/README.md +++ b/libssh/README.md @@ -1,4 +1,5 @@ [![pipeline status](https://gitlab.com/libssh/libssh-mirror/badges/master/pipeline.svg)](https://gitlab.com/libssh/libssh-mirror/commits/master) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libssh) ``` _ _ _ _ @@ -36,7 +37,7 @@ https://www.libssh.org # Contributing -Please read the file 'SubmittingPatches' next to this README file. It explains +Please read the file 'CONTRIBUTING.md' next to this README file. It explains our copyright policy and how you should send patches for upstream inclusion. Have fun and happy libssh hacking! diff --git a/libssh/SubmittingPatches b/libssh/SubmittingPatches deleted file mode 100644 index bd38fae..0000000 --- a/libssh/SubmittingPatches +++ /dev/null @@ -1,118 +0,0 @@ -How to contribute a patch to libssh -==================================== - -Please checkout the libssh source code using git. Change the code and then -use "git format-patch" to create a patch. The patch should be signed (see -below) and send it to libssh@libssh.org, or attach it to a bug report at -https://red.libssh.org/ - -For larger code changes, breaking the changes up into a set of simple -patches, each of which does a single thing, are much easier to review. -Patch sets like that will most likely have an easier time being merged -into the libssh code than large single patches that make lots of -changes in one large diff. - -Ownership of the contributed code -================================== - -libssh is a project with distributed copyright ownership, which means -we prefer the copyright on parts of libssh to be held by individuals -rather than corporations if possible. There are historical legal -reasons for this, but one of the best ways to explain it is that it's -much easier to work with individuals who have ownership than corporate -legal departments if we ever need to make reasonable compromises with -people using and working with libssh. - -We track the ownership of every part of libssh via https://git.libssh.org, -our source code control system, so we know the provenance of every piece -of code that is committed to libssh. - -So if possible, if you're doing libssh changes on behalf of a company -who normally owns all the work you do please get them to assign -personal copyright ownership of your changes to you as an individual, -that makes things very easy for us to work with and avoids bringing -corporate legal departments into the picture. - -If you can't do this we can still accept patches from you owned by -your employer under a standard employment contract with corporate -copyright ownership. It just requires a simple set-up process first. - -We use a process very similar to the way things are done in the Linux -Kernel community, so it should be very easy to get a sign off from -your corporate legal department. The only changes we've made are to -accommodate the license we use, which is LGPLv2 (or later) whereas the -Linux kernel uses GPLv2. - -The process is called signing. - -How to sign your work ----------------------- - -Once you have permission to contribute to libssh from your employer, simply -email a copy of the following text from your corporate email address to: - -contributing@libssh.org - - - -libssh Developer's Certificate of Origin. Version 1.0 - - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the appropriate - version of the GNU General Public License; or - -(b) The contribution is based upon previous work that, to the best of - my knowledge, is covered under an appropriate open source license - and I have the right under that license to submit that work with - modifications, whether created in whole or in part by me, under - the GNU General Public License, in the appropriate version; or - -(c) The contribution was provided directly to me by some other - person who certified (a) or (b) and I have not modified it. - -(d) I understand and agree that this project and the contribution are - public and that a record of the contribution (including all - metadata and personal information I submit with it, including my - sign-off) is maintained indefinitely and may be redistributed - consistent with the libssh Team's policies and the requirements of - the GNU GPL where they are relevant. - -(e) I am granting this work to this project under the terms of the - GNU Lesser General Public License as published by the - Free Software Foundation; either version 2.1 of - the License, or (at the option of the project) any later version. - - https://www.gnu.org/licenses/lgpl-2.1.html - - -We will maintain a copy of that email as a record that you have the -rights to contribute code to libssh under the required licenses whilst -working for the company where the email came from. - -Then when sending in a patch via the normal mechanisms described -above, add a line that states: - - Signed-off-by: Random J Developer - -using your real name and the email address you sent the original email -you used to send the libssh Developer's Certificate of Origin to us -(sorry, no pseudonyms or anonymous contributions.) - -That's it! Such code can then quite happily contain changes that have -copyright messages such as: - - (c) Example Corporation. - -and can be merged into the libssh codebase in the same way as patches -from any other individual. You don't need to send in a copy of the -libssh Developer's Certificate of Origin for each patch, or inside each -patch. Just the sign-off message is all that is required once we've -received the initial email. - -Have fun and happy libssh hacking ! - -The libssh Team - diff --git a/libssh/cmake/Modules/DefineCompilerFlags.cmake b/libssh/cmake/Modules/DefineCompilerFlags.cmake index c9aea58..39378a1 100644 --- a/libssh/cmake/Modules/DefineCompilerFlags.cmake +++ b/libssh/cmake/Modules/DefineCompilerFlags.cmake @@ -36,9 +36,9 @@ if (UNIX AND NOT WIN32) CACHE STRING "Flags used by the linker during MEMORYSANITIZER builds.") # Activate with: -DCMAKE_BUILD_TYPE=UndefinedSanitizer - set(CMAKE_C_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover" + set(CMAKE_C_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" CACHE STRING "Flags used by the C compiler during UNDEFINEDSANITIZER builds.") - set(CMAKE_CXX_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover" + set(CMAKE_CXX_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" CACHE STRING "Flags used by the CXX compiler during UNDEFINEDSANITIZER builds.") set(CMAKE_SHARED_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") diff --git a/libssh/cmake/Modules/Findsofthsm.cmake b/libssh/cmake/Modules/Findsofthsm.cmake new file mode 100644 index 0000000..3a29b6d --- /dev/null +++ b/libssh/cmake/Modules/Findsofthsm.cmake @@ -0,0 +1,36 @@ +# - Try to find softhsm +# Once done this will define +# +# SOFTHSM_FOUND - system has softhsm +# SOFTHSM_LIBRARIES - Link these to use softhsm +# +#============================================================================= +# Copyright (c) 2019 Sahana Prasad +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +find_library(SOFTHSM2_LIBRARY + NAMES + softhsm2 +) + +if (SOFTHSM2_LIBRARY) + set(SOFTHSM_LIBRARIES + ${SOFTHSM_LIBRARIES} + ${SOFTHSM2_LIBRARY} + ) +endif (SOFTHSM2_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(softhsm DEFAULT_MSG SOFTHSM_LIBRARIES) + +# show the SOFTHSM_INCLUDE_DIR and SOFTHSM_LIBRARIES variables only in the advanced view +mark_as_advanced(SOFTHSM_LIBRARIES) diff --git a/libssh/config.h.cmake b/libssh/config.h.cmake index 98a72f6..1357615 100644 --- a/libssh/config.h.cmake +++ b/libssh/config.h.cmake @@ -103,28 +103,19 @@ /* Define to 1 if you have OpenSSL with X25519 support */ #cmakedefine HAVE_OPENSSL_X25519 1 -/*************************** FUNCTIONS ***************************/ - -/* Define to 1 if you have the `EVP_aes128_ctr' function. */ -#cmakedefine HAVE_OPENSSL_EVP_AES_CTR 1 - -/* Define to 1 if you have the `EVP_aes128_cbc' function. */ -#cmakedefine HAVE_OPENSSL_EVP_AES_CBC 1 - -/* Define to 1 if you have the `EVP_aes128_gcm' function. */ -#cmakedefine HAVE_OPENSSL_EVP_AES_GCM 1 +/* Define to 1 if you have OpenSSL with Poly1305 support */ +#cmakedefine HAVE_OPENSSL_EVP_POLY1305 1 -/* Define to 1 if you have the `CRYPTO_THREADID_set_callback' function. */ -#cmakedefine HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK 1 +/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ +#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 -/* Define to 1 if you have the `CRYPTO_ctr128_encrypt' function. */ -#cmakedefine HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT 1 +/*************************** FUNCTIONS ***************************/ -/* Define to 1 if you have the `EVP_CIPHER_CTX_new' function. */ -#cmakedefine HAVE_OPENSSL_EVP_CIPHER_CTX_NEW 1 +/* Define to 1 if you have the `EVP_chacha20' function. */ +#cmakedefine HAVE_OPENSSL_EVP_CHACHA20 1 -/* Define to 1 if you have the `EVP_KDF_CTX_new_id' function. */ -#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID 1 +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' or `EVP_KDF_CTX_new` function. */ +#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX 1 /* Define to 1 if you have the `FIPS_mode' function. */ #cmakedefine HAVE_OPENSSL_FIPS_MODE 1 @@ -234,6 +225,7 @@ #cmakedefine HAVE_FALLTHROUGH_ATTRIBUTE 1 #cmakedefine HAVE_UNUSED_ATTRIBUTE 1 +#cmakedefine HAVE_WEAK_ATTRIBUTE 1 #cmakedefine HAVE_CONSTRUCTOR_ATTRIBUTE 1 #cmakedefine HAVE_DESTRUCTOR_ATTRIBUTE 1 @@ -260,6 +252,9 @@ /* 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 */ +#cmakedefine WITH_INSECURE_NONE 1 + /* Define to 1 if you want to enable blowfish cipher support */ #cmakedefine WITH_BLOWFISH_CIPHER 1 @@ -278,6 +273,9 @@ /* Define to 1 if you want to enable NaCl support */ #cmakedefine WITH_NACL 1 +/* Define to 1 if you want to enable PKCS #11 URI support */ +#cmakedefine WITH_PKCS11_URI 1 + /*************************** ENDIAN *****************************/ /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most diff --git a/libssh/doc/README.gitlab.freebsd.md b/libssh/doc/README.gitlab.freebsd.md new file mode 100644 index 0000000..65628d2 --- /dev/null +++ b/libssh/doc/README.gitlab.freebsd.md @@ -0,0 +1,101 @@ +# Install a FreeBSD CI instance + +Install the following packages: + +``` +pkg install -y bash git gmake cmake cmocka openssl wget pkgconf ccache bash +``` + +Create gitlab-runner user: + +``` +pw group add -n gitlab-runner +pw user add -n gitlab-runner -g gitlab-runner -s /usr/local/bin/bash +mkdir /home/gitlab-runner +chown gitlab-runner:gitlab-runner /home/gitlab-runner +``` + +Get the gitlab-runner binary for freebsd: + +``` +wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-freebsd-amd64 +chmod +x /usr/local/bin/gitlab-runner +``` + +Create a log file and allow access: + +``` +touch /var/log/gitlab_runner.log && chown gitlab-runner:gitlab-runner /var/log/gitlab_runner.log +``` + +We need a start script to run it on boot: + +``` +mkdir -p /usr/local/etc/rc.d +cat > /usr/local/etc/rc.d/gitlab_runner << EOF +#!/usr/local/bin/bash +# PROVIDE: gitlab_runner +# REQUIRE: DAEMON NETWORKING +# BEFORE: +# KEYWORD: + +. /etc/rc.subr + +name="gitlab_runner" +rcvar="gitlab_runner_enable" + +load_rc_config $name + +user="gitlab-runner" +user_home="/home/gitlab-runner" +command="/usr/local/bin/gitlab-runner run" +pidfile="/var/run/${name}.pid" + +start_cmd="gitlab_runner_start" +stop_cmd="gitlab_runner_stop" +status_cmd="gitlab_runner_status" + +gitlab_runner_start() +{ + export USER=${user} + export HOME=${user_home} + + if checkyesno ${rcvar}; then + cd ${user_home} + /usr/sbin/daemon -u ${user} -p ${pidfile} ${command} > /var/log/gitlab_runner.log 2>&1 + fi +} + +gitlab_runner_stop() +{ + if [ -f ${pidfile} ]; then + kill `cat ${pidfile}` + fi +} + +gitlab_runner_status() +{ + if [ ! -f ${pidfile} ] || kill -0 `cat ${pidfile}`; then + echo "Service ${name} is not running." + else + echo "${name} appears to be running." + fi +} + +run_rc_command $1 +EOF +chmod +x /usr/local/etc/rc.d/gitlab_runner +``` + +Register your gitlab-runner with your gitlab project + +``` +su gitlab-runner -c 'gitlab-runner register' +``` + +Start the gitlab runner service: + +``` +sysrc -f /etc/rc.conf "gitlab_runner_enable=YES" +service gitlab_runner start +``` diff --git a/libssh/doc/authentication.dox b/libssh/doc/authentication.dox index 3196f64..7d0ab81 100644 --- a/libssh/doc/authentication.dox +++ b/libssh/doc/authentication.dox @@ -33,6 +33,9 @@ The process of authenticating by public key to a server is the following: used to authenticate the user). - then, you retrieve the private key for this key and send a message proving that you know that private key. + - when several identity files are specified, then the order of processing of + these files is from the last-mentioned to the first one + (if specified in the ~/.ssh/config, then starting from the bottom to the top). The function ssh_userauth_autopubkey() does this using the available keys in "~/.ssh/". The return values are the following: diff --git a/libssh/doc/forwarding.dox b/libssh/doc/forwarding.dox index bb93c7b..2b202b4 100644 --- a/libssh/doc/forwarding.dox +++ b/libssh/doc/forwarding.dox @@ -101,7 +101,7 @@ used to retrieve google's home page from the remote SSH server. int direct_forwarding(ssh_session session) { ssh_channel forwarding_channel; - int rc; + int rc = SSH_ERROR; char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; int nbytes, nwritten; @@ -165,6 +165,8 @@ int web_server(ssh_session session) char buffer[256]; int nbytes, nwritten; int port = 0; + char *peer_address = NULL; + int peer_port = 0; char *helloworld = "" "HTTP/1.1 200 OK\n" "Content-Type: text/html\n" @@ -187,7 +189,8 @@ int web_server(ssh_session session) return rc; } - channel = ssh_channel_accept_forward(session, 60000, &port); + channel = ssh_channel_open_forward_port(session, 60000, &port, + &peer_address, &peer_port); if (channel == NULL) { fprintf(stderr, "Error waiting for incoming connection: %s\n", @@ -204,6 +207,7 @@ int web_server(ssh_session session) ssh_get_error(session)); ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_ERROR; } if (strncmp(buffer, "GET /", 5)) continue; @@ -216,13 +220,15 @@ int web_server(ssh_session session) ssh_get_error(session)); ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_ERROR; } - printf("Sent answer\n"); + printf("Sent answer to %s:%d\n", peer_address, peer_port); } ssh_channel_send_eof(channel); ssh_channel_free(channel); + ssh_string_free_char(peer_address); return SSH_OK; } @endcode diff --git a/libssh/doc/mainpage.dox b/libssh/doc/mainpage.dox index f4ff4d8..a3d6408 100644 --- a/libssh/doc/mainpage.dox +++ b/libssh/doc/mainpage.dox @@ -21,9 +21,9 @@ The libssh library provides: - Key Exchange Methods: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 - Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256,ssh-dss - - Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc, none + - 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, none + - MAC hashes: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5 - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic - Channels: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, auth-agent-req@openssh.com - Global Requests: tcpip-forward, forwarded-tcpip @@ -38,7 +38,7 @@ The libssh library provides: @section main-additional-features Additional Features - Client and server support - - SSHv2 and SSHv1 protocol support + - SSHv2 protocol support - Supports Linux, UNIX, BSD, Solaris, OS/2 and Windows - Automated test cases with nightly tests - Event model based on poll(2), or a poll(2)-emulation. @@ -211,6 +211,8 @@ It was later modified and expanded by the following RFCs. (only the "server-sig-algs" extension implemented) - RFC 8332, Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol + - RFC 8709, + Ed25519 and Ed448 Public Key Algorithms for the Secure Shell (SSH) Protocol There are also drafts that are being currently developed and followed. diff --git a/libssh/doc/pkcs11.dox b/libssh/doc/pkcs11.dox new file mode 100644 index 0000000..0bdfc6d --- /dev/null +++ b/libssh/doc/pkcs11.dox @@ -0,0 +1,67 @@ +/** +@page libssh_tutor_pkcs11 Chapter 9: Authentication using PKCS #11 URIs +@section how_to How to use PKCS #11 URIs in libssh? + +PKCS #11 is a Cryptographic Token Interface Standard that provides an API +to devices like smart cards that store cryptographic private information. +Such cryptographic devices are referenced as tokens. A mechanism through which +objects stored on the tokens can be uniquely identified is called PKCS #11 URI +(Uniform Resource Identifier) and is defined in RFC 7512 +(https://tools.ietf.org/html/rfc7512). + +Pre-requisites: + +OpenSSL defines an abstract layer called the "engine" to achieve cryptographic +acceleration. The engine_pkcs11 module acts like an interface between the PKCS #11 +modules and the OpenSSL engine. + +To build and use libssh with PKCS #11 support: +1. Enable the cmake option: $ cmake -DWITH_PKCS11_URI=ON +2. Build with OpenSSL. +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). + +The functions ssh_pki_import_pubkey_file() and ssh_pki_import_privkey_file() that +import the public and private keys from files respectively are now modified to support +PKCS #11 URIs. These functions automatically detect if the provided filename is a file path +or a PKCS #11 URI (when it begins with "pkcs11:"). If a PKCS #11 URI is detected, +the engine is loaded and initialized. Through the engine, the private/public key +corresponding to the PKCS #11 URI are loaded from the PKCS #11 device. + +If you wish to authenticate using public keys on your own, follow the steps mentioned under +"Authentication with public keys" in Chapter 2 - A deeper insight into authentication. + +The function pki_uri_import() is used to populate the public/private ssh_key from the +engine with PKCS #11 URIs as the look up. + +Here is a minimalistic example of public key authentication using PKCS #11 URIs: + +@code +int authenticate_pkcs11_URI(ssh_session session) +{ + int rc; + char priv_uri[1042] = "pkcs11:token=my-token;object=my-object;type=private?pin-value=1234"; + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK) + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication with PKCS #11 URIs failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@subsection Caveats + +We recommend the users to provide a specific PKCS #11 URI so that it matches only a single slot in the engine. +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. + +*/ diff --git a/libssh/doc/shell.dox b/libssh/doc/shell.dox index 0693bbc..d770f27 100644 --- a/libssh/doc/shell.dox +++ b/libssh/doc/shell.dox @@ -320,18 +320,36 @@ int interactive_shell_session(ssh_session session, ssh_channel channel) If your remote application is graphical, you can forward the X11 protocol to your local computer. -To do that, you first declare that you accept X11 connections with -ssh_channel_accept_x11(). Then you create the forwarding tunnel for -the X11 protocol with ssh_channel_request_x11(). +To do that, you first declare a callback to manage channel_open_request_x11_function. +Then you create the forwarding tunnel for the X11 protocol with ssh_channel_request_x11(). The following code performs channel initialization and shell session opening, and handles a parallel X11 connection: @code +#include + +ssh_channel x11channel = NULL; + +ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + x11channel = ssh_channel_new(session); + return x11channel; +} + int interactive_shell_session(ssh_channel channel) { int rc; - ssh_channel x11channel; + + struct ssh_callbacks_struct cb = + { + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL + }; + + ssh_callbacks_init(&cb); + rc = ssh_set_callbacks(session, &cb); + if (rc != SSH_OK) return rc; rc = ssh_channel_request_pty(channel); if (rc != SSH_OK) return rc; @@ -350,12 +368,15 @@ int interactive_shell_session(ssh_channel channel) } @endcode -Don't forget to set the $DISPLAY environment variable on the remote +Don't forget to check the $DISPLAY environment variable on the remote side, or the remote applications won't try using the X11 tunnel: @code -$ export DISPLAY=:0 +$ echo $DISPLAY +localhost:10.0 $ xclock & @endcode +See an implementation example at https://gitlab.com/libssh/libssh-mirror/-/tree/master/examples/ssh_X11_client.c for details. + */ diff --git a/libssh/examples/CMakeLists.txt b/libssh/examples/CMakeLists.txt index 5e0c2ce..466865f 100644 --- a/libssh/examples/CMakeLists.txt +++ b/libssh/examples/CMakeLists.txt @@ -35,18 +35,22 @@ if (UNIX AND NOT WIN32) target_compile_options(ssh-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(ssh-client ssh::ssh) + add_executable(ssh-X11-client ssh_X11_client.c ${examples_SRCS}) + target_compile_options(ssh-X11-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-X11-client ssh::ssh) + if (WITH_SERVER AND (ARGP_LIBRARY OR HAVE_ARGP_H)) if (HAVE_LIBUTIL) - add_executable(ssh_server_fork ssh_server_fork.c) - target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + add_executable(ssh_server_fork ssh_server.c) + target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS} -DWITH_FORK) target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARY} util) + + add_executable(ssh_server_pthread ssh_server.c) + target_compile_options(ssh_server_pthread PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_server_pthread ssh::ssh ${ARGP_LIBRARY} pthread util) endif (HAVE_LIBUTIL) if (WITH_GSSAPI AND GSSAPI_FOUND) - add_executable(samplesshd-cb samplesshd-cb.c) - target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(samplesshd-cb ssh::ssh ${ARGP_LIBRARY}) - add_executable(proxy proxy.c) target_compile_options(proxy PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARY}) @@ -60,9 +64,22 @@ if (UNIX AND NOT WIN32) target_compile_options(samplesshd-kbdint PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(samplesshd-kbdint ssh::ssh ${ARGP_LIBRARY}) + add_executable(keygen2 keygen2.c ${examples_SRCS}) + target_compile_options(keygen2 PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(keygen2 ssh::ssh ${ARGP_LIBRARY}) + endif() endif (UNIX AND NOT WIN32) +if (WITH_SERVER) + add_executable(samplesshd-cb samplesshd-cb.c) + target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-cb ssh::ssh) + if (ARGP_LIBRARY OR HAVE_ARGP_H) + target_link_libraries(samplesshd-cb ${ARGP_LIBRARY}) + endif(ARGP_LIBRARY OR HAVE_ARGP_H) +endif() + add_executable(exec exec.c ${examples_SRCS}) target_compile_options(exec PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(exec ssh::ssh) diff --git a/libssh/examples/exec.c b/libssh/examples/exec.c index 7200dde..77d3be4 100644 --- a/libssh/examples/exec.c +++ b/libssh/examples/exec.c @@ -17,7 +17,7 @@ int main(void) { return 1; } - channel = ssh_channel_new(session);; + channel = ssh_channel_new(session); if (channel == NULL) { ssh_disconnect(session); ssh_free(session); diff --git a/libssh/examples/keygen2.c b/libssh/examples/keygen2.c new file mode 100644 index 0000000..82aae7e --- /dev/null +++ b/libssh/examples/keygen2.c @@ -0,0 +1,505 @@ +/* + * keygen2.c - Generate SSH keys using libssh + * Author: Anderson Toshiyuki Sasaki + */ + +/* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct arguments_st { + enum ssh_keytypes_e type; + unsigned long bits; + char *file; + char *passphrase; + int action_list; +}; + +static struct argp_option options[] = { + { + .name = "bits", + .key = 'b', + .arg = "BITS", + .flags = 0, + .doc = "The size of the key to be generated. " + "If omitted, a default value is used depending on the TYPE. " + "Accepted values are: " + "1024, 2048, 3072 (default), 4096, and 8192 for TYPE=\"rsa\"; " + "256 (default), 384, and 521 for TYPE=\"ecdsa\"; " + "1024 (default) and 2048 for TYPE=\"dsa\"; " + "can be omitted for TYPE=\"ed25519\" " + "(it will be ignored if provided).\n", + .group = 0 + }, + { + .name = "file", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "The output file. " + "If not provided, the used file name will be generated " + "according to the key type as \"id_TYPE\" " + "(e.g. \"id_rsa\" for type \"rsa\"). " + "The public key file name is generated from the private key " + "file name by appending \".pub\".\n", + .group = 0 + }, + { + .name = "passphrase", + .key = 'p', + .arg = "PASSPHRASE", + .flags = 0, + .doc = "The passphrase used to encrypt the private key. " + "If omitted the file will not be encrypted.\n", + .group = 0 + }, + { + .name = "type", + .key = 't', + .arg = "TYPE", + .flags = 0, + .doc = "The type of the key to be generated. " + "Accepted values are: " + "\"rsa\", \"ecdsa\", \"ed25519\", and \"dsa\".\n", + .group = 0 + }, + { + .name = "list", + .key = 'l', + .arg = NULL, + .flags = 0, + .doc = "List the Fingerprint of the given key\n", + .group = 0 + }, + { + /* End of the options */ + 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 arguments_st *arguments = NULL; + error_t rc = 0; + + if (state == NULL) { + return EINVAL; + } + + arguments = state->input; + if (arguments == NULL) { + fprintf(stderr, "Error: NULL pointer to arguments structure " + "provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'b': + errno = 0; + arguments->bits = strtoul(arg, NULL, 10); + if (errno != 0) { + rc = errno; + goto end; + } + break; + case 'f': + arguments->file = strdup(arg); + if (arguments->file == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->passphrase = strdup(arg); + if (arguments->passphrase == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 't': + if (!strcmp(arg, "rsa")) { + arguments->type = SSH_KEYTYPE_RSA; + } + else if (!strcmp(arg, "dsa")) { + arguments->type = SSH_KEYTYPE_DSS; + } + else if (!strcmp(arg, "ecdsa")) { + arguments->type = SSH_KEYTYPE_ECDSA; + } + else if (!strcmp(arg, "ed25519")) { + arguments->type = SSH_KEYTYPE_ED25519; + } + else { + fprintf(stderr, "Error: Invalid key type\n"); + argp_usage(state); + rc = EINVAL; + goto end; + } + break; + case 'l': + arguments->action_list = 1; + break; + case ARGP_KEY_ARG: + if (state->arg_num > 0) { + /* Too many arguments. */ + printf("Error: Too many arguments\n"); + argp_usage(state); + } + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +static int validate_args(struct arguments_st *args) +{ + int rc = 0; + + if (args == NULL) { + return EINVAL; + } + + /* no other arguments needed for listing key fingerprints */ + if (args->action_list) { + return 0; + } + + switch (args->type) { + case SSH_KEYTYPE_RSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 3072; + break; + case 1024: + case 2048: + case 3072: + case 4096: + case 8192: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + + if (args->file == NULL) { + args->file = strdup("id_rsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ECDSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 256; + break; + case 256: + case 384: + case 521: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + if (args->file == NULL) { + args->file = strdup("id_ecdsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_DSS: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 1024; + break; + case 1024: + case 2048: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + if (args->file == NULL) { + args->file = strdup("id_dsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ED25519: + /* Ignore value and overwrite with a zero */ + args->bits = 0; + + if (args->file == NULL) { + args->file = strdup("id_ed25519"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + default: + fprintf(stderr, "Error: unknown key type\n"); + rc = EINVAL; + break; + } + + return rc; +} + +/* Program documentation. */ +static char doc[] = "Generate an SSH key pair. " + "The \"--type\" (short: \"-t\") option is required."; + +/* Our argp parser */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +static void +list_fingerprint(char *file) +{ + ssh_key key = NULL; + unsigned char *hash = NULL; + size_t hlen = 0; + int rc; + + rc = ssh_pki_import_privkey_file(file, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to import private key %s\n", file); + return; + } + + rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to get key fingerprint\n"); + return; + } + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + ssh_clean_pubkey_hash(&hash); + ssh_key_free(key); +} + +int main(int argc, char *argv[]) +{ + ssh_key key = NULL; + int rc = 0; + char overwrite[1024] = ""; + + char *pubkey_file = NULL; + + struct arguments_st arguments = { + .type = SSH_KEYTYPE_UNKNOWN, + .bits = 0, + .file = NULL, + .passphrase = NULL, + .action_list = 0, + }; + + if (argc < 2) { + argp_help(&argp, stdout, ARGP_HELP_DOC | ARGP_HELP_USAGE, argv[0]); + goto end; + } + + rc = argp_parse(&argp, argc, argv, 0, 0, &arguments); + if (rc != 0) { + goto end; + } + + rc = validate_args(&arguments); + if (rc != 0) { + goto end; + } + + if (arguments.action_list && arguments.file) { + list_fingerprint(arguments.file); + goto end; + } + + errno = 0; + rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", arguments.file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(arguments.file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(arguments.file, S_IRUSR | S_IWUSR); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create private key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", arguments.file); + goto end; + } + } else { + close(rc); + } + + /* Generate a new private key */ + rc = ssh_pki_generate(arguments.type, arguments.bits, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to generate keys"); + goto end; + } + + /* Write the private key */ + 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; + } + + /* If a passphrase was provided, overwrite and free it as it is not needed + * anymore */ + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + arguments.passphrase = NULL; + } + + pubkey_file = (char *)malloc(strlen(arguments.file) + 5); + if (pubkey_file == NULL) { + rc = ENOMEM; + goto end; + } + + sprintf(pubkey_file, "%s.pub", arguments.file); + + errno = 0; + rc = open(pubkey_file, + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", pubkey_file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(pubkey_file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(pubkey_file, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create public key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", pubkey_file); + goto end; + } + } else { + close(rc); + } + + /* Write the public key */ + rc = ssh_pki_export_pubkey_file(key, pubkey_file); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to write public key file"); + goto end; + } + +end: + if (key != NULL) { + ssh_key_free(key); + } + + if (arguments.file != NULL) { + free(arguments.file); + } + + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + } + + if (pubkey_file != NULL) { + free(pubkey_file); + } + return rc; +} diff --git a/libssh/examples/libssh_scp.c b/libssh/examples/libssh_scp.c index 7e08642..6fdf8a4 100644 --- a/libssh/examples/libssh_scp.c +++ b/libssh/examples/libssh_scp.c @@ -22,6 +22,10 @@ program. #include #include "examples_common.h" +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + static char **sources; static int nsources; static char *destination; @@ -105,10 +109,6 @@ static void location_free(struct location *loc) free(loc->user); } loc->user = NULL; - if (loc->host) { - free(loc->host); - } - loc->host = NULL; } free(loc); } @@ -261,7 +261,7 @@ static int do_copy(struct location *src, struct location *dest, int recursive) { socket_t fd; struct stat s; int w, r; - char buffer[16384]; + char buffer[BUF_SIZE]; size_t total = 0; mode_t mode; char *filename = NULL; @@ -404,10 +404,11 @@ int main(int argc, char **argv) { int i; int r; if (opts(argc, argv) < 0) { - r = EXIT_FAILURE; - goto end; + return EXIT_FAILURE; } + ssh_init(); + dest = parse_location(destination); if (dest == NULL) { r = EXIT_FAILURE; @@ -449,5 +450,7 @@ int main(int argc, char **argv) { close_location(dest); location_free(dest); end: + ssh_finalize(); + free(sources); return r; } diff --git a/libssh/examples/proxy.c b/libssh/examples/proxy.c index dcf4d0d..159a37e 100644 --- a/libssh/examples/proxy.c +++ b/libssh/examples/proxy.c @@ -25,6 +25,10 @@ clients must be made or how a client should react. #include #include +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + #define USER "myuser" #define PASSWORD "mypassword" @@ -225,7 +229,7 @@ int main(int argc, char **argv){ .channel_open_request_session_function = new_session_channel }; - char buf[2048]; + char buf[BUF_SIZE]; char host[128]=""; char *ptr; int i,r, rc; @@ -291,7 +295,7 @@ int main(int argc, char **argv){ snprintf(buf,sizeof(buf), "Hello %s, welcome to the Sample SSH proxy.\r\nPlease select your destination: ", username); ssh_channel_write(chan, buf, strlen(buf)); do{ - i=ssh_channel_read(chan,buf, 2048, 0); + i=ssh_channel_read(chan,buf, sizeof(buf), 0); if(i>0) { ssh_channel_write(chan, buf, i); if(strlen(host) + i < sizeof(host)){ diff --git a/libssh/examples/samplesftp.c b/libssh/examples/samplesftp.c index a862441..9c8cd34 100644 --- a/libssh/examples/samplesftp.c +++ b/libssh/examples/samplesftp.c @@ -29,11 +29,13 @@ clients must be made or how a client should react. #include "examples_common.h" #ifdef WITH_SFTP +#ifndef BUF_SIZE +#define BUF_SIZE 65536 +#endif + static int verbosity; static char *destination; -#define DATALEN 65536 - static void do_sftp(ssh_session session) { sftp_session sftp = sftp_new(session); sftp_dir dir; @@ -44,7 +46,7 @@ static void do_sftp(ssh_session session) { sftp_file to; int len = 1; unsigned int i; - char data[DATALEN] = {0}; + char data[BUF_SIZE] = {0}; char *lnk; unsigned int count; @@ -223,9 +225,9 @@ static void do_sftp(ssh_session session) { to = sftp_open(sftp, "/tmp/grosfichier", O_WRONLY|O_CREAT, 0644); for (i = 0; i < 1000; ++i) { - len = sftp_write(to, data, DATALEN); + len = sftp_write(to, data, sizeof(data)); printf("wrote %d bytes\n", len); - if (len != DATALEN) { + if (len != sizeof(data)) { printf("chunk %d : %d (%s)\n", i, len, ssh_get_error(session)); } } diff --git a/libssh/examples/samplesshd-cb.c b/libssh/examples/samplesshd-cb.c index f93ab4b..e5b4899 100644 --- a/libssh/examples/samplesshd-cb.c +++ b/libssh/examples/samplesshd-cb.c @@ -25,6 +25,14 @@ clients must be made or how a client should react. #include #include +#ifdef _WIN32 +#include +#endif + +#ifndef BUF_SIZE +#define BUF_SIZE 2049 +#endif + #ifndef KEYS_FOLDER #ifdef _WIN32 #define KEYS_FOLDER @@ -41,6 +49,27 @@ static int tries = 0; static int error = 0; static ssh_channel chan=NULL; +static int auth_none(ssh_session session, + const char *user, + void *userdata) +{ + ssh_string banner = NULL; + + (void)user; /* unused */ + (void)userdata; /* unused */ + + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + + banner = ssh_string_from_char("Banner Example\n"); + if (banner != NULL) { + ssh_send_issue_banner(session, banner); + } + ssh_string_free(banner); + + return SSH_AUTH_DENIED; +} + static int auth_password(ssh_session session, const char *user, const char *password, void *userdata){ (void)userdata; @@ -60,6 +89,7 @@ static int auth_password(ssh_session session, const char *user, return SSH_AUTH_DENIED; } +#ifdef WITH_GSSAPI static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); (void)userdata; @@ -72,6 +102,7 @@ static int auth_gssapi_mic(ssh_session session, const char *user, const char *pr authenticated = 1; return SSH_AUTH_SUCCESS; } +#endif static int pty_request(ssh_session session, ssh_channel channel, const char *term, int x,int y, int px, int py, void *userdata){ @@ -165,6 +196,14 @@ static struct argp_option options[] = { .doc = "Get verbose output.", .group = 0 }, + { + .name = "config", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Configuration file to use.", + .group = 0 + }, {NULL, 0, NULL, 0, NULL, 0} }; @@ -191,6 +230,9 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { case 'v': ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); break; + case 'f': + ssh_bind_options_parse_config(sshbind, arg); + break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { /* Too many arguments. */ @@ -221,12 +263,15 @@ int main(int argc, char **argv){ ssh_event mainloop; struct ssh_server_callbacks_struct cb = { .userdata = NULL, + .auth_none_function = auth_none, .auth_password_function = auth_password, +#ifdef WITH_GSSAPI .auth_gssapi_mic_function = auth_gssapi_mic, +#endif .channel_open_request_session_function = new_session_channel }; - char buf[2048]; + char buf[BUF_SIZE]; int i; int r; @@ -282,19 +327,24 @@ int main(int argc, char **argv){ } else printf("Authenticated and got a channel\n"); do{ - i=ssh_channel_read(chan,buf, 2048, 0); + i=ssh_channel_read(chan, buf, sizeof(buf) - 1, 0); if(i>0) { - ssh_channel_write(chan, buf, i); - if (write(1,buf,i) < 0) { - printf("error writing to buffer\n"); + if (ssh_channel_write(chan, buf, i) == SSH_ERROR) { + printf("error writing to channel\n"); return 1; } + + buf[i] = '\0'; + printf("%s", buf); + fflush(stdout); + if (buf[0] == '\x0d') { - if (write(1, "\n", 1) < 0) { - printf("error writing to buffer\n"); + if (ssh_channel_write(chan, "\n", 1) == SSH_ERROR) { + printf("error writing to channel\n"); return 1; } - ssh_channel_write(chan, "\n", 1); + + printf("\n"); } } } while (i>0); diff --git a/libssh/examples/samplesshd-kbdint.c b/libssh/examples/samplesshd-kbdint.c index 522aebd..9b09cf2 100644 --- a/libssh/examples/samplesshd-kbdint.c +++ b/libssh/examples/samplesshd-kbdint.c @@ -25,6 +25,10 @@ clients must be made or how a client should react. #include #include +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + #define SSHD_USER "libssh" #define SSHD_PASSWORD "libssh" @@ -293,7 +297,7 @@ int main(int argc, char **argv){ ssh_bind sshbind; ssh_message message; ssh_channel chan=0; - char buf[2048]; + char buf[BUF_SIZE]; int auth=0; int shell=0; int i; @@ -399,7 +403,7 @@ int main(int argc, char **argv){ printf("it works !\n"); do{ - i=ssh_channel_read(chan,buf, 2048, 0); + i=ssh_channel_read(chan,buf, sizeof(buf), 0); if(i>0) { if(*buf == '' || *buf == '') break; diff --git a/libssh/examples/scp_download.c b/libssh/examples/scp_download.c index 85ccf92..e6c1e79 100644 --- a/libssh/examples/scp_download.c +++ b/libssh/examples/scp_download.c @@ -22,6 +22,10 @@ program. #include #include "examples_common.h" +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + static int verbosity = 0; static const char *createcommand = "rm -fr /tmp/libssh_tests && mkdir /tmp/libssh_tests && " @@ -102,7 +106,7 @@ static void create_files(ssh_session session){ static int fetch_files(ssh_session session){ int size; - char buffer[16384]; + char buffer[BUF_SIZE]; int mode; char *filename; int r; diff --git a/libssh/examples/senddata.c b/libssh/examples/senddata.c index 042b462..21181fb 100644 --- a/libssh/examples/senddata.c +++ b/libssh/examples/senddata.c @@ -17,7 +17,7 @@ int main(void) { return 1; } - channel = ssh_channel_new(session);; + channel = ssh_channel_new(session); if (channel == NULL) { ssh_disconnect(session); return 1; diff --git a/libssh/examples/ssh_X11_client.c b/libssh/examples/ssh_X11_client.c new file mode 100644 index 0000000..369b9b4 --- /dev/null +++ b/libssh/examples/ssh_X11_client.c @@ -0,0 +1,866 @@ +/* + * ssh.c - Simple example of SSH X11 client using libssh + * + * Copyright (C) 2022 Marco Fortina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + * + * + * ssh_X11_client + * ============== + * + * AUTHOR URL + * https://gitlab.com/marco.fortina/libssh-x11-client/ + * + * This is a simple example of SSH X11 client using libssh. + * + * Features: + * + * - support local display (e.g. :0) + * - support remote display (e.g. localhost:10.0) + * - using callbacks and event polling to significantly reduce CPU utilization + * - use X11 forwarding with authentication spoofing (like openssh) + * + * Note: + * + * - part of this code was inspired by openssh's one. + * + * Dependencies: + * + * - gcc >= 7.5.0 + * - libssh >= 0.8.0 + * - libssh-dev >= 0.8.0 + * + * To Build: + * gcc -o ssh_X11_client ssh_X11_client.c -lssh -g + * + * Donations: + * + * If you liked this work and wish to support the developer please donate to: + * Bitcoin: 1N2rQimKbeUQA8N2LU5vGopYQJmZsBM2d6 + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* + * Data Structures and Macros +*/ + +#define _PATH_UNIX_X "/tmp/.X11-unix/X%d" +#define _XAUTH_CMD "/usr/bin/xauth list %s 2>/dev/null" + +typedef struct item { + ssh_channel channel; + int fd_in; + int fd_out; + int protected; + struct item *next; +} node_t; + +node_t *node = NULL; + + +/* + * Mutex +*/ + +pthread_mutex_t mutex; + + +/* + * Function declarations +*/ + +/* Linked nodes to manage channel/fd tuples */ +static void insert_item(ssh_channel channel, int fd_in, int fd_out, int protected); +static void delete_item(ssh_channel channel); +static node_t * search_item(ssh_channel channel); + +/* X11 Display */ +const char * ssh_gai_strerror(int gaierr); +static int x11_get_proto(const char *display, char **_proto, char **_data); +static void set_nodelay(int fd); +static int connect_local_xsocket_path(const char *pathname); +static int connect_local_xsocket(int display_number); +static int x11_connect_display(void); + +/* Send data to channel */ +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata); + +/* Read data from channel */ +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *userdata); + +/* EOF&Close channel */ +static void channel_close_callback(ssh_session session, ssh_channel channel, void *userdata); + +/* X11 Request */ +static ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata); + +/* Main loop */ +static int main_loop(ssh_channel channel); + +/* Internals */ +int64_t _current_timestamp(void); + +/* Global variables */ +const char *hostname = NULL; +int enableX11 = 1; + +/* + * Callbacks Data Structures +*/ + +/* SSH Channel Callbacks */ +struct ssh_channel_callbacks_struct channel_cb = +{ + .channel_data_function = copy_channel_to_fd_callback, + .channel_eof_function = channel_close_callback, + .channel_close_function = channel_close_callback, + .userdata = NULL +}; + +/* SSH Callbacks */ +struct ssh_callbacks_struct cb = +{ + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL +}; + + +/* + * SSH Event Context +*/ + +short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; +ssh_event event; + + +/* + * Internal data structures +*/ + +struct termios _saved_tio; + + +/* + * Internal functions +*/ + +int64_t +_current_timestamp(void) { + struct timeval tv; + int64_t milliseconds; + + gettimeofday(&tv, NULL); + milliseconds = (int64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); + + return milliseconds; +} + +static void +_logging_callback(int priority, const char *function, const char *buffer, void *userdata) +{ + FILE *fp = NULL; + char buf[100]; + int64_t milliseconds; + + time_t now = time (0); + + (void)userdata; + + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime (&now)); + + fp = fopen("debug.log","a"); + if(fp == NULL) + { + printf("Error!"); + exit(-11); + } + + milliseconds = _current_timestamp(); + + fprintf(fp, "[%s.%jd, %d] %s: %s\n", buf, milliseconds, priority, function, buffer); + fclose(fp); +} + +static int +_enter_term_raw_mode(void) +{ + struct termios tio; + int ret = tcgetattr(fileno(stdin), &tio); + if (ret != -1) { + _saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + ret = tcsetattr(fileno(stdin), TCSADRAIN, &tio); + } + return ret; +} + +static int +_leave_term_raw_mode(void) +{ + int ret = tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio); + return ret; +} + + +/* + * Functions +*/ + +static void +insert_item(ssh_channel channel, int fd_in, int fd_out, int protected) +{ + node_t *node_iterator = NULL, *new = NULL; + + pthread_mutex_lock(&mutex); + + if (node == NULL) { + /* Calloc ensure that node is full of 0 */ + node = (node_t *) calloc(1, sizeof(node_t)); + node->channel = channel; + node->fd_in = fd_in; + node->fd_out = fd_out; + node->protected = protected; + node->next = NULL; + } else { + node_iterator = node; + while (node_iterator->next != NULL) + node_iterator = node_iterator->next; + /* Create the new node */ + new = (node_t *) malloc(sizeof(node_t)); + new->channel = channel; + new->fd_in = fd_in; + new->fd_out = fd_out; + new->protected = protected; + new->next = NULL; + node_iterator->next = new; + + } + + pthread_mutex_unlock(&mutex); +} + + +static void +delete_item(ssh_channel channel) +{ + node_t *current = NULL, *previous = NULL; + + pthread_mutex_lock(&mutex); + + for (current = node; current; previous = current, current = current->next) { + if (current->channel != channel) + continue; + + if (previous == NULL) + node = current->next; + else + previous->next = current->next; + + free(current); + pthread_mutex_unlock(&mutex); + return; + } + + pthread_mutex_unlock(&mutex); +} + + +static node_t * +search_item(ssh_channel channel) +{ + node_t *current = node; + + pthread_mutex_lock(&mutex); + + while (current != NULL) { + if (current->channel == channel) { + pthread_mutex_unlock(&mutex); + return current; + } else { + current = current->next; + } + } + + pthread_mutex_unlock(&mutex); + + return NULL; +} + + + +static void +set_nodelay(int fd) +{ + int opt; + socklen_t optlen; + + optlen = sizeof(opt); + if (getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "getsockopt TCP_NODELAY: %.100s", strerror(errno)); + return; + } + if (opt == 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d is TCP_NODELAY", fd); + return; + } + opt = 1; + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d setting TCP_NODELAY", fd); + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1) + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "setsockopt TCP_NODELAY: %.100s", strerror(errno)); +} + + +const char * +ssh_gai_strerror(int gaierr) +{ + if (gaierr == EAI_SYSTEM && errno != 0) + return strerror(errno); + return gai_strerror(gaierr); +} + + + +static int +x11_get_proto(const char *display, char **_proto, char **_cookie) +{ + char cmd[1024], line[512], xdisplay[512]; + static char proto[512], cookie[512]; + FILE *f = NULL; + int ret = 0; + + *_proto = proto; + *_cookie = cookie; + + proto[0] = cookie[0] = '\0'; + + if (strncmp(display, "localhost:", 10) == 0) { + if ((ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10)) < 0 || (size_t)ret >= sizeof(xdisplay)) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display name too long. display: %s", display); + return -1; + } + display = xdisplay; + } + + snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "xauth cmd: %s", cmd); + + f = popen(cmd, "r"); + if (f && fgets(line, sizeof(line), f) && sscanf(line, "%*s %511s %511s", proto, cookie) == 2) { + ret = 0; + } else { + ret = 1; + } + + if (f) pclose(f); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s - ret: %d", proto, cookie, ret); + + return ret; +} + +static int +connect_local_xsocket_path(const char *pathname) +{ + int sock; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + /* pathname is guaranteed to be initialized and larger than addr.sun_path[108] */ + memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1); + if (connect(sock, (struct sockaddr *)&addr, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname)) == 0) + return sock; + close(sock); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s: %.100s", addr.sun_path, strerror(errno)); + return -1; +} + + +static int +connect_local_xsocket(int display_number) +{ + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number); + return connect_local_xsocket_path(buf); +} + + +static int +x11_connect_display() +{ + int display_number; + const char *display = NULL; + char buf[1024], *cp = NULL; + struct addrinfo hints, *ai = NULL, *aitop = NULL; + char strport[NI_MAXSERV]; + int gaierr = 0, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (!display) { + return -1; + } + + /* Check if it is a unix domain socket. */ + if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { + /* Connect to the unix domain socket. */ + if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Could not parse display number from DISPLAY: %.100s", display); + return -1; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display_number: %d", display_number); + + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %d", sock); + + if (sock < 0) + return -1; + + /* OK, we now have a connection to the display. */ + return sock; + } + + /* Connect to an inet socket. */ + strncpy(buf, display, sizeof(buf) - 1); + cp = strchr(buf, ':'); + if (!cp) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + if (sscanf(cp + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Could not parse display number from DISPLAY: %.100s", display); + return -1; + } + + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof(strport), "%u", 6000 + display_number); + if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "%.100s: unknown host. (%s)", buf, ssh_gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (!ai) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", buf, 6000 + display_number, strerror(errno)); + return -1; + } + set_nodelay(sock); + return sock; +} + + + +static int +copy_fd_to_channel_callback(int fd, int revents, void *userdata) +{ + ssh_channel channel = (ssh_channel)userdata; + char buf[2097152]; + int sz = 0, ret = 0; + + node_t *temp_node = search_item(channel); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "event: %d - fd: %d", revents, fd); + + if (!channel) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel does not exist."); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + + if (fcntl(fd, F_GETFD) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fcntl error. fd: %d", fd); + ssh_channel_close(channel); + return -1; + } + + if ((revents & POLLIN) || (revents & POLLPRI)) { + sz = read(fd, buf, sizeof(buf)); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sz: %d", sz); + if (sz > 0) { + ret = ssh_channel_write(channel, buf, sz); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel_write ret: %d", ret); + } else if (sz < 0) { + ssh_channel_close(channel); + return -1; + } else { + /* sz = 0. Why the hell I'm here? */ + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Why the hell am I here?: sz: %d", sz); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + } + + if ((revents & POLLHUP) || (revents & POLLNVAL) || (revents & POLLERR)) { + ssh_channel_close(channel); + return -1; + } + + return sz; +} + + +static int +copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, void *data, uint32_t len, int is_stderr, void *userdata) +{ + node_t *temp_node = NULL; + int fd, sz; + + (void)session; + (void)is_stderr; + (void)userdata; + + temp_node = search_item(channel); + + fd = temp_node->fd_out; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "len: %d - fd: %d - is_stderr: %d", len, fd, is_stderr); + + sz = write(fd, data, len); + + return sz; +} + + +static void +channel_close_callback(ssh_session session, ssh_channel channel, void *userdata) +{ + node_t *temp_node = NULL; + + (void)session; + (void)userdata; + + temp_node = search_item(channel); + + if (temp_node != NULL) { + int fd = temp_node->fd_in; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd: %d", fd); + + delete_item(channel); + ssh_event_remove_fd(event, fd); + + if (temp_node->protected == 0) { + close(fd); + } + } +} + + +static ssh_channel +x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + ssh_channel channel = NULL; + int sock; + + (void)shost; + (void)sport; + (void)userdata; + + channel = ssh_channel_new(session); + + sock = x11_connect_display(); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock); + + insert_item(channel, sock, sock, 0); + + ssh_event_add_fd(event, sock, events, copy_fd_to_channel_callback, channel); + ssh_event_add_session(event, session); + + ssh_add_channel_callbacks(channel, &channel_cb); + + return channel; +} + + + +/* + * MAIN LOOP +*/ + +static int +main_loop(ssh_channel channel) +{ + ssh_session session = ssh_channel_get_session(channel); + + insert_item(channel, fileno(stdin), fileno(stdout), 1); + + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(channel, &channel_cb); + + event = ssh_event_new(); + if (event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + + if (ssh_event_add_fd(event, fileno(stdin), events, copy_fd_to_channel_callback, channel) != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + + if(ssh_event_add_session(event, session) != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + if (ssh_event_dopoll(event, 1000) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + /* fall through */ + } + } while (!ssh_channel_is_closed(channel)); + + delete_item(channel); + ssh_event_remove_fd(event, fileno(stdin)); + ssh_event_remove_session(event, session); + ssh_event_free(event); + + return 0; +} + + +/* + * USAGE + */ + +static void +usage(void) +{ + fprintf(stderr, + "Usage : ssh-X11-client [options] [login@]hostname\n" + "sample X11 client - libssh-%s\n" + "Options :\n" + " -l user : Specifies the user to log in as on the remote machine.\n" + " -p port : Port to connect to on the remote host.\n" + " -v : Verbose mode. Multiple -v options increase the verbosity. The maximum is 5.\n" + " -C : Requests compression of all data.\n" + " -x : Disables X11 forwarding.\n" + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc,argv,"x")) != -1) { + switch(i) { + case 'x': + enableX11 = 0; + break; + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + + if (optind < argc) { + hostname = argv[optind++]; + } + + if (hostname == NULL) { + return -1; + } + + return 0; +} + +/* + * MAIN +*/ + +int +main(int argc, char **argv) +{ + char *password = NULL; + + ssh_session session = NULL; + ssh_channel channel = NULL; + + int ret; + + const char *display = NULL; + char *proto = NULL, *cookie = NULL; + + ssh_set_log_callback(_logging_callback); + ret = ssh_init(); + if (ret != SSH_OK) return ret; + + session = ssh_new(); + if (session == NULL) exit(-1); + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, "Error parsing command line: %s\n", ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, hostname) < 0) { + return -1; + } + + ret = ssh_connect(session); + if (ret != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + exit(-1); + } + + password = getpass("Password: "); + ret = ssh_userauth_password(session, NULL, password); + if (ret != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Error authenticating with password: %s\n", ssh_get_error(session)); + exit(-1); + } + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + ret = ssh_channel_open_session(channel); + if (ret != SSH_OK) return ret; + + ret = ssh_channel_request_pty(channel); + if (ret != SSH_OK) return ret; + + ret = ssh_channel_change_pty_size(channel, 80, 24); + if (ret != SSH_OK) return ret; + + if (enableX11 == 1) { + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display) { + ssh_callbacks_init(&cb); + ret = ssh_set_callbacks(session, &cb); + if (ret != SSH_OK) return ret; + + if (x11_get_proto(display, &proto, &cookie) != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "Using fake authentication data for X11 forwarding"); + proto = NULL; + cookie = NULL; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s", proto, cookie); + /* See https://gitlab.com/libssh/libssh-mirror/-/blob/master/src/channels.c#L2062 for details. */ + ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0); + if (ret != SSH_OK) return ret; + } + } + + ret = _enter_term_raw_mode(); + if (ret != 0) exit(-1); + + ret = ssh_channel_request_shell(channel); + if (ret != SSH_OK) return ret; + + ret = main_loop(channel); + if (ret != SSH_OK) return ret; + + _leave_term_raw_mode(); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); +} diff --git a/libssh/examples/ssh_client.c b/libssh/examples/ssh_client.c index 54ecd30..aaf0cb5 100644 --- a/libssh/examples/ssh_client.c +++ b/libssh/examples/ssh_client.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,10 @@ #include "examples_common.h" #define MAXCMD 10 -static char *host; -static char *user; +static char *host = NULL; +static char *user = NULL; static char *cmds[MAXCMD]; +static char *config_file = NULL; static struct termios terminal; static char *pcap_file = NULL; @@ -81,7 +83,7 @@ static void add_cmd(char *cmd) return; } - cmds[n] = strdup(cmd); + cmds[n] = cmd; } static void usage(void) @@ -94,6 +96,7 @@ static void usage(void) " -p port : connect to port\n" " -d : use DSS to verify host public key\n" " -r : use RSA to verify host public key\n" + " -F file : parse configuration file instead of default one\n" #ifdef WITH_PCAP " -P file : create a pcap debugging file\n" #endif @@ -110,11 +113,14 @@ static int opts(int argc, char **argv) { int i; - while((i = getopt(argc,argv,"T:P:")) != -1) { + while((i = getopt(argc,argv,"T:P:F:")) != -1) { switch(i){ case 'P': pcap_file = optarg; break; + case 'F': + config_file = optarg; + break; #ifndef _WIN32 case 'T': proxycommand = optarg; @@ -122,7 +128,7 @@ static int opts(int argc, char **argv) #endif default: fprintf(stderr, "Unknown option %c\n", optopt); - usage(); + return -1; } } if (optind < argc) { @@ -134,7 +140,7 @@ static int opts(int argc, char **argv) } if (host == NULL) { - usage(); + return -1; } return 0; @@ -169,22 +175,25 @@ static void do_exit(int i) exit(0); } -static ssh_channel chan; static int signal_delayed = 0; +#ifdef SIGWINCH static void sigwindowchanged(int i) { (void) i; signal_delayed = 1; } +#endif static void setsignal(void) { +#ifdef SIGWINCH signal(SIGWINCH, sigwindowchanged); +#endif signal_delayed = 0; } -static void sizechanged(void) +static void sizechanged(ssh_channel chan) { struct winsize win = { .ws_row = 0, @@ -222,7 +231,7 @@ static void select_loop(ssh_session session,ssh_channel channel) while (ssh_channel_is_open(channel)) { if (signal_delayed) { - sizechanged(); + sizechanged(channel); } rc = ssh_event_dopoll(event, 60000); if (rc == SSH_ERROR) { @@ -262,10 +271,9 @@ static void shell(ssh_session session) ssh_channel_free(channel); return; } - chan = channel; if (interactive) { ssh_channel_request_pty(channel); - sizechanged(); + sizechanged(channel); } if (ssh_channel_request_shell(channel)) { @@ -290,14 +298,12 @@ static void shell(ssh_session session) static void batch_shell(ssh_session session) { ssh_channel channel; - char buffer[1024]; + char buffer[PATH_MAX]; size_t i; int s = 0; for (i = 0; i < MAXCMD && cmds[i]; ++i) { s += snprintf(buffer + s, sizeof(buffer) - s, "%s ", cmds[i]); - free(cmds[i]); - cmds[i] = NULL; } channel = ssh_channel_new(session); @@ -326,7 +332,7 @@ static int client(ssh_session session) return -1; } } - if (ssh_options_set(session, SSH_OPTIONS_HOST ,host) < 0) { + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { return -1; } if (proxycommand != NULL) { @@ -334,7 +340,13 @@ static int client(ssh_session session) return -1; } } - ssh_options_parse_config(session, NULL); + /* 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_connect(session)) { fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); @@ -398,18 +410,20 @@ int main(int argc, char **argv) { ssh_session session; + ssh_init(); session = ssh_new(); ssh_callbacks_init(&cb); ssh_set_callbacks(session,&cb); - if (ssh_options_getopt(session, &argc, argv)) { + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { fprintf(stderr, "Error parsing command line: %s\n", ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); usage(); } - opts(argc, argv); signal(SIGTERM, do_exit); set_pcap(session); diff --git a/libssh/examples/ssh_server_fork.c b/libssh/examples/ssh_server.c similarity index 81% rename from libssh/examples/ssh_server_fork.c rename to libssh/examples/ssh_server.c index c9892d8..85eb168 100644 --- a/libssh/examples/ssh_server_fork.c +++ b/libssh/examples/ssh_server.c @@ -24,6 +24,7 @@ The goal is to show the API in action. #ifdef HAVE_LIBUTIL_H #include #endif +#include #ifdef HAVE_PTY_H #include #endif @@ -40,6 +41,10 @@ The goal is to show the API in action. #include #include +#ifndef BUF_SIZE +#define BUF_SIZE 1048576 +#endif + #ifndef KEYS_FOLDER #ifdef _WIN32 #define KEYS_FOLDER @@ -48,9 +53,6 @@ The goal is to show the API in action. #endif #endif -#define USER "myuser" -#define PASS "mypassword" -#define BUF_SIZE 1048576 #define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) #define SFTP_SERVER_PATH "/usr/lib/sftp-server" @@ -75,6 +77,8 @@ static void set_default_keys(ssh_bind sshbind, } #define DEF_STR_SIZE 1024 char authorizedkeys[DEF_STR_SIZE] = {0}; +char username[128] = "myuser"; +char password[128] = "mypassword"; #ifdef HAVE_ARGP_H const char *argp_program_version = "libssh server example " SSH_STRINGIFY(LIBSSH_VERSION); @@ -137,6 +141,22 @@ static struct argp_option options[] = { .doc = "Set the authorized keys file.", .group = 0 }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "pass", + .key = 'P', + .arg = "PASSWORD", + .flags = 0, + .doc = "Set expected password.", + .group = 0 + }, { .name = "no-default-keys", .key = 'n', @@ -193,6 +213,12 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { 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"); @@ -226,6 +252,89 @@ 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 dsa_already_set = 0; + int ecdsa_already_set = 0; + int key; + + while((key = getopt(argc, argv, "a:d:e:k:np:P:r:u:v")) != -1) { + if (key == 'n') { + no_default_keys = 1; + } else if (key == 'p') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg); + } else if (key == 'd') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_DSAKEY, optarg); + dsa_already_set = 1; + } else if (key == 'k') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + /* We can't track the types of keys being added with this + 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_RSAKEY, optarg); + rsa_already_set = 1; + } else if (key == 'e') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_ECDSAKEY, optarg); + ecdsa_already_set = 1; + } else if (key == 'a') { + strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1); + } else if (key == 'u') { + strncpy(username, optarg, sizeof(username) - 1); + } else if (key == 'P') { + strncpy(password, optarg, sizeof(password) - 1); + } else if (key == 'v') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + "3"); + } else { + break; + } + } + + if (key != -1) { + printf("Usage: %s [OPTION...] BINDADDR\n" + "libssh %s -- a Secure Shell protocol implementation\n" + "\n" + " -a, --authorizedkeys=FILE Set the authorized keys file.\n" + " -d, --dsakey=FILE Set the dsa key.\n" + " -e, --ecdsakey=FILE Set the ecdsa key.\n" + " -k, --hostkey=FILE Set a host key. Can be used multiple times.\n" + " Implies no default keys.\n" + " -n, --no-default-keys Do not set default key locations.\n" + " -p, --port=PORT Set the port to bind.\n" + " -P, --pass=PASSWORD Set expected password.\n" + " -r, --rsakey=FILE Set the rsa key.\n" + " -u, --user=USERNAME Set expected username.\n" + " -v, --verbose Get verbose output.\n" + " -?, --help Give this help list\n" + "\n" + "Mandatory or optional arguments to long options are also mandatory or optional\n" + "for any corresponding short options.\n" + "\n" + "Report bugs to .\n", + argv[0], SSH_STRINGIFY(LIBSSH_VERSION)); + return -1; + } + + if (optind != argc - 1) { + printf("Usage: %s [OPTION...] BINDADDR\n", argv[0]); + return -1; + } + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, argv[optind]); + + if (!no_default_keys) { + set_default_keys(sshbind, + rsa_already_set, + dsa_already_set, + ecdsa_already_set); + } + + return 0; +} #endif /* HAVE_ARGP_H */ /* A userdata struct for channel. */ @@ -440,7 +549,7 @@ static int auth_password(ssh_session session, const char *user, (void) session; - if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) { + if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) { sdata->authenticated = 1; return SSH_AUTH_SUCCESS; } @@ -678,18 +787,38 @@ 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; while (waitpid(-1, NULL, WNOHANG) > 0); } +#else +static void *session_thread(void *arg) { + ssh_session session = arg; + ssh_event event; + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child thread 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); + return NULL; +} +#endif int main(int argc, char **argv) { ssh_bind sshbind; ssh_session session; - ssh_event event; - struct sigaction sa; int rc; +#ifdef WITH_FORK + struct sigaction sa; /* Set up SIGCHLD handler. */ sa.sa_handler = sigchld_handler; @@ -699,6 +828,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Failed to register SIGCHLD handler\n"); return 1; } +#endif rc = ssh_init(); if (rc < 0) { @@ -709,20 +839,24 @@ int main(int argc, char **argv) { sshbind = ssh_bind_new(); if (sshbind == NULL) { fprintf(stderr, "ssh_bind_new failed\n"); + ssh_finalize(); return 1; } #ifdef HAVE_ARGP_H argp_parse(&argp, argc, argv, 0, 0, sshbind); #else - (void) argc; - (void) argv; - - set_default_keys(sshbind, 0, 0, 0); + if (parse_opt(argc, argv, sshbind) < 0) { + ssh_bind_free(sshbind); + ssh_finalize(); + return 1; + } #endif /* HAVE_ARGP_H */ if(ssh_bind_listen(sshbind) < 0) { fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + ssh_bind_free(sshbind); + ssh_finalize(); return 1; } @@ -735,6 +869,9 @@ int main(int argc, char **argv) { /* Blocks until there is a new incoming connection. */ if(ssh_bind_accept(sshbind, session) != SSH_ERROR) { +#ifdef WITH_FORK + ssh_event event; + switch(fork()) { case 0: /* Remove the SIGCHLD handler inherited from parent. */ @@ -760,6 +897,16 @@ int main(int argc, char **argv) { case -1: fprintf(stderr, "Failed to fork\n"); } +#else + pthread_t tid; + + rc = pthread_create(&tid, NULL, session_thread, session); + if (rc == 0) { + pthread_detach(tid); + continue; + } + fprintf(stderr, "Failed to pthread_create\n"); +#endif } else { fprintf(stderr, "%s\n", ssh_get_error(sshbind)); } diff --git a/libssh/examples/sshd_direct-tcpip.c b/libssh/examples/sshd_direct-tcpip.c index e8d5a6c..18a870b 100644 --- a/libssh/examples/sshd_direct-tcpip.c +++ b/libssh/examples/sshd_direct-tcpip.c @@ -35,6 +35,10 @@ clients must be made or how a client should react. #include #include +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) #ifndef __unused__ @@ -88,7 +92,7 @@ cleanup_push(struct cleanup_node_struct** head_ref, // Allocate memory for node struct cleanup_node_struct *new_node = malloc(sizeof *new_node); - if (head_ref != NULL) { + if (*head_ref != NULL) { new_node->next = *head_ref; } else { new_node->next = NULL; @@ -353,7 +357,7 @@ my_fd_data_function(UNUSED_PARAM(socket_t fd), ssh_channel channel = event_fd_data->channel; ssh_session session; int len, i, wr; - char buf[16384]; + char buf[BUF_SIZE]; int blocking; if (channel == NULL) { @@ -418,7 +422,7 @@ my_fd_data_function(UNUSED_PARAM(socket_t fd), break; } wr += i; - _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel_write (%d from %d)", wr, len); + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "ssh_channel_write (%d from %d)", wr, len); } while (i > 0 && wr < len); } else { @@ -522,6 +526,7 @@ message_callback(UNUSED_PARAM(ssh_session session), SAFE_FREE(pFd); SAFE_FREE(cb_chan); SAFE_FREE(event_fd_data); + close(socket_fd); return 1; } diff --git a/libssh/examples/sshnetcat.c b/libssh/examples/sshnetcat.c index ccc72c8..9bc5d52 100644 --- a/libssh/examples/sshnetcat.c +++ b/libssh/examples/sshnetcat.c @@ -34,6 +34,11 @@ clients must be made or how a client should react. #include #include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 4096 +#endif + char *host; const char *desthost="localhost"; const char *port="22"; @@ -77,7 +82,7 @@ static int opts(int argc, char **argv){ static void select_loop(ssh_session session,ssh_channel channel){ fd_set fds; struct timeval timeout; - char buffer[4096]; + char buffer[BUF_SIZE]; /* channels will be set to the channels to poll. * outchannels will contain the result of the poll */ diff --git a/libssh/include/libssh/agent.h b/libssh/include/libssh/agent.h index d4eefbb..7205215 100644 --- a/libssh/include/libssh/agent.h +++ b/libssh/include/libssh/agent.h @@ -77,7 +77,6 @@ struct ssh_agent_struct { ssh_channel channel; }; -#ifndef _WIN32 /* agent.c */ /** * @brief Create a new ssh agent structure. @@ -115,6 +114,5 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, ssh_string ssh_agent_sign_data(ssh_session session, const ssh_key pubkey, struct ssh_buffer_struct *data); -#endif #endif /* __AGENT_H */ diff --git a/libssh/include/libssh/bind.h b/libssh/include/libssh/bind.h index 6b5f19d..c0439d2 100644 --- a/libssh/include/libssh/bind.h +++ b/libssh/include/libssh/bind.h @@ -50,6 +50,8 @@ struct ssh_bind_struct { bool config_processed; char *config_dir; char *pubkey_accepted_key_types; + char* moduli_file; + int rsa_min_size; }; struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct diff --git a/libssh/include/libssh/bind_config.h b/libssh/include/libssh/bind_config.h index cb68da8..7ee19b8 100644 --- a/libssh/include/libssh/bind_config.h +++ b/libssh/include/libssh/bind_config.h @@ -61,4 +61,14 @@ enum ssh_bind_config_opcode_e { */ int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input); + #endif /* BIND_CONFIG_H_ */ diff --git a/libssh/include/libssh/buffer.h b/libssh/include/libssh/buffer.h index cd2dea6..a55a1b4 100644 --- a/libssh/include/libssh/buffer.h +++ b/libssh/include/libssh/buffer.h @@ -63,9 +63,9 @@ int ssh_buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); int ssh_buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); /* buffer_read_*() returns the number of bytes read, except for ssh strings */ -int ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); -int ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); -int ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); +uint32_t ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +uint32_t ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +uint32_t ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); /* ssh_buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); diff --git a/libssh/include/libssh/callbacks.h b/libssh/include/libssh/callbacks.h index 15e8801..36fe7f8 100644 --- a/libssh/include/libssh/callbacks.h +++ b/libssh/include/libssh/callbacks.h @@ -56,7 +56,7 @@ typedef void (*ssh_callback_int) (int code, void *user); * @returns number of bytes processed by the callee. The remaining bytes will * be sent in the next callback message, when more data is available. */ -typedef int (*ssh_callback_data) (const void *data, size_t len, void *user); +typedef size_t (*ssh_callback_data) (const void *data, size_t len, void *user); typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); @@ -221,8 +221,8 @@ typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *us * @param user User that wants to authenticate * @param pubkey public key used for authentication * @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe), - * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be - * replied with a SSH_AUTH_DENIED. + * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be + * replied with a SSH_AUTH_DENIED. * @param userdata Userdata to be passed to the callback function. * @returns SSH_AUTH_SUCCESS Authentication is accepted. * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. @@ -272,7 +272,7 @@ typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const * @param session current session handler * @param[in] input_token input token provided by client * @param[out] output_token output of the gssapi accept_sec_context method, - * NULL after completion. + * NULL after completion. * @returns SSH_OK if the token was generated correctly or accept_sec_context * returned GSS_S_COMPLETE * @returns SSH_ERROR in case of error @@ -354,6 +354,9 @@ typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; * This functions sets the callback structure to use your own callback * functions for user authentication, new channels and requests. * + * Note, that the structure is not copied to the session structure so it needs + * to be valid for the whole session lifetime. + * * @code * struct ssh_server_callbacks_struct cb = { * .userdata = data, @@ -548,6 +551,9 @@ typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; * This functions sets the callback structure to use your own callback * functions for auth, logging and status. * + * Note, that the callback structure is not copied into the session so it needs + * to be valid for the whole session lifetime. + * * @code * struct ssh_callbacks_struct cb = { * .userdata = data, @@ -771,7 +777,7 @@ typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, */ typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata); struct ssh_channel_callbacks_struct { @@ -849,7 +855,11 @@ typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; * @brief Set the channel callback functions. * * This functions sets the callback structure to use your own callback - * functions for channel data and exceptions + * functions for channel data and exceptions. + * + * Note, that the structure is not copied to the channel structure so it needs + * to be valid as for the whole life of the channel or until it is removed with + * ssh_remove_channel_callbacks(). * * @code * struct ssh_channel_callbacks_struct cb = { diff --git a/libssh/include/libssh/chacha.h b/libssh/include/libssh/chacha.h index bac78c6..867f532 100644 --- a/libssh/include/libssh/chacha.h +++ b/libssh/include/libssh/chacha.h @@ -17,7 +17,6 @@ struct chacha_ctx { #define CHACHA_NONCELEN 8 #define CHACHA_CTRLEN 8 #define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) -#define CHACHA_BLOCKLEN 64 void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits) #ifdef HAVE_GCC_BOUNDED_ATTRIBUTE diff --git a/libssh/include/libssh/chacha20-poly1305-common.h b/libssh/include/libssh/chacha20-poly1305-common.h new file mode 100644 index 0000000..b2f0231 --- /dev/null +++ b/libssh/include/libssh/chacha20-poly1305-common.h @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 Red Hat, Inc. + * + * Author: 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; 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 + */ + +/* + * chacha20-poly1305.h file + * This file includes definitions needed for Chacha20-poly1305 AEAD cipher + * using different crypto backends. + */ + +#ifndef CHACHA20_POLY1305_H +#define CHACHA20_POLY1305_H + +#define CHACHA20_BLOCKSIZE 64 +#define CHACHA20_KEYLEN 32 + +#define POLY1305_TAGLEN 16 +/* size of the keys k1 and k2 as defined in specs */ +#define POLY1305_KEYLEN 32 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack(pop) +#endif +; + +#endif /* CHACHA20_POLY1305_H */ diff --git a/libssh/include/libssh/channels.h b/libssh/include/libssh/channels.h index bbabcfd..ce8540a 100644 --- a/libssh/include/libssh/channels.h +++ b/libssh/include/libssh/channels.h @@ -98,7 +98,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request); SSH_PACKET_CALLBACK(channel_rcv_data); int channel_default_bufferize(ssh_channel channel, - void *data, size_t len, + void *data, uint32_t len, bool is_stderr); int ssh_channel_flush(ssh_channel channel); uint32_t ssh_channel_new_id(ssh_session session); diff --git a/libssh/include/libssh/config.h b/libssh/include/libssh/config.h index f2b9b57..a24d11a 100644 --- a/libssh/include/libssh/config.h +++ b/libssh/include/libssh/config.h @@ -42,7 +42,6 @@ enum ssh_config_opcode_e { SOC_MACS, SOC_COMPRESSION, SOC_TIMEOUT, - SOC_PROTOCOL, SOC_STRICTHOSTKEYCHECK, SOC_KNOWNHOSTS, SOC_PROXYCOMMAND, @@ -60,8 +59,9 @@ enum ssh_config_opcode_e { SOC_KBDINTERACTIVEAUTHENTICATION, SOC_PASSWORDAUTHENTICATION, SOC_PUBKEYAUTHENTICATION, - SOC_PUBKEYACCEPTEDTYPES, + SOC_PUBKEYACCEPTEDKEYTYPES, SOC_REKEYLIMIT, + SOC_IDENTITYAGENT, SOC_MAX /* Keep this one last in the list */ }; diff --git a/libssh/include/libssh/crypto.h b/libssh/include/libssh/crypto.h index 489b490..1d73613 100644 --- a/libssh/include/libssh/crypto.h +++ b/libssh/include/libssh/crypto.h @@ -111,7 +111,15 @@ struct ssh_crypto_struct { #endif /* WITH_GEX */ #ifdef HAVE_ECDH #ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY *ecdh_privkey; +#else + EVP_PKEY *ecdh_privkey; +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_t ecdh_privkey; #elif defined HAVE_LIBMBEDCRYPTO @@ -211,7 +219,12 @@ struct ssh_cipher_struct { const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void); int sshkdf_derive_key(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len); +int secure_memcmp(const void *s1, const void *s2, size_t n); +#ifdef HAVE_LIBCRYPTO +ENGINE *pki_get_engine(void); +#endif /* HAVE_LIBCRYPTO */ + #endif /* _CRYPTO_H_ */ diff --git a/libssh/include/libssh/dh.h b/libssh/include/libssh/dh.h index 390b30d..353dc23 100644 --- a/libssh/include/libssh/dh.h +++ b/libssh/include/libssh/dh.h @@ -34,14 +34,24 @@ struct dh_ctx; int ssh_dh_init_common(struct ssh_crypto_struct *crypto); void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_get_parameters(struct dh_ctx *ctx, const_bignum *modulus, const_bignum *generator); +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator); +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_set_parameters(struct dh_ctx *ctx, const bignum modulus, const bignum generator); int ssh_dh_keypair_gen_keys(struct dh_ctx *ctx, int peer); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, const_bignum *priv, const_bignum *pub); +#else +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub); +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, const bignum priv, const bignum pub); diff --git a/libssh/include/libssh/keys.h b/libssh/include/libssh/keys.h index 7b13861..934189c 100644 --- a/libssh/include/libssh/keys.h +++ b/libssh/include/libssh/keys.h @@ -32,8 +32,12 @@ struct ssh_public_key_struct { gcry_sexp_t dsa_pub; gcry_sexp_t rsa_pub; #elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA *dsa_pub; RSA *rsa_pub; +#else /* OPENSSL_VERSION_NUMBER */ + EVP_PKEY *key_pub; +#endif #elif defined(HAVE_LIBMBEDCRYPTO) mbedtls_pk_context *rsa_pub; void *dsa_pub; @@ -46,8 +50,12 @@ struct ssh_private_key_struct { gcry_sexp_t dsa_priv; gcry_sexp_t rsa_priv; #elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA *dsa_priv; RSA *rsa_priv; +#else + EVP_PKEY *key_priv; +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined(HAVE_LIBMBEDCRYPTO) mbedtls_pk_context *rsa_priv; void *dsa_priv; diff --git a/libssh/include/libssh/legacy.h b/libssh/include/libssh/legacy.h index 911173e..c0b50e2 100644 --- a/libssh/include/libssh/legacy.h +++ b/libssh/include/libssh/legacy.h @@ -88,19 +88,19 @@ SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); -LIBSSH_API void privatekey_free(ssh_private_key prv); -LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, +SSH_DEPRECATED LIBSSH_API void privatekey_free(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, int type, const char *passphrase); -LIBSSH_API void publickey_free(ssh_public_key key); -LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, +SSH_DEPRECATED LIBSSH_API void publickey_free(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, ssh_string pubkey, int type); -LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, +SSH_DEPRECATED LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, int *type); -LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); -LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); -LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, +SSH_DEPRECATED LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, ssh_string *publickey, int *type); -LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); +SSH_DEPRECATED LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); diff --git a/libssh/include/libssh/libcrypto.h b/libssh/include/libssh/libcrypto.h index 4117942..16e5f98 100644 --- a/libssh/include/libssh/libcrypto.h +++ b/libssh/include/libssh/libcrypto.h @@ -38,7 +38,7 @@ typedef EVP_MD_CTX* SHA256CTX; typedef EVP_MD_CTX* SHA384CTX; typedef EVP_MD_CTX* SHA512CTX; typedef EVP_MD_CTX* MD5CTX; -typedef HMAC_CTX* HMACCTX; +typedef EVP_MD_CTX* HMACCTX; #ifdef HAVE_ECC typedef EVP_MD_CTX *EVPCTX; #else @@ -60,10 +60,6 @@ typedef void *EVPCTX; #include #include -#define OPENSSL_0_9_7b 0x0090702fL -#if (OPENSSL_VERSION_NUMBER <= OPENSSL_0_9_7b) -#define BROKEN_AES_CTR -#endif typedef BIGNUM* bignum; typedef const BIGNUM* const_bignum; typedef BN_CTX* bignum_CTX; @@ -114,6 +110,8 @@ typedef BN_CTX* bignum_CTX; /* Returns true if the OpenSSL is operating in FIPS mode */ #ifdef HAVE_OPENSSL_FIPS_MODE #define ssh_fips_mode() (FIPS_mode() != 0) +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ssh_fips_mode() EVP_default_properties_is_fips_enabled(NULL) #else #define ssh_fips_mode() false #endif diff --git a/libssh/include/libssh/libssh.h b/libssh/include/libssh/libssh.h index 1664dfc..7857a77 100644 --- a/libssh/include/libssh/libssh.h +++ b/libssh/include/libssh/libssh.h @@ -1,7 +1,7 @@ /* * This file is part of the SSH Library * - * Copyright (c) 2003-2021 by Aris Adamantiadis and the libssh team + * Copyright (c) 2003-2022 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,6 +49,8 @@ #endif #endif +#include + #ifdef _MSC_VER /* Visual Studio hasn't inttypes.h so it doesn't know uint32_t */ typedef int int32_t; @@ -289,6 +291,10 @@ enum ssh_keytypes_e{ SSH_KEYTYPE_ECDSA_P384_CERT01, SSH_KEYTYPE_ECDSA_P521_CERT01, SSH_KEYTYPE_ED25519_CERT01, + SSH_KEYTYPE_SK_ECDSA, + SSH_KEYTYPE_SK_ECDSA_CERT01, + SSH_KEYTYPE_SK_ED25519, + SSH_KEYTYPE_SK_ED25519_CERT01, }; enum ssh_keycmp_e { @@ -400,6 +406,8 @@ enum ssh_options_e { SSH_OPTIONS_PROCESS_CONFIG, SSH_OPTIONS_REKEY_DATA, SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, }; enum { @@ -471,8 +479,6 @@ LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connectio const char *cookie, int screen_number); LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); -LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct - timeval * timeout); LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, ssh_counter counter); @@ -503,7 +509,12 @@ LIBSSH_API char *ssh_dirname (const char *path); LIBSSH_API int ssh_finalize(void); /* REVERSE PORT FORWARDING */ -LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, +LIBSSH_API ssh_channel ssh_channel_open_forward_port(ssh_session session, + int timeout_ms, + int *destination_port, + char **originator, + int *originator_port); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int *destination_port); LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, @@ -545,7 +556,27 @@ SSH_DEPRECATED LIBSSH_API int ssh_write_knownhost(ssh_session session); SSH_DEPRECATED LIBSSH_API char *ssh_dump_knownhost(ssh_session session); SSH_DEPRECATED LIBSSH_API int ssh_is_server_known(ssh_session session); SSH_DEPRECATED LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +SSH_DEPRECATED LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_close(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +SSH_DEPRECATED LIBSSH_API void ssh_scp_free(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_init(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +SSH_DEPRECATED LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); LIBSSH_API int ssh_get_random(void *where,int len,int strong); @@ -583,6 +614,10 @@ LIBSSH_API int ssh_set_log_level(int level); LIBSSH_API int ssh_get_log_level(void); LIBSSH_API void *ssh_get_log_userdata(void); LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) PRINTF_ATTRIBUTE(3, 0); LIBSSH_API void _ssh_log(int verbosity, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); @@ -653,6 +688,7 @@ LIBSSH_API int ssh_key_is_private(const ssh_key k); LIBSSH_API int ssh_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what); +LIBSSH_API ssh_key ssh_key_dup(const ssh_key key); LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); @@ -708,24 +744,6 @@ LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); -LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); -LIBSSH_API int ssh_scp_close(ssh_scp scp); -LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); -LIBSSH_API void ssh_scp_free(ssh_scp scp); -LIBSSH_API int ssh_scp_init(ssh_scp scp); -LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); -LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); -LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); -LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); -LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); -LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); -LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); -LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); -LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); -LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); -LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); -LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); -LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, fd_set *readfds, struct timeval *timeout); LIBSSH_API int ssh_service_request(ssh_session session, const char *service); @@ -753,6 +771,8 @@ LIBSSH_API int ssh_userauth_publickey(ssh_session session, 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, const char *username, const char *passphrase); @@ -821,6 +841,7 @@ LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); +LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); #ifndef LIBSSH_LEGACY_0_4 #include "libssh/legacy.h" diff --git a/libssh/include/libssh/libsshpp.hpp b/libssh/include/libssh/libsshpp.hpp index 75c9c7a..602c7ae 100644 --- a/libssh/include/libssh/libsshpp.hpp +++ b/libssh/include/libssh/libsshpp.hpp @@ -369,13 +369,11 @@ class Session { return state; } void log(int priority, const char *format, ...){ - char buffer[1024]; va_list va; va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(priority, "libsshpp", format, &va); va_end(va); - _ssh_log(priority, "libsshpp", "%s", buffer); } /** @brief copies options from a session to another @@ -525,7 +523,7 @@ class Channel { return ssh_channel_is_open(channel) != 0; } int openForward(const char *remotehost, int remoteport, - const char *sourcehost=NULL, int localport=0){ + const char *sourcehost, int localport=0){ int err=ssh_channel_open_forward(channel,remotehost,remoteport, sourcehost, localport); ssh_throw(err); @@ -632,8 +630,8 @@ class Channel { * @param is_stderr write should be done on the stderr channel (server only) * @returns number of bytes written * @throws SshException in case of error - * @see channel_write - * @see channel_write_stderr + * @see ssh_channel_write + * @see ssh_channel_write_stderr */ int write(const void *data, size_t len, bool is_stderr=false){ int ret; @@ -671,7 +669,7 @@ class Channel { inline Channel *Session::acceptForward(int timeout_ms){ ssh_channel forward = - ssh_channel_accept_forward(c_session, timeout_ms, NULL); + ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL); ssh_throw_null(c_session,forward); Channel *newchan = new Channel(*this,forward); return newchan; diff --git a/libssh/include/libssh/messages.h b/libssh/include/libssh/messages.h index 04d041d..1341d70 100644 --- a/libssh/include/libssh/messages.h +++ b/libssh/include/libssh/messages.h @@ -28,6 +28,7 @@ struct ssh_auth_request { int method; char *password; struct ssh_key_struct *pubkey; + char *sigtype; enum ssh_publickey_state_e signature_state; char kbdint_response; }; diff --git a/libssh/include/libssh/misc.h b/libssh/include/libssh/misc.h index 3cc3b11..569e2b7 100644 --- a/libssh/include/libssh/misc.h +++ b/libssh/include/libssh/misc.h @@ -81,7 +81,7 @@ 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)) -int ssh_make_milliseconds(long sec, long usec); +int ssh_make_milliseconds(unsigned long sec, unsigned long usec); void ssh_timestamp_init(struct ssh_timestamp *ts); int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); @@ -96,5 +96,7 @@ int ssh_mkdirs(const char *pathname, mode_t mode); int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); int ssh_newline_vis(const char *string, char *buf, size_t buf_len); +int ssh_tmpname(char *template); +char *ssh_strreplace(const char *src, const char *pattern, const char *repl); #endif /* MISC_H_ */ diff --git a/libssh/include/libssh/options.h b/libssh/include/libssh/options.h index 5b1cb9f..e8dc6c6 100644 --- a/libssh/include/libssh/options.h +++ b/libssh/include/libssh/options.h @@ -22,6 +22,7 @@ #define _OPTIONS_H int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_config_parse_string(ssh_session session, const char *input); int ssh_options_set_algo(ssh_session session, enum ssh_kex_types_e algo, const char *list); diff --git a/libssh/include/libssh/packet.h b/libssh/include/libssh/packet.h index 561bba8..dec3a43 100644 --- a/libssh/include/libssh/packet.h +++ b/libssh/include/libssh/packet.h @@ -67,7 +67,7 @@ int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); int ssh_packet_parse_type(ssh_session session); //int packet_flush(ssh_session session, int enforce_blocking); -int ssh_packet_socket_callback(const void *data, size_t len, void *user); +size_t ssh_packet_socket_callback(const void *data, size_t len, void *user); void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks); @@ -80,7 +80,7 @@ int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *sourc size_t start, size_t encrypted_size); unsigned char *ssh_packet_encrypt(ssh_session session, void *packet, - uint32_t len); + size_t len); int ssh_packet_hmac_verify(ssh_session session, const void *data, size_t len, unsigned char *mac, enum ssh_hmac_e type); int ssh_packet_set_newkeys(ssh_session session, diff --git a/libssh/include/libssh/pki.h b/libssh/include/libssh/pki.h index 2fa7582..1d5250f 100644 --- a/libssh/include/libssh/pki.h +++ b/libssh/include/libssh/pki.h @@ -21,6 +21,7 @@ #ifndef PKI_H_ #define PKI_H_ +#include #include "libssh/priv.h" #ifdef HAVE_OPENSSL_EC_H #include @@ -28,9 +29,11 @@ #ifdef HAVE_OPENSSL_ECDSA_H #include #endif - +#ifdef HAVE_LIBCRYPTO +#include +#endif #include "libssh/crypto.h" -#ifdef HAVE_OPENSSL_ED25519 +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_ED25519) /* If using OpenSSL implementation, define the signature lenght which would be * defined in libssh/ed25519.h otherwise */ #define ED25519_SIG_LEN 64 @@ -46,6 +49,7 @@ #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 struct ssh_key_struct { enum ssh_keytypes_e type; @@ -61,21 +65,31 @@ struct ssh_key_struct { mbedtls_ecdsa_context *ecdsa; void *dsa; #elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA *dsa; RSA *rsa; +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move into the #if above + */ # if defined(HAVE_OPENSSL_ECC) EC_KEY *ecdsa; # else void *ecdsa; # endif /* HAVE_OPENSSL_EC_H */ + /* This holds either ENGINE key for PKCS#11 support or just key in + * high-level format required by OpenSSL 3.0 */ + EVP_PKEY *key; #endif /* HAVE_LIBGCRYPT */ -#ifdef HAVE_OPENSSL_ED25519 +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_ED25519) uint8_t *ed25519_pubkey; uint8_t *ed25519_privkey; #else ed25519_pubkey *ed25519_pubkey; ed25519_privkey *ed25519_privkey; #endif + ssh_string sk_application; void *cert; enum ssh_keytypes_e cert_type; }; @@ -92,16 +106,19 @@ struct ssh_signature_struct { ssh_string rsa_sig; struct mbedtls_ecdsa_sig ecdsa_sig; #endif /* HAVE_LIBGCRYPT */ -#ifndef HAVE_OPENSSL_ED25519 +#if !defined(HAVE_LIBCRYPTO) || !defined(HAVE_OPENSSL_ED25519) ed25519_signature *ed25519_sig; #endif ssh_string raw_sig; + + /* Security Key specific additions */ + uint8_t sk_flags; + uint32_t sk_counter; }; typedef struct ssh_signature_struct *ssh_signature; /* SSH Key Functions */ -ssh_key ssh_key_dup(const ssh_key key); void ssh_key_clean (ssh_key key); const char * @@ -119,6 +136,8 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name); #define is_cert_type(kt)\ ((kt) == SSH_KEYTYPE_DSS_CERT01 ||\ (kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01 ||\ ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ (kt) <= SSH_KEYTYPE_ED25519_CERT01)) @@ -164,4 +183,13 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); int ssh_key_algorithm_allowed(ssh_session session, const char *type); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); + +/* Return the key size in bits */ +int ssh_key_size(ssh_key key); + +/* PKCS11 URI function to check if filename is a path or a PKCS11 URI */ +bool ssh_pki_is_uri(const char *filename); +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri); + #endif /* PKI_H_ */ diff --git a/libssh/include/libssh/pki_priv.h b/libssh/include/libssh/pki_priv.h index d365a2d..c0edb85 100644 --- a/libssh/include/libssh/pki_priv.h +++ b/libssh/include/libssh/pki_priv.h @@ -43,6 +43,14 @@ int bcrypt_pbkdf(const char *pass, /* Magic defined in OpenSSH/PROTOCOL.key */ #define OPENSSH_AUTH_MAGIC "openssh-key-v1" +/* Determine type of ssh key. */ +enum ssh_key_e { + SSH_KEY_PUBLIC = 0, + SSH_KEY_PRIVATE +}; + +void pki_key_clean(ssh_key key); + int pki_key_ecdsa_nid_from_name(const char *name); const char *pki_key_ecdsa_nid_to_name(int nid); const char *ssh_key_signature_to_char(enum ssh_keytypes_e type, @@ -156,4 +164,8 @@ ssh_key ssh_pki_openssh_privkey_import(const char *text_key, ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +/* URI Function */ +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); #endif /* PKI_PRIV_H_ */ diff --git a/libssh/include/libssh/poly1305.h b/libssh/include/libssh/poly1305.h index 9174bd1..513f1b9 100644 --- a/libssh/include/libssh/poly1305.h +++ b/libssh/include/libssh/poly1305.h @@ -5,9 +5,7 @@ #ifndef POLY1305_H #define POLY1305_H - -#define POLY1305_KEYLEN 32 -#define POLY1305_TAGLEN 16 +#include "libssh/chacha20-poly1305-common.h" void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, const uint8_t key[POLY1305_KEYLEN]) diff --git a/libssh/include/libssh/priv.h b/libssh/include/libssh/priv.h index 3e91086..bab761b 100644 --- a/libssh/include/libssh/priv.h +++ b/libssh/include/libssh/priv.h @@ -29,6 +29,7 @@ #ifndef _LIBSSH_PRIV_H #define _LIBSSH_PRIV_H +#include #include #include #include @@ -66,11 +67,6 @@ char *strndup(const char *s, size_t n); #ifdef _WIN32 -/* Imitate define of inttypes.h */ -# ifndef PRIdS -# define PRIdS "Id" -# endif - # ifndef PRIu64 # if __WORDSIZE == 64 # define PRIu64 "lu" @@ -156,14 +152,15 @@ char *strndup(const char *s, size_t n); # endif /* _MSC_VER */ struct timeval; -int gettimeofday(struct timeval *__p, void *__t); +int ssh_gettimeofday(struct timeval *__p, void *__t); + +#define gettimeofday ssh_gettimeofday #define _XCLOSESOCKET closesocket #else /* _WIN32 */ #include -#define PRIdS "zd" #define _XCLOSESOCKET close @@ -173,7 +170,15 @@ int gettimeofday(struct timeval *__p, void *__t); #include "libssh/callbacks.h" /* some constants */ -#ifndef MAX_PACKAT_LEN +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 4096 +#endif +#endif + +#ifndef MAX_PACKET_LEN #define MAX_PACKET_LEN 262144 #endif #ifndef ERROR_BUFFERLEN @@ -296,7 +301,7 @@ int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); /* match.c */ int match_pattern_list(const char *string, const char *pattern, - unsigned int len, int dolower); + size_t len, int dolower); int match_hostname(const char *host, const char *pattern, unsigned int len); /* connector.c */ @@ -347,7 +352,7 @@ void explicit_bzero(void *s, size_t n); #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) /** - * Get the argument cound of variadic arguments + * Get the argument count of variadic arguments */ /* * Since MSVC 2010 there is a bug in passing __VA_ARGS__ to subsequent @@ -426,4 +431,7 @@ void ssh_agent_state_free(void *data); bool is_ssh_initialized(void); +#define SSH_ERRNO_MSG_MAX 1024 +char *ssh_strerror(int err_num, char *buf, size_t buflen); + #endif /* _LIBSSH_PRIV_H */ diff --git a/libssh/include/libssh/server.h b/libssh/include/libssh/server.h index 41f89d5..c1a9c15 100644 --- a/libssh/include/libssh/server.h +++ b/libssh/include/libssh/server.h @@ -56,6 +56,8 @@ enum ssh_bind_options_e { 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, }; typedef struct ssh_bind_struct* ssh_bind; @@ -243,6 +245,18 @@ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); */ LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); +/** + * @brief Send the server's issue-banner to client. + * + * + * @param[in] session The server session. + * + * @param[in] banner The server's banner. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); + /********************************************************** * SERVER MESSAGING **********************************************************/ @@ -282,8 +296,10 @@ LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); * * @see ssh_message_get() * @see ssh_message_type() + * @warning This function should not be used anymore as there is a + * callback based server implementation now auth_password_function. */ -LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); /** * @brief Get the publickey of the authenticated user. @@ -298,11 +314,16 @@ LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); * @see ssh_key_cmp() * @see ssh_message_get() * @see ssh_message_type() + * @warning This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function. */ -LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); +SSH_DEPRECATED LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); -LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); + +/* Replaced by callback based server implementation auth_pubkey_function */ +SSH_DEPRECATED LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); + LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); @@ -331,11 +352,12 @@ LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); -LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); +/* Replaced by callback based server implementation function channel_pty_request_function*/ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); @@ -344,17 +366,18 @@ LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); -LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); -LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); -LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); +/* Replaced by callback based server implementation function channel_open_request_x11_function*/ +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); LIBSSH_API int ssh_message_global_request_port(ssh_message msg); LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, int remoteport, const char *sourcehost, int localport); -LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, diff --git a/libssh/include/libssh/session.h b/libssh/include/libssh/session.h index 2225615..d3e5787 100644 --- a/libssh/include/libssh/session.h +++ b/libssh/include/libssh/session.h @@ -23,6 +23,7 @@ #include #include "libssh/priv.h" +#include "libssh/callbacks.h" #include "libssh/kex.h" #include "libssh/packet.h" #include "libssh/pcap.h" @@ -135,6 +136,7 @@ struct ssh_session_struct { the server */ char *discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ ssh_buffer in_buffer; PACKET in_packet; ssh_buffer out_buffer; @@ -173,7 +175,7 @@ struct ssh_session_struct { struct ssh_crypto_struct *next_crypto; /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ struct ssh_list *channels; /* linked list of channels */ - int maxchannel; + uint32_t maxchannel; ssh_agent agent; /* ssh agent */ /* keyb interactive data */ @@ -217,9 +219,11 @@ struct ssh_session_struct { char *pubkey_accepted_types; char *ProxyCommand; char *custombanner; + char *moduli_file; + char *agent_socket; unsigned long timeout; /* seconds */ unsigned long timeout_usec; - unsigned int port; + uint16_t port; socket_t fd; int StrictHostKeyChecking; char compressionlevel; @@ -232,6 +236,7 @@ struct ssh_session_struct { uint8_t options_seen[SOC_MAX]; uint64_t rekey_data; uint32_t rekey_time; + int rsa_min_size; } opts; /* counters */ ssh_counter socket_counter; @@ -246,7 +251,7 @@ struct ssh_session_struct { typedef int (*ssh_termination_function)(void *user); int ssh_handle_packets(ssh_session session, int timeout); int ssh_handle_packets_termination(ssh_session session, - long timeout, + int timeout, ssh_termination_function fct, void *user); void ssh_socket_exception_callback(int code, int errno_code, void *user); diff --git a/libssh/include/libssh/sftp.h b/libssh/include/libssh/sftp.h index 8c14b21..c855df8 100644 --- a/libssh/include/libssh/sftp.h +++ b/libssh/include/libssh/sftp.h @@ -258,7 +258,7 @@ LIBSSH_API int sftp_init(sftp_session sftp); * @param sftp The sftp session where the error is saved. * * @return The saved error (see server responses), < 0 if an error - * in the function occured. + * in the function occurred. * * @see Server responses */ @@ -413,7 +413,7 @@ LIBSSH_API void sftp_attributes_free(sftp_attributes file); * * @param dir The sftp directory handle to close. * - * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. */ LIBSSH_API int sftp_closedir(sftp_dir dir); @@ -422,7 +422,7 @@ LIBSSH_API int sftp_closedir(sftp_dir dir); * * @param file The open sftp file handle to close. * - * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occured. + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. * * @see sftp_open() */ @@ -478,7 +478,7 @@ LIBSSH_API void sftp_file_set_blocking(sftp_file handle); * * @param file The opened sftp file handle to be read from. * - * @param buf Pointer to buffer to recieve read data. + * @param buf Pointer to buffer to receive read data. * * @param count Size of the buffer in bytes. * @@ -527,7 +527,7 @@ LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); * * @param file The opened sftp file handle to be read from. * - * @param data Pointer to buffer to recieve read data. + * @param data Pointer to buffer to receive read data. * * @param len Size of the buffer in bytes. It should be bigger or * equal to the length parameter of the @@ -537,7 +537,7 @@ LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); * function. * * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error - * occured, SSH_AGAIN if the file is opened in nonblocking + * 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 identifier @@ -632,7 +632,7 @@ LIBSSH_API void sftp_rewind(sftp_file file); LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); /** - * @brief Remove a directoy. + * @brief Remove a directory. * * @param sftp The sftp session handle. * @@ -806,7 +806,7 @@ LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); /** * @brief Synchronize a file's in-core state with storage device * - * This calls the "fsync@openssh.com" extention. You should check if the + * This calls the "fsync@openssh.com" extension. You should check if the * extensions is supported using: * * @code @@ -854,7 +854,7 @@ LIBSSH_API int sftp_server_version(sftp_session sftp); LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); /** - * @brief Intialize the sftp server. + * @brief Initialize the sftp server. * * @param sftp The sftp session to init. * diff --git a/libssh/include/libssh/sftp_priv.h b/libssh/include/libssh/sftp_priv.h index 8392519..ccde008 100644 --- a/libssh/include/libssh/sftp_priv.h +++ b/libssh/include/libssh/sftp_priv.h @@ -22,7 +22,7 @@ #define SFTP_PRIV_H sftp_packet sftp_packet_read(sftp_session sftp); -ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); void sftp_packet_free(sftp_packet packet); int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); sftp_attributes sftp_parse_attr(sftp_session session, diff --git a/libssh/include/libssh/socket.h b/libssh/include/libssh/socket.h index 5e345c6..ae56f5f 100644 --- a/libssh/include/libssh/socket.h +++ b/libssh/include/libssh/socket.h @@ -35,13 +35,13 @@ void ssh_socket_reset(ssh_socket s); void ssh_socket_free(ssh_socket s); void ssh_socket_set_fd(ssh_socket s, socket_t fd); socket_t ssh_socket_get_fd(ssh_socket s); -#ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path); void ssh_execute_command(const char *command, socket_t in, socket_t out); +#ifndef _WIN32 int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); #endif void ssh_socket_close(ssh_socket s); -int ssh_socket_write(ssh_socket s,const void *buffer, int len); +int ssh_socket_write(ssh_socket s,const void *buffer, uint32_t len); int ssh_socket_is_open(ssh_socket s); int ssh_socket_fd_isset(ssh_socket s, fd_set *set); void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); diff --git a/libssh/include/libssh/wrapper.h b/libssh/include/libssh/wrapper.h index ba64939..f4a33d2 100644 --- a/libssh/include/libssh/wrapper.h +++ b/libssh/include/libssh/wrapper.h @@ -42,7 +42,8 @@ enum ssh_hmac_e { SSH_HMAC_SHA512, SSH_HMAC_MD5, SSH_HMAC_AEAD_POLY1305, - SSH_HMAC_AEAD_GCM + SSH_HMAC_AEAD_GCM, + SSH_HMAC_NONE, }; enum ssh_des_e { @@ -67,42 +68,42 @@ struct ssh_crypto_struct; typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; MD5CTX md5_init(void); -void md5_update(MD5CTX c, const void *data, unsigned long len); +void md5_update(MD5CTX c, const void *data, size_t len); void md5_final(unsigned char *md,MD5CTX c); SHACTX sha1_init(void); -void sha1_update(SHACTX c, const void *data, unsigned long len); +void sha1_update(SHACTX c, const void *data, size_t len); void sha1_final(unsigned char *md,SHACTX c); -void sha1(const unsigned char *digest,int len,unsigned char *hash); +void sha1(const unsigned char *digest,size_t len,unsigned char *hash); SHA256CTX sha256_init(void); -void sha256_update(SHA256CTX c, const void *data, unsigned long len); +void sha256_update(SHA256CTX c, const void *data, size_t len); void sha256_final(unsigned char *md,SHA256CTX c); -void sha256(const unsigned char *digest, int len, unsigned char *hash); +void sha256(const unsigned char *digest, size_t len, unsigned char *hash); SHA384CTX sha384_init(void); -void sha384_update(SHA384CTX c, const void *data, unsigned long len); +void sha384_update(SHA384CTX c, const void *data, size_t len); void sha384_final(unsigned char *md,SHA384CTX c); -void sha384(const unsigned char *digest, int len, unsigned char *hash); +void sha384(const unsigned char *digest, size_t len, unsigned char *hash); SHA512CTX sha512_init(void); -void sha512_update(SHA512CTX c, const void *data, unsigned long len); +void sha512_update(SHA512CTX c, const void *data, size_t len); void sha512_final(unsigned char *md,SHA512CTX c); -void sha512(const unsigned char *digest, int len, unsigned char *hash); +void sha512(const unsigned char *digest, size_t len, unsigned char *hash); -void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen); +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, unsigned long len); +void evp_update(EVPCTX ctx, const void *data, size_t len); void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); -HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); -void hmac_update(HMACCTX c, const void *data, unsigned long len); -void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); +HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); +int hmac_update(HMACCTX c, const void *data, size_t len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len); size_t hmac_digest_len(enum ssh_hmac_e type); int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len); int crypt_set_algorithms_client(ssh_session session); @@ -119,4 +120,11 @@ struct ssh_hmac_struct *ssh_get_hmactab(void); struct ssh_cipher_struct *ssh_get_ciphertab(void); const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, int selection); +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new, int demote); +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new, int demote); +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new, int demote); +#endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ + #endif /* WRAPPER_H_ */ diff --git a/libssh/src/ABI/current b/libssh/src/ABI/current index 4850fb3..f4cfd30 100644 --- a/libssh/src/ABI/current +++ b/libssh/src/ABI/current @@ -1 +1 @@ -4.8.7 \ No newline at end of file +4.9.4 \ No newline at end of file diff --git a/libssh/src/ABI/libssh-4.8.2.symbols b/libssh/src/ABI/libssh-4.9.0.symbols similarity index 98% rename from libssh/src/ABI/libssh-4.8.2.symbols rename to libssh/src/ABI/libssh-4.9.0.symbols index dce4add..a26e2c5 100644 --- a/libssh/src/ABI/libssh-4.8.2.symbols +++ b/libssh/src/ABI/libssh-4.9.0.symbols @@ -149,6 +149,7 @@ 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 @@ -241,6 +242,7 @@ 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 @@ -346,6 +348,7 @@ 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 @@ -353,6 +356,7 @@ 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 @@ -407,8 +411,10 @@ 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 diff --git a/libssh/src/ABI/libssh-4.8.3.symbols b/libssh/src/ABI/libssh-4.9.1.symbols similarity index 98% rename from libssh/src/ABI/libssh-4.8.3.symbols rename to libssh/src/ABI/libssh-4.9.1.symbols index dce4add..a26e2c5 100644 --- a/libssh/src/ABI/libssh-4.8.3.symbols +++ b/libssh/src/ABI/libssh-4.9.1.symbols @@ -149,6 +149,7 @@ 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 @@ -241,6 +242,7 @@ 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 @@ -346,6 +348,7 @@ 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 @@ -353,6 +356,7 @@ 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 @@ -407,8 +411,10 @@ 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 diff --git a/libssh/src/ABI/libssh-4.8.4.symbols b/libssh/src/ABI/libssh-4.9.2.symbols similarity index 98% rename from libssh/src/ABI/libssh-4.8.4.symbols rename to libssh/src/ABI/libssh-4.9.2.symbols index dce4add..a26e2c5 100644 --- a/libssh/src/ABI/libssh-4.8.4.symbols +++ b/libssh/src/ABI/libssh-4.9.2.symbols @@ -149,6 +149,7 @@ 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 @@ -241,6 +242,7 @@ 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 @@ -346,6 +348,7 @@ 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 @@ -353,6 +356,7 @@ 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 @@ -407,8 +411,10 @@ 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 diff --git a/libssh/src/ABI/libssh-4.8.5.symbols b/libssh/src/ABI/libssh-4.9.3.symbols similarity index 98% rename from libssh/src/ABI/libssh-4.8.5.symbols rename to libssh/src/ABI/libssh-4.9.3.symbols index dce4add..a26e2c5 100644 --- a/libssh/src/ABI/libssh-4.8.5.symbols +++ b/libssh/src/ABI/libssh-4.9.3.symbols @@ -149,6 +149,7 @@ 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 @@ -241,6 +242,7 @@ 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 @@ -346,6 +348,7 @@ 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 @@ -353,6 +356,7 @@ 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 @@ -407,8 +411,10 @@ 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 diff --git a/libssh/src/ABI/libssh-4.9.4.symbols b/libssh/src/ABI/libssh-4.9.4.symbols new file mode 100644 index 0000000..a26e2c5 --- /dev/null +++ b/libssh/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/libssh/src/CMakeLists.txt b/libssh/src/CMakeLists.txt index a576cf7..c090fef 100644 --- a/libssh/src/CMakeLists.txt +++ b/libssh/src/CMakeLists.txt @@ -9,13 +9,6 @@ set(LIBSSH_LINK_LIBRARIES ${LIBSSH_REQUIRED_LIBRARIES} ) -if (WIN32) - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ws2_32 - ) -endif (WIN32) - if (OPENSSL_CRYPTO_LIBRARIES) set(LIBSSH_PRIVATE_INCLUDE_DIRS ${LIBSSH_PRIVATE_INCLUDE_DIRS} @@ -93,6 +86,15 @@ if (MINGW AND Threads_FOUND) ) endif() +# This 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} + ws2_32 + ) +endif (WIN32) + if (BUILD_STATIC_LIB) set(LIBSSH_STATIC_LIBRARY ssh_static @@ -112,6 +114,7 @@ set(libssh_SRCS config.c connect.c connector.c + crypto_common.c curve25519.c dh.c ecdh.c @@ -143,9 +146,6 @@ set(libssh_SRCS wrapper.c external/bcrypt_pbkdf.c external/blowfish.c - external/chacha.c - external/poly1305.c - chachapoly.c config_parser.c token.c pki_ed25519_common.c @@ -184,6 +184,8 @@ if (WITH_GCRYPT) gcrypt_missing.c pki_gcrypt.c ecdh_gcrypt.c + getrandom_gcrypt.c + md_gcrypt.c dh_key.c pki_ed25519.c external/ed25519.c @@ -191,6 +193,14 @@ if (WITH_GCRYPT) external/ge25519.c external/sc25519.c ) + if (NOT HAVE_GCRYPT_CHACHA_POLY) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT HAVE_GCRYPT_CHACHA_POLY) elseif (WITH_MBEDTLS) set(libssh_SRCS ${libssh_SRCS} @@ -199,6 +209,8 @@ elseif (WITH_MBEDTLS) mbedcrypto_missing.c pki_mbedcrypto.c ecdh_mbedcrypto.c + getrandom_mbedcrypto.c + md_mbedcrypto.c dh_key.c pki_ed25519.c external/ed25519.c @@ -206,12 +218,23 @@ elseif (WITH_MBEDTLS) external/ge25519.c external/sc25519.c ) + if (NOT (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H)) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif() + else (WITH_GCRYPT) set(libssh_SRCS ${libssh_SRCS} threads/libcrypto.c pki_crypto.c ecdh_crypto.c + getrandom_crypto.c + md_crypto.c libcrypto.c dh_crypto.c ) @@ -225,6 +248,14 @@ else (WITH_GCRYPT) external/sc25519.c ) endif (NOT HAVE_OPENSSL_ED25519) + if (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305)) if(OPENSSL_VERSION VERSION_LESS "1.1.0") set(libssh_SRCS ${libssh_SRCS} libcrypto-compat.c) endif() @@ -275,12 +306,12 @@ if (WITH_GSSAPI AND GSSAPI_FOUND) endif (WITH_GSSAPI AND GSSAPI_FOUND) if (NOT WITH_NACL) - if (NOT HAVE_OPENSSL_ED25519) + if (NOT HAVE_LIBCRYPTO OR NOT HAVE_OPENSSL_ED25519) set(libssh_SRCS ${libssh_SRCS} external/curve25519_ref.c ) - endif (NOT HAVE_OPENSSL_ED25519) + endif() endif (NOT WITH_NACL) # Set the path to the default map file @@ -317,10 +348,6 @@ endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) # This gets built as a static library, if -DBUILD_SHARED_LIBS=OFF is passed to # cmake. add_library(ssh ${libssh_SRCS}) -target_compile_options(ssh - PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) target_include_directories(ssh PUBLIC $ @@ -377,10 +404,6 @@ install(EXPORT libssh-config if (BUILD_STATIC_LIB) add_library(ssh-static STATIC ${libssh_SRCS}) - target_compile_options(ssh-static - PRIVATE - ${DEFAULT_C_COMPILE_FLAGS} - -D_GNU_SOURCE) target_include_directories(ssh-static PUBLIC diff --git a/libssh/src/agent.c b/libssh/src/agent.c index 62b0093..299b4ad 100644 --- a/libssh/src/agent.c +++ b/libssh/src/agent.c @@ -33,8 +33,6 @@ * the agent returns the signed data */ -#ifndef _WIN32 - #include "config.h" #include @@ -46,8 +44,14 @@ #include #endif +#ifndef _WIN32 #include #include +#include +#else +#include +#include +#endif #include "libssh/agent.h" #include "libssh/priv.h" @@ -63,9 +67,9 @@ (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ ((x) == SSH2_AGENT_FAILURE)) -static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int do_read) { +static uint32_t atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) { char *b = buf; - size_t pos = 0; + uint32_t pos = 0; ssize_t res; ssh_pollfd_t pfd; ssh_channel channel = agent->channel; @@ -79,9 +83,9 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int while (n > pos) { if (do_read) { - res = read(fd, b + pos, n - pos); + res = recv(fd, b + pos, n - pos, 0); } else { - res = write(fd, b + pos, n - pos); + res = send(fd, b + pos, n - pos, 0); } switch (res) { case -1: @@ -102,7 +106,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int errno = do_read ? 0 : EPIPE; return pos; default: - pos += (size_t) res; + pos += (uint32_t) res; } } return pos; @@ -117,7 +121,7 @@ static size_t atomicio(struct ssh_agent_struct *agent, void *buf, size_t n, int continue; if (res == SSH_ERROR) return 0; - pos += (size_t)res; + pos += (uint32_t)res; } return pos; } @@ -216,7 +220,8 @@ static int agent_connect(ssh_session session) { if (session->agent->channel != NULL) return 0; - auth_sock = getenv("SSH_AUTH_SOCK"); + auth_sock = session->opts.agent_socket ? + session->opts.agent_socket : getenv("SSH_AUTH_SOCK"); if (auth_sock && *auth_sock) { if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { @@ -251,7 +256,9 @@ static int agent_decode_reply(struct ssh_session_struct *session, int type) { static int agent_talk(struct ssh_session_struct *session, struct ssh_buffer_struct *request, struct ssh_buffer_struct *reply) { uint32_t len = 0; - uint8_t payload[1024] = {0}; + uint8_t tmpbuf[4]; + uint8_t *payload = tmpbuf; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; len = ssh_buffer_get_len(request); SSH_LOG(SSH_LOG_TRACE, "Request length: %u", len); @@ -262,20 +269,20 @@ static int agent_talk(struct ssh_session_struct *session, if (atomicio(session->agent, ssh_buffer_get(request), len, 0) != len) { SSH_LOG(SSH_LOG_WARN, "atomicio sending request failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } } else { SSH_LOG(SSH_LOG_WARN, "atomicio sending request length failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } /* wait for response, read the length of the response packet */ if (atomicio(session->agent, payload, 4, 1) != 4) { SSH_LOG(SSH_LOG_WARN, "atomicio read response length failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } @@ -287,21 +294,18 @@ static int agent_talk(struct ssh_session_struct *session, } SSH_LOG(SSH_LOG_TRACE, "Response length: %u", len); - while (len > 0) { - size_t n = len; - if (n > sizeof(payload)) { - n = sizeof(payload); - } - if (atomicio(session->agent, payload, n, 1) != n) { - SSH_LOG(SSH_LOG_WARN, - "Error reading response from authentication socket."); - return -1; - } - if (ssh_buffer_add_data(reply, payload, n) < 0) { - SSH_LOG(SSH_LOG_WARN, "Not enough space"); - return -1; - } - len -= n; + payload = ssh_buffer_allocate(reply, len); + if (payload == NULL) { + SSH_LOG(SSH_LOG_WARN, "Not enough space"); + return -1; + } + + if (atomicio(session->agent, payload, len, 1) != len) { + SSH_LOG(SSH_LOG_WARN, + "Error reading response from authentication socket."); + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(reply, len); + return -1; } return 0; @@ -313,7 +317,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) ssh_buffer reply = NULL; unsigned int type = 0; uint32_t count = 0; - int rc; + uint32_t rc; /* send message to the agent requesting the list of identities */ request = ssh_buffer_new(); @@ -394,7 +398,7 @@ uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) return session->agent->count; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, char **comment) { if (ssh_agent_get_ident_count(session) > 0) { @@ -404,7 +408,7 @@ ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, return NULL; } -/* caller has to free commment */ +/* caller has to free comment */ ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, char **comment) { struct ssh_key_struct *key; @@ -590,5 +594,3 @@ ssh_string ssh_agent_sign_data(ssh_session session, return sig_blob; } - -#endif /* _WIN32 */ diff --git a/libssh/src/auth.c b/libssh/src/auth.c index 3a5f0ef..aa3aa96 100644 --- a/libssh/src/auth.c +++ b/libssh/src/auth.c @@ -58,7 +58,7 @@ /** * @internal * - * @brief Ask access to the ssh-userauth service. + * @brief Ask for access to the ssh-userauth service. * * @param[in] session The SSH session handle. * @@ -66,7 +66,8 @@ * @returns SSH_AGAIN on nonblocking mode, if calling that function * again is necessary */ -static int ssh_userauth_request_service(ssh_session session) { +static int ssh_userauth_request_service(ssh_session session) +{ int rc; rc = ssh_service_request(session, "ssh-userauth"); @@ -78,7 +79,8 @@ static int ssh_userauth_request_service(ssh_session session) { return rc; } -static int ssh_auth_response_termination(void *user) { +static int ssh_auth_response_termination(void *user) +{ ssh_session session = (ssh_session)user; switch (session->auth.state) { case SSH_AUTH_STATE_NONE: @@ -139,7 +141,8 @@ static const char *ssh_auth_get_current_method(ssh_session session) * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again * SSH_AUTH_ERROR Error during the process. */ -static int ssh_userauth_get_response(ssh_session session) { +static int ssh_userauth_get_response(ssh_session session) +{ int rc = SSH_AUTH_ERROR; rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, @@ -364,7 +367,7 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { * * @param[in] username Deprecated, set to NULL. * - * @returns A bitfield of the fllowing values: + * @returns A bitfield of the following values: * - SSH_AUTH_METHOD_PASSWORD * - SSH_AUTH_METHOD_PUBLICKEY * - SSH_AUTH_METHOD_HOSTBASED @@ -403,10 +406,11 @@ int ssh_userauth_list(ssh_session session, const char *username) * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. */ -int ssh_userauth_none(ssh_session session, const char *username) { +int ssh_userauth_none(ssh_session session, const char *username) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_NONE: @@ -492,6 +496,7 @@ int ssh_userauth_try_publickey(ssh_session session, { ssh_string pubkey_s = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; if (session == NULL) { @@ -513,7 +518,7 @@ int ssh_userauth_try_publickey(ssh_session session, SSH_FATAL, "Wrong state (%d) during pending SSH call", session->pending_call_state); - return SSH_ERROR; + return SSH_AUTH_ERROR; } /* Check if the given public key algorithm is allowed */ @@ -523,13 +528,21 @@ int ssh_userauth_try_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -611,6 +624,7 @@ int ssh_userauth_publickey(ssh_session session, const ssh_key privkey) { ssh_string str = NULL; + bool allowed; int rc; const char *sig_type_c = NULL; enum ssh_keytypes_e key_type; @@ -647,13 +661,21 @@ int ssh_userauth_publickey(ssh_session session, "Invalid key type (unknown)"); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", sig_type_c); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } rc = ssh_userauth_request_service(session); if (rc == SSH_AGAIN) { @@ -722,7 +744,6 @@ int ssh_userauth_publickey(ssh_session session, return SSH_AUTH_ERROR; } -#ifndef _WIN32 static int ssh_userauth_agent_publickey(ssh_session session, const char *username, ssh_key pubkey) @@ -730,6 +751,7 @@ static int ssh_userauth_agent_publickey(ssh_session session, ssh_string pubkey_s = NULL; ssh_string sig_blob = NULL; const char *sig_type_c = NULL; + bool allowed; int rc; switch(session->pending_call_state) { @@ -765,7 +787,8 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } - if (!ssh_key_algorithm_allowed(session, sig_type_c)) { + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { ssh_set_error(session, SSH_REQUEST_DENIED, "The key algorithm '%s' is not allowed to be used by" " PUBLICKEY_ACCEPTED_TYPES configuration option", @@ -773,6 +796,14 @@ static int ssh_userauth_agent_publickey(ssh_session session, SSH_STRING_FREE(pubkey_s); return SSH_AUTH_DENIED; } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", sig_type_c, ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", @@ -837,7 +868,8 @@ struct ssh_agent_state_struct { }; /* Internal function */ -void ssh_agent_state_free(void *data) { +void ssh_agent_state_free(void *data) +{ struct ssh_agent_state_struct *state = data; if (state) { @@ -870,7 +902,8 @@ void ssh_agent_state_free(void *data) { * before you connect to the server. */ int ssh_userauth_agent(ssh_session session, - const char *username) { + const char *username) +{ int rc = SSH_AUTH_ERROR; struct ssh_agent_state_struct *state; @@ -959,7 +992,6 @@ int ssh_userauth_agent(ssh_session session, session->agent_state = NULL; return rc; } -#endif enum ssh_auth_auto_state_e { SSH_AUTH_AUTO_STATE_NONE = 0, @@ -975,6 +1007,55 @@ struct ssh_auth_auto_state_struct { ssh_key pubkey; }; +/** + * @brief Get the identity that is currently being processed by + * ssh_userauth_publickey_auto() + * + * This is meant to be used by a callback that happens as part of the + * execution of ssh_userauth_publickey_auto(). The auth_function + * callback might want to know which key a passphrase is needed for, + * for example. + * + * @param[in] session The SSH session. + * + * @param[out] value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value) +{ + const char *id = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->auth.auto_state != NULL && session->auth.auto_state->it != NULL) { + id = session->auth.auto_state->it->data; + } + + if (id == NULL) { + return SSH_ERROR; + } + + *value = strdup(id); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + /** * @brief Tries to automatically authenticate with public key and "none" * @@ -995,8 +1076,7 @@ struct ssh_auth_auto_state_struct { * method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n - * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use - * ssh_userauth_publickey().\n + * SSH_AUTH_SUCCESS: Authentication success\n * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. * @@ -1037,7 +1117,6 @@ int ssh_userauth_publickey_auto(ssh_session session, } state = session->auth.auto_state; if (state->state == SSH_AUTH_AUTO_STATE_NONE) { -#ifndef _WIN32 /* Try authentication with ssh-agent first */ rc = ssh_userauth_agent(session, username); if (rc == SSH_AUTH_SUCCESS || @@ -1045,7 +1124,6 @@ int ssh_userauth_publickey_auto(ssh_session session, rc == SSH_AUTH_AGAIN ) { return rc; } -#endif state->state = SSH_AUTH_AUTO_STATE_PUBKEY; } if (state->it == NULL) { @@ -1054,13 +1132,29 @@ int ssh_userauth_publickey_auto(ssh_session session, while (state->it != NULL) { const char *privkey_file = state->it->data; - char pubkey_file[1024] = {0}; + char pubkey_file[PATH_MAX] = {0}; + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { SSH_LOG(SSH_LOG_DEBUG, "Trying to authenticate with %s", privkey_file); state->privkey = NULL; state->pubkey = NULL; - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + + if (ssh_pki_is_uri(privkey_file)) { + char *pub_uri_from_priv = NULL; + SSH_LOG(SSH_LOG_INFO, + "Authenticating with PKCS #11 URI."); + pub_uri_from_priv = ssh_pki_export_pub_uri_from_priv_uri(privkey_file); + if (pub_uri_from_priv == NULL) { + return SSH_ERROR; + } else { + snprintf(pubkey_file, sizeof(pubkey_file), "%s", + pub_uri_from_priv); + SAFE_FREE(pub_uri_from_priv); + } + } else { + snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + } rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); if (rc == SSH_ERROR) { @@ -1234,10 +1328,11 @@ int ssh_userauth_publickey_auto(ssh_session session, */ int ssh_userauth_password(ssh_session session, const char *username, - const char *password) { + const char *password) +{ int rc; - switch(session->pending_call_state) { + switch (session->pending_call_state) { case SSH_PENDING_CALL_NONE: break; case SSH_PENDING_CALL_AUTH_PASSWORD: @@ -1295,7 +1390,6 @@ int ssh_userauth_password(ssh_session session, return SSH_AUTH_ERROR; } -#ifndef _WIN32 /* LEGACY */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, @@ -1312,20 +1406,28 @@ 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; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = publickey->dsa_pub; key->rsa = publickey->rsa_pub; +#else + key->key = publickey->key_pub; +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_userauth_agent_publickey(session, username, key); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = NULL; key->rsa = NULL; +#else + key->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(key); return rc; } -#endif /* _WIN32 */ -ssh_kbdint ssh_kbdint_new(void) { +ssh_kbdint ssh_kbdint_new(void) +{ ssh_kbdint kbd; kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); @@ -1337,7 +1439,8 @@ ssh_kbdint ssh_kbdint_new(void) { } -void ssh_kbdint_free(ssh_kbdint kbd) { +void ssh_kbdint_free(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1373,7 +1476,8 @@ void ssh_kbdint_free(ssh_kbdint kbd) { SAFE_FREE(kbd); } -void ssh_kbdint_clean(ssh_kbdint kbd) { +void ssh_kbdint_clean(ssh_kbdint kbd) +{ size_t i, n; if (kbd == NULL) { @@ -1651,7 +1755,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, - const char *submethods) { + const char *submethods) +{ int rc = SSH_AUTH_ERROR; if (session == NULL) { @@ -1693,7 +1798,8 @@ int ssh_userauth_kbdint(ssh_session session, const char *user, * * @returns The number of prompts. */ -int ssh_userauth_kbdint_getnprompts(ssh_session session) { +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ if (session == NULL) { return SSH_ERROR; } @@ -1715,7 +1821,8 @@ int ssh_userauth_kbdint_getnprompts(ssh_session session) { * * @returns The name of the message block. Do not free it. */ -const char *ssh_userauth_kbdint_getname(ssh_session session) { +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ if (session == NULL) { return NULL; } @@ -1738,7 +1845,8 @@ const char *ssh_userauth_kbdint_getname(ssh_session session) { * @returns The instruction of the message block. */ -const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1773,8 +1881,9 @@ const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { * if (echo) ... * @endcode */ -const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, - char *echo) { +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ if (session == NULL) return NULL; if (session->kbdint == NULL) { @@ -1801,7 +1910,8 @@ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, * * @returns The number of answers. */ -int ssh_userauth_kbdint_getnanswers(ssh_session session) { +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ if (session == NULL || session->kbdint == NULL) { return SSH_ERROR; } @@ -1809,7 +1919,7 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) { } /** - * @brief Get the answer for a question from a message block. + * @brief Get the answer to a question from a message block. * * @param[in] session The ssh session to use. * @@ -1817,7 +1927,8 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) { * * @return 0 on success, < 0 on error. */ -const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ if (session == NULL || session->kbdint == NULL || session->kbdint->answers == NULL) { return NULL; @@ -1848,8 +1959,10 @@ const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { * * @return 0 on success, < 0 on error. */ -int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, - const char *answer) { +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ if (session == NULL) { return -1; } @@ -1895,7 +2008,8 @@ int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_userauth_gssapi(ssh_session session) { +int ssh_userauth_gssapi(ssh_session session) +{ int rc = SSH_AUTH_DENIED; #ifdef WITH_GSSAPI switch(session->pending_call_state) { diff --git a/libssh/src/bignum.c b/libssh/src/bignum.c index ef8de31..e9b9519 100644 --- a/libssh/src/bignum.c +++ b/libssh/src/bignum.c @@ -86,7 +86,7 @@ void ssh_print_bignum(const char *name, const_bignum num) if (num != NULL) { bignum_bn2hex(num, &hex); } - fprintf(stderr, "%s value: %s\n", name, (hex == NULL) ? "(null)" : (char *) hex); + SSH_LOG(SSH_LOG_DEBUG, "%s value: %s\n", name, (hex == NULL) ? "(null)" : (char *) hex); #ifdef HAVE_LIBGCRYPT SAFE_FREE(hex); #elif defined HAVE_LIBCRYPTO diff --git a/libssh/src/bind.c b/libssh/src/bind.c index fa8df9e..77acbb6 100644 --- a/libssh/src/bind.c +++ b/libssh/src/bind.c @@ -79,6 +79,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, int opt = 1; socket_t s; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ZERO_STRUCT(hints); @@ -98,7 +99,8 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ai->ai_socktype, ai->ai_protocol); if (s == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(sshbind, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); return -1; } @@ -108,7 +110,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, ssh_set_error(sshbind, SSH_FATAL, "Setting socket options failed: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -120,7 +122,7 @@ static socket_t bind_socket(ssh_bind sshbind, const char *hostname, "Binding to %s:%d: %s", hostname, port, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); freeaddrinfo (ai); CLOSE_SOCKET(s); return -1; @@ -280,9 +282,10 @@ int ssh_bind_listen(ssh_bind sshbind) { } 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, strerror(errno)); + fd, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); CLOSE_SOCKET(fd); ssh_key_free(sshbind->dsa); sshbind->dsa = NULL; @@ -339,7 +342,7 @@ static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, } /** @internal - * @brief returns the current poll handle, or create it + * @brief returns the current poll handle, or creates it * @param sshbind the ssh_bind object * @returns a ssh_poll handle suitable for operation */ @@ -393,6 +396,7 @@ void ssh_bind_free(ssh_bind sshbind){ /* options */ SAFE_FREE(sshbind->banner); + SAFE_FREE(sshbind->moduli_file); SAFE_FREE(sshbind->bindaddr); SAFE_FREE(sshbind->config_dir); SAFE_FREE(sshbind->pubkey_accepted_key_types); @@ -485,8 +489,25 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ } session->common.log_verbosity = sshbind->common.log_verbosity; - if(sshbind->banner != NULL) - session->opts.custombanner = strdup(sshbind->banner); + + if (sshbind->banner != NULL) { + session->opts.custombanner = strdup(sshbind->banner); + if (session->opts.custombanner == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->moduli_file != NULL) { + session->opts.moduli_file = strdup(sshbind->moduli_file); + if (session->opts.moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + session->opts.rsa_min_size = sshbind->rsa_min_size; + ssh_socket_free(session->socket); session->socket = ssh_socket_new(session); if (session->socket == NULL) { @@ -550,34 +571,44 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd){ return SSH_OK; } -int ssh_bind_accept(ssh_bind sshbind, ssh_session session) { - socket_t fd = SSH_INVALID_SOCKET; - int rc; - if (sshbind->bindfd == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, - "Can't accept new clients on a not bound socket."); - return SSH_ERROR; - } +int ssh_bind_accept(ssh_bind sshbind, ssh_session session) +{ + socket_t fd = SSH_INVALID_SOCKET; + int rc; - if (session == NULL){ - ssh_set_error(sshbind, SSH_FATAL,"session is null"); - return SSH_ERROR; - } + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Can't accept new clients on a not bound socket."); + return SSH_ERROR; + } - fd = accept(sshbind->bindfd, NULL, NULL); - if (fd == SSH_INVALID_SOCKET) { - ssh_set_error(sshbind, SSH_FATAL, - "Accepting a new connection: %s", - strerror(errno)); - return SSH_ERROR; - } - rc = ssh_bind_accept_fd(sshbind, session, fd); + if (session == NULL) { + ssh_set_error(sshbind, SSH_FATAL, "session is null"); + return SSH_ERROR; + } - if(rc == SSH_ERROR){ - CLOSE_SOCKET(fd); - ssh_socket_free(session->socket); - } - return rc; + fd = accept(sshbind->bindfd, NULL, NULL); + if (fd == SSH_INVALID_SOCKET) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == EINTR) { + ssh_set_error(sshbind, SSH_EINTR, + "Accepting a new connection (child signal error): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } else { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + return SSH_ERROR; + } + rc = ssh_bind_accept_fd(sshbind, session, fd); + + if (rc == SSH_ERROR) { + CLOSE_SOCKET(fd); + ssh_socket_free(session->socket); + } + + return rc; } diff --git a/libssh/src/bind_config.c b/libssh/src/bind_config.c index 14b84db..27c42c9 100644 --- a/libssh/src/bind_config.c +++ b/libssh/src/bind_config.c @@ -40,7 +40,9 @@ #include "libssh/server.h" #include "libssh/options.h" +#ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 1024 +#endif /* Flags used for the parser state */ #define PARSING 1 @@ -187,18 +189,29 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen); - -static void local_parse_file(ssh_bind bind, - const char *filename, - uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth); + +#define LIBSSH_BIND_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_bind bind, + const char *filename, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_BIND_CONF_MAX_DEPTH) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -211,7 +224,7 @@ static void local_parse_file(ssh_bind bind, while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen, depth); if (rv < 0) { fclose(f); return; @@ -226,7 +239,8 @@ static void local_parse_file(ssh_bind bind, static void local_parse_glob(ssh_bind bind, const char *fileglob, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { glob_t globbuf = { .gl_flags = 0, @@ -246,7 +260,7 @@ static void local_parse_glob(ssh_bind bind, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen); + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen, depth); } globfree(&globbuf); @@ -272,7 +286,8 @@ ssh_bind_config_parse_line(ssh_bind bind, const char *line, unsigned int count, uint32_t *parser_flags, - uint8_t *seen) + uint8_t *seen, + unsigned int depth) { enum ssh_bind_config_opcode_e opcode; const char *p = NULL; @@ -286,7 +301,12 @@ ssh_bind_config_parse_line(ssh_bind bind, return -1; } - if ((line == NULL) || (parser_flags == NULL)) { + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + if (parser_flags == NULL) { ssh_set_error_invalid(bind); return -1; } @@ -331,9 +351,9 @@ ssh_bind_config_parse_line(ssh_bind bind, p = ssh_config_get_str_tok(&s, NULL); if (p && (*parser_flags & PARSING)) { #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(bind, p, parser_flags, seen); + local_parse_glob(bind, p, parser_flags, seen, depth + 1); #else - local_parse_file(bind, p, parser_flags, seen); + local_parse_file(bind, p, parser_flags, seen, depth + 1); #endif /* HAVE_GLOB */ } break; @@ -626,7 +646,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) parser_flags = PARSING; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen); + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen, 0); if (rv) { fclose(f); return -1; @@ -636,3 +656,64 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) fclose(f); return 0; } + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0, line_len; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + SSH_LOG(SSH_LOG_DEBUG, "Reading bind configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parser_flags = PARSING; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, "Line %u too long: %u characters", + line_num, line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/libssh/src/buffer.c b/libssh/src/buffer.c index ce12f49..e006801 100644 --- a/libssh/src/buffer.c +++ b/libssh/src/buffer.c @@ -46,9 +46,9 @@ */ struct ssh_buffer_struct { bool secure; - size_t used; - size_t allocated; - size_t pos; + uint32_t used; + uint32_t allocated; + uint32_t pos; uint8_t *data; }; @@ -83,21 +83,21 @@ static void buffer_verify(ssh_buffer buf) if (buf->used > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: allocated %zu, used %zu\n", + "BUFFER ERROR: allocated %u, used %u\n", buf->allocated, buf->used); do_abort = true; } if (buf->pos > buf->used) { fprintf(stderr, - "BUFFER ERROR: position %zu, used %zu\n", + "BUFFER ERROR: position %u, used %u\n", buf->pos, buf->used); do_abort = true; } if (buf->pos > buf->allocated) { fprintf(stderr, - "BUFFER ERROR: position %zu, allocated %zu\n", + "BUFFER ERROR: position %u, allocated %u\n", buf->pos, buf->allocated); do_abort = true; @@ -129,7 +129,7 @@ struct ssh_buffer_struct *ssh_buffer_new(void) /* * Always preallocate 64 bytes. * - * -1 for ralloc_buffer magic. + * -1 for realloc_buffer magic. */ rc = ssh_buffer_allocate_size(buf, 64 - 1); if (rc != 0) { @@ -178,9 +178,9 @@ void ssh_buffer_set_secure(ssh_buffer buffer) buffer->secure = true; } -static int realloc_buffer(struct ssh_buffer_struct *buffer, size_t needed) +static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) { - size_t smallest = 1; + uint32_t smallest = 1; uint8_t *new = NULL; buffer_verify(buffer); @@ -693,7 +693,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 /** * @internal * - * @brief Get a 8 bits unsigned int out of the buffer and adjusts the read + * @brief Get a 8 bits unsigned int out of the buffer and adjust the read * pointer. * * @param[in] buffer The buffer to read. @@ -702,7 +702,7 @@ uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint3 * * @returns 0 if there is not enough data in buffer, 1 otherwise. */ -int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ +uint32_t ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); } @@ -717,7 +717,7 @@ int ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ * * @returns 0 if there is not enough data in buffer, 4 otherwise. */ -int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ +uint32_t ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); } /** @@ -732,12 +732,12 @@ int ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ * * @returns 0 if there is not enough data in buffer, 8 otherwise. */ -int ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ +uint32_t ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); } /** - * @brief Valdiates that the given length can be obtained from the buffer. + * @brief Validates that the given length can be obtained from the buffer. * * @param[in] buffer The buffer to read from. * @@ -757,7 +757,7 @@ int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) /** * @internal * - * @brief Get a SSH String out of the buffer and adjusts the read pointer. + * @brief Get an SSH String out of the buffer and adjust the read pointer. * * @param[in] buffer The buffer to read. * @@ -868,7 +868,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, va_arg(ap, bignum); /* * Use a fixed size for a bignum - * (they should normaly be around 32) + * (they should normally be around 32) */ needed_size += 64; break; @@ -900,7 +900,7 @@ static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, } } - rc = ssh_buffer_allocate_size(buffer, needed_size); + rc = ssh_buffer_allocate_size(buffer, (uint32_t)needed_size); if (rc != 0) { return SSH_ERROR; } @@ -1102,7 +1102,8 @@ int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, bignum *bignum; void **data; } o; - size_t len, rlen, max_len; + size_t len; + uint32_t rlen, max_len; ssh_string tmp_string = NULL; va_list ap_copy; size_t count; diff --git a/libssh/src/chachapoly.c b/libssh/src/chachapoly.c index 820e7f6..2cd2385 100644 --- a/libssh/src/chachapoly.c +++ b/libssh/src/chachapoly.c @@ -26,9 +26,8 @@ #include "libssh/chacha.h" #include "libssh/poly1305.h" #include "libssh/misc.h" +#include "libssh/chacha20-poly1305-common.h" -/* size of the keys k1 and k2 as defined in specs */ -#define CHACHA20_KEYLEN 32 struct chacha20_poly1305_keysched { /* key used for encrypting the length field*/ struct chacha_ctx k1; @@ -36,13 +35,6 @@ struct chacha20_poly1305_keysched { struct chacha_ctx k2; }; -#pragma pack(push, 1) -struct ssh_packet_header { - uint32_t length; - uint8_t payload[]; -}; -#pragma pack(pop) - static const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0}; static const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0}; @@ -73,7 +65,7 @@ static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, /** * @internal * - * @brief encrypts an outgoing packet with chacha20 and authenticate it + * @brief encrypts an outgoing packet with chacha20 and authenticates it * with poly1305. */ static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, @@ -172,7 +164,7 @@ static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, ssh_log_hexdump("received tag", mac, POLY1305_TAGLEN); #endif - cmp = memcmp(tag, mac, POLY1305_TAGLEN); + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); if(cmp != 0) { /* mac error */ SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error"); diff --git a/libssh/src/channels.c b/libssh/src/channels.c index 1041dbf..1d42280 100644 --- a/libssh/src/channels.c +++ b/libssh/src/channels.c @@ -29,6 +29,7 @@ #include #include #include +#include #ifdef HAVE_SYS_TIME_H #include #endif /* HAVE_SYS_TIME_H */ @@ -79,6 +80,9 @@ static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); * @param[in] session The ssh session to use. * * @return A pointer to a newly allocated channel, NULL on error. + * The channel needs to be freed with ssh_channel_free(). + * + * @see ssh_channel_free() */ ssh_channel ssh_channel_new(ssh_session session) { @@ -147,8 +151,9 @@ ssh_channel ssh_channel_new(ssh_session session) * * @return The new channel identifier. */ -uint32_t ssh_channel_new_id(ssh_session session) { - return ++(session->maxchannel); +uint32_t ssh_channel_new_id(ssh_session session) +{ + return ++(session->maxchannel); } /** @@ -245,6 +250,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " "state %d", channel->state); + SAFE_FREE(error); goto error; } @@ -262,7 +268,8 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ return SSH_PACKET_USED; } -static int ssh_channel_open_termination(void *c){ +static int ssh_channel_open_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->state != SSH_CHANNEL_STATE_OPENING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -369,7 +376,7 @@ channel_open(ssh_channel channel, if (channel->state == SSH_CHANNEL_STATE_OPEN) { err = SSH_OK; } else if (err != SSH_AGAIN) { - /* Messages were handled correctly, but he channel state is invalid */ + /* Messages were handled correctly, but the channel state is invalid */ err = SSH_ERROR; } @@ -396,7 +403,7 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { /** * @internal - * @brief grows the local window and send a packet to the other party + * @brief grows the local window and sends a packet to the other party * @param session SSH session * @param channel SSH channel * @param minimumsize The minimum acceptable size for the new window. @@ -409,7 +416,7 @@ static int grow_window(ssh_session session, uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; int rc; - if(new_window <= channel->local_window){ + if (new_window <= channel->local_window) { SSH_LOG(SSH_LOG_PROTOCOL, "growing window (channel %d:%d) to %d bytes : not needed (%d bytes)", channel->local_channel, channel->remote_channel, new_window, @@ -462,7 +469,8 @@ static int grow_window(ssh_session session, * @return The related ssh_channel, or NULL if the channel is * unknown or the packet is invalid. */ -static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) { +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) +{ ssh_channel channel; uint32_t chan; int rc; @@ -521,7 +529,7 @@ SSH_PACKET_CALLBACK(channel_rcv_data){ ssh_channel channel; ssh_string str; ssh_buffer buf; - size_t len; + uint32_t len; int is_stderr; int rest; (void)user; @@ -554,7 +562,7 @@ SSH_PACKET_CALLBACK(channel_rcv_data){ len = ssh_string_len(str); SSH_LOG(SSH_LOG_PACKET, - "Channel receiving %" PRIdS " bytes data in %d (local win=%d remote win=%d)", + "Channel receiving %u bytes data in %d (local win=%d remote win=%d)", len, is_stderr, channel->local_window, @@ -563,7 +571,7 @@ SSH_PACKET_CALLBACK(channel_rcv_data){ /* What shall we do in this case? Let's accept it anyway */ if (len > channel->local_window) { SSH_LOG(SSH_LOG_RARE, - "Data packet too big for our window(%" PRIdS " vs %d)", + "Data packet too big for our window(%u vs %d)", len, channel->local_window); } @@ -651,40 +659,55 @@ SSH_PACKET_CALLBACK(channel_rcv_eof) { return SSH_PACKET_USED; } +static bool ssh_channel_has_unread_data(ssh_channel channel) +{ + if (channel == NULL) { + return false; + } + + if ((channel->stdout_buffer && + ssh_buffer_get_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + ssh_buffer_get_len(channel->stderr_buffer) > 0)) + { + return true; + } + + return false; +} + SSH_PACKET_CALLBACK(channel_rcv_close) { - ssh_channel channel; - (void)user; - (void)type; + ssh_channel channel; + (void)user; + (void)type; - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Received close on channel (%d:%d)", - channel->local_channel, - channel->remote_channel); - - if ((channel->stdout_buffer && - ssh_buffer_get_len(channel->stdout_buffer) > 0) || - (channel->stderr_buffer && - ssh_buffer_get_len(channel->stderr_buffer) > 0)) { - channel->delayed_close = 1; - } else { - channel->state = SSH_CHANNEL_STATE_CLOSED; - } - if (channel->remote_eof == 0) { - SSH_LOG(SSH_LOG_PACKET, - "Remote host not polite enough to send an eof before close"); - } - channel->remote_eof = 1; - /* - * The remote eof doesn't break things if there was still data into read - * buffer because the eof is ignored until the buffer is empty. - */ + SSH_LOG(SSH_LOG_PACKET, + "Received close on channel (%d:%d)", + channel->local_channel, + channel->remote_channel); + + if (!ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } else { + channel->delayed_close = 1; + } + + if (channel->remote_eof == 0) { + SSH_LOG(SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + channel->remote_eof = 1; ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, @@ -692,17 +715,17 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { channel->session, channel); - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; - if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) - ssh_channel_do_free(channel); + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; + if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) + ssh_channel_do_free(channel); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } SSH_PACKET_CALLBACK(channel_rcv_request) { ssh_channel channel; char *request=NULL; - uint8_t status; + uint8_t want_reply; int rc; (void)user; (void)type; @@ -715,7 +738,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { rc = ssh_buffer_unpack(packet, "sb", &request, - &status); + &want_reply); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); return SSH_PACKET_USED; @@ -823,13 +846,34 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { } if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + SAFE_FREE(request); SSH_LOG(SSH_LOG_PROTOCOL, "Received an auth-agent-req request"); - ssh_callbacks_execute_list(channel->callbacks, - ssh_channel_callbacks, - channel_auth_agent_req_function, - channel->session, - channel); + + 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; } @@ -838,11 +882,11 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * client requests. That means we need to create a ssh message to be passed * to the user code handling ssh messages */ - ssh_message_handle_channel_request(session,channel,packet,request,status); + ssh_message_handle_channel_request(session,channel,packet,request,want_reply); #else SSH_LOG(SSH_LOG_WARNING, "Unhandled channel request %s", request); #endif - + SAFE_FREE(request); return SSH_PACKET_USED; @@ -855,7 +899,7 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { * FIXME is the window changed? */ int channel_default_bufferize(ssh_channel channel, - void *data, size_t len, + void *data, uint32_t len, bool is_stderr) { ssh_session session; @@ -872,7 +916,7 @@ int channel_default_bufferize(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "placing %zu bytes into channel buffer (%s)", + "placing %u bytes into channel buffer (%s)", len, is_stderr ? "stderr" : "stdout"); if (!is_stderr) { @@ -927,8 +971,9 @@ int channel_default_bufferize(ssh_channel channel, * @see ssh_channel_request_shell() * @see ssh_channel_request_exec() */ -int ssh_channel_open_session(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_open_session(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } @@ -954,8 +999,9 @@ int ssh_channel_open_session(ssh_channel channel) { * * @see ssh_channel_open_forward() */ -int ssh_channel_open_auth_agent(ssh_channel channel){ - if(channel == NULL) { +int ssh_channel_open_auth_agent(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } @@ -990,16 +1036,17 @@ int ssh_channel_open_auth_agent(ssh_channel channel){ * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; ssh_string str = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return rc; } @@ -1061,7 +1108,7 @@ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, * * @warning This function does not bind the local port and does not * automatically forward the content of a socket to the channel. - * You still have to use channel_read and channel_write for this. + * You still have to use ssh_channel_read and ssh_channel_write for this. * @warning Requires support of OpenSSH for UNIX domain socket forwarding. */ int ssh_channel_open_forward_unix(ssh_channel channel, @@ -1166,7 +1213,7 @@ void ssh_channel_free(ssh_channel channel) channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; /* The idea behind the flags is the following : it is well possible - * that a client closes a channel that stills exists on the server side. + * that a client closes a channel that still exists on the server side. * We definitively close the channel when we receive a close message *and* * the user closed it. */ @@ -1346,7 +1393,8 @@ int ssh_channel_close(ssh_channel channel) } /* this termination function waits for a window growing condition */ -static int ssh_channel_waitwindow_termination(void *c){ +static int ssh_channel_waitwindow_termination(void *c) +{ ssh_channel channel = (ssh_channel) c; if (channel->remote_window > 0 || channel->session->session_state == SSH_SESSION_STATE_ERROR || @@ -1359,7 +1407,8 @@ static int ssh_channel_waitwindow_termination(void *c){ /* This termination function waits until the session is not in blocked status * anymore, e.g. because of a key re-exchange. */ -static int ssh_waitsession_unblocked(void *s){ +static int ssh_waitsession_unblocked(void *s) +{ ssh_session session = (ssh_session)s; switch (session->session_state){ case SSH_SESSION_STATE_DH: @@ -1379,8 +1428,9 @@ static int ssh_waitsession_unblocked(void *s){ * SSH_ERROR On error. * SSH_AGAIN Timeout elapsed (or in nonblocking mode). */ -int ssh_channel_flush(ssh_channel channel){ - return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); +int ssh_channel_flush(ssh_channel channel) +{ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); } static int channel_write_common(ssh_channel channel, @@ -1463,7 +1513,7 @@ static int channel_write_common(ssh_channel channel, effectivelen = len; } - effectivelen = MIN(effectivelen, maxpacketlen);; + effectivelen = MIN(effectivelen, maxpacketlen); rc = ssh_buffer_pack(session->out_buffer, "bd", @@ -1501,7 +1551,7 @@ static int channel_write_common(ssh_channel channel, } SSH_LOG(SSH_LOG_PACKET, - "channel_write wrote %ld bytes", (long int) effectivelen); + "ssh_channel_write wrote %ld bytes", (long int) effectivelen); channel->remote_window -= effectivelen; len -= effectivelen; @@ -1529,7 +1579,7 @@ static int channel_write_common(ssh_channel channel, /** * @brief Get the remote window size. * - * This is the maximum amounts of bytes the remote side expects us to send + * This is the maximum amount of bytes the remote side expects us to send * before growing the window again. * * @param[in] channel The channel to query. @@ -1543,7 +1593,8 @@ static int channel_write_common(ssh_channel channel, * @warning A zero return value means ssh_channel_write (default settings) * will block until the window grows back. */ -uint32_t ssh_channel_window_size(ssh_channel channel) { +uint32_t ssh_channel_window_size(ssh_channel channel) +{ return channel->remote_window; } @@ -1560,8 +1611,9 @@ uint32_t ssh_channel_window_size(ssh_channel channel) { * * @see ssh_channel_read() */ -int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 0); +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 0); } /** @@ -1573,8 +1625,9 @@ int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) { * * @see ssh_channel_is_closed() */ -int ssh_channel_is_open(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_open(ssh_channel channel) +{ + if (channel == NULL) { return 0; } return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); @@ -1589,8 +1642,9 @@ int ssh_channel_is_open(ssh_channel channel) { * * @see ssh_channel_is_open() */ -int ssh_channel_is_closed(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_is_closed(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); @@ -1603,18 +1657,16 @@ int ssh_channel_is_closed(ssh_channel channel) { * * @return 0 if there is no EOF, nonzero otherwise. */ -int ssh_channel_is_eof(ssh_channel channel) { - if(channel == NULL) { - return SSH_ERROR; - } - if ((channel->stdout_buffer && - ssh_buffer_get_len(channel->stdout_buffer) > 0) || - (channel->stderr_buffer && - ssh_buffer_get_len(channel->stderr_buffer) > 0)) { - return 0; - } +int ssh_channel_is_eof(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + if (ssh_channel_has_unread_data(channel)) { + return 0; + } - return (channel->remote_eof != 0); + return (channel->remote_eof != 0); } /** @@ -1628,11 +1680,12 @@ int ssh_channel_is_eof(ssh_channel channel) { * in non-blocking mode. * @see ssh_set_blocking() */ -void ssh_channel_set_blocking(ssh_channel channel, int blocking) { - if(channel == NULL) { - return; - } - ssh_set_blocking(channel->session,blocking); +void ssh_channel_set_blocking(ssh_channel channel, int blocking) +{ + if (channel == NULL) { + return; + } + ssh_set_blocking(channel->session, blocking); } /** @@ -1696,7 +1749,8 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_failure){ return SSH_PACKET_USED; } -static int ssh_channel_request_termination(void *c){ +static int ssh_channel_request_termination(void *c) +{ ssh_channel channel = (ssh_channel)c; if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || channel->session->session_state == SSH_SESSION_STATE_ERROR) @@ -1706,7 +1760,8 @@ static int ssh_channel_request_termination(void *c){ } static int channel_request(ssh_channel channel, const char *request, - ssh_buffer buffer, int reply) { + ssh_buffer buffer, int reply) +{ ssh_session session = channel->session; int rc = SSH_ERROR; int ret; @@ -1792,7 +1847,7 @@ static int channel_request(ssh_channel channel, const char *request, /** * @brief Request a pty with a specific type and size. * - * @param[in] channel The channel to sent the request. + * @param[in] channel The channel to send the request. * * @param[in] terminal The terminal type ("vt100, xterm,..."). * @@ -1806,7 +1861,8 @@ static int channel_request(ssh_channel channel, const char *request, * to be done again. */ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, - int col, int row) { + int col, int row) +{ ssh_session session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1868,7 +1924,8 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, * * @see ssh_channel_request_pty_size() */ -int ssh_channel_request_pty(ssh_channel channel) { +int ssh_channel_request_pty(ssh_channel channel) +{ return ssh_channel_request_pty_size(channel, "xterm", 80, 24); } @@ -1884,10 +1941,11 @@ int ssh_channel_request_pty(ssh_channel channel) { * @return SSH_OK on success, SSH_ERROR if an error occurred. * * @warning Do not call it from a signal handler if you are not sure any other - * libssh function using the same channel/session is running at same - * time (not 100% threadsafe). + * libssh function using the same channel/session is running at the + * same time (not 100% threadsafe). */ -int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) +{ ssh_session session = channel->session; ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1926,8 +1984,9 @@ int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) { * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_shell(ssh_channel channel) { - if(channel == NULL) { +int ssh_channel_request_shell(ssh_channel channel) +{ + if (channel == NULL) { return SSH_ERROR; } @@ -1948,7 +2007,8 @@ int ssh_channel_request_shell(ssh_channel channel) { * * @warning You normally don't have to call it for sftp, see sftp_new(). */ -int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -1997,14 +2057,16 @@ int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) { * * @note You should use sftp_new() which does this for you. */ -int ssh_channel_request_sftp( ssh_channel channel){ +int ssh_channel_request_sftp( ssh_channel channel) +{ if(channel == NULL) { return SSH_ERROR; } return ssh_channel_request_subsystem(channel, "sftp"); } -static char *generate_cookie(void) { +static char *generate_cookie(void) +{ static const char *hex = "0123456789abcdef"; char s[36]; unsigned char rnd[16]; @@ -2028,7 +2090,7 @@ static char *generate_cookie(void) { * @brief Sends the "x11-req" channel request over an existing session channel. * * This will enable redirecting the display of the remote X11 applications to - * local X server over an secure tunnel. + * local X server over a secure tunnel. * * @param[in] channel An existing session channel where the remote X11 * applications are going to be executed. @@ -2050,7 +2112,8 @@ static char *generate_cookie(void) { * to be done again. */ int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, - const char *cookie, int screen_number) { + const char *cookie, int screen_number) +{ ssh_buffer buffer = NULL; char *c = NULL; int rc = SSH_ERROR; @@ -2101,7 +2164,8 @@ int ssh_channel_request_x11(ssh_channel channel, int single_connection, const ch } static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, - int timeout_ms, int *destination_port) { + int timeout_ms, int *destination_port, char **originator, int *originator_port) +{ #ifndef _WIN32 static const struct timespec ts = { .tv_sec = 0, @@ -2135,6 +2199,12 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, if(destination_port) { *destination_port=msg->channel_request_open.destination_port; } + if(originator) { + *originator=strdup(msg->channel_request_open.originator); + } + if(originator_port) { + *originator_port=msg->channel_request_open.originator_port; + } ssh_message_free(msg); return channel; @@ -2165,8 +2235,9 @@ static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, * @return A newly created channel, or NULL if no X11 request from * the server. */ -ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) { - return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL); +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) +{ + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL); } /** @@ -2235,7 +2306,8 @@ SSH_PACKET_CALLBACK(ssh_request_denied){ } -static int ssh_global_request_termination(void *s){ +static int ssh_global_request_termination(void *s) +{ ssh_session session = (ssh_session) s; if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || session->session_state == SSH_SESSION_STATE_ERROR) @@ -2414,17 +2486,19 @@ int ssh_channel_listen_forward(ssh_session session, } /* DEPRECATED */ -int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) { +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) +{ return ssh_channel_listen_forward(session, address, port, bound_port); } /* DEPRECATED */ -ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL); +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) +{ + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL); } /** - * @brief Accept an incoming TCP/IP forwarding channel and get information + * @brief Accept an incoming TCP/IP forwarding channel and get some information * about incomming connection * @param[in] session The ssh session to use. * @@ -2436,7 +2510,30 @@ ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) { * the server */ ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) { - return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port); + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incomming connection + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[out] destination_port A pointer to destination port or NULL. + * + * @param[out] originator A pointer to a pointer to a string of originator host or NULL. + * That the caller is responsible for to ssh_string_free_char(). + * + * @param[out] originator_port A pointer to originator port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + * + * @see ssh_string_free_char() + */ +ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port); } /** @@ -2486,7 +2583,8 @@ int ssh_channel_cancel_forward(ssh_session session, } /* DEPRECATED */ -int ssh_forward_cancel(ssh_session session, const char *address, int port) { +int ssh_forward_cancel(ssh_session session, const char *address, int port) +{ return ssh_channel_cancel_forward(session, address, port); } @@ -2505,7 +2603,8 @@ int ssh_forward_cancel(ssh_session session, const char *address, int port) { * to be done again. * @warning Some environment variables may be refused by security reasons. */ -int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) { +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2575,7 +2674,8 @@ int ssh_channel_request_env(ssh_channel channel, const char *name, const char *v * * @see ssh_channel_request_shell() */ -int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2619,10 +2719,6 @@ int ssh_channel_request_exec(ssh_channel channel, const char *cmd) { * Sends a signal 'sig' to the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). - * - * OpenSSH doesn't support signals yet, see: - * https://bugzilla.mindrot.org/show_bug.cgi?id=1424 * * @param[in] channel The channel to send signal. * @@ -2642,17 +2738,17 @@ 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 - * (including attempts to send signal via SSH-v1 session). + * @return SSH_OK on success, SSH_ERROR if an error occurred. */ -int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; - if(channel == NULL) { + if (channel == NULL) { return SSH_ERROR; } - if(sig == NULL) { + if (sig == NULL) { ssh_set_error_invalid(channel->session); return rc; } @@ -2682,16 +2778,15 @@ int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) { * Sends a break signal to the remote process. * Note, that remote system may not support breaks. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported. * * @param[in] channel The channel to send the break to. * * @param[in] length The break-length in milliseconds to send. * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ -int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { +int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -2724,7 +2819,7 @@ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { * * @param[in] channel The channel to read from. * - * @param[in] buffer The buffer which will get the data. + * @param[out] buffer The buffer which will get the data. * * @param[in] count The count of bytes to be read. If it is bigger than 0, * the exact size will be read, else (bytes=0) it will @@ -2739,9 +2834,10 @@ int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) { * @see ssh_channel_read */ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, - int is_stderr) { + int is_stderr) +{ ssh_session session; - char buffer_tmp[8192]; + char *buffer_tmp = NULL; int r; uint32_t total=0; @@ -2763,14 +2859,19 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, return r; } if(r > 0){ + count = r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } r=ssh_channel_read(channel, buffer_tmp, r, is_stderr); if(r < 0){ + ssh_buffer_pass_bytes_end(buffer, count); return r; } - if(ssh_buffer_add_data(buffer,buffer_tmp,r) < 0){ - ssh_set_error_oom(session); - r = SSH_ERROR; - } + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - r); return r; } @@ -2780,19 +2881,23 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); } while (r == 0); } + + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } while(total < count){ - r=ssh_channel_read(channel, buffer_tmp, sizeof(buffer_tmp), is_stderr); + r=ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); if(r<0){ + ssh_buffer_pass_bytes_end(buffer, count); return r; } if(r==0){ + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - total); return total; } - if (ssh_buffer_add_data(buffer,buffer_tmp,r) < 0) { - ssh_set_error_oom(session); - - return SSH_ERROR; - } total += r; } @@ -2805,7 +2910,8 @@ struct ssh_channel_read_termination_struct { ssh_buffer buffer; }; -static int ssh_channel_read_termination(void *s){ +static int ssh_channel_read_termination(void *s) +{ struct ssh_channel_read_termination_struct *ctx = s; if (ssh_buffer_get_len(ctx->buffer) >= ctx->count || ctx->channel->remote_eof || @@ -2815,28 +2921,25 @@ static int ssh_channel_read_termination(void *s){ return 0; } -/* TODO FIXME Fix the delayed close thing */ -/* TODO FIXME Fix the blocking behaviours */ +/* TODO: FIXME Fix the blocking behaviours */ /** * @brief Reads data from a channel. * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * * @param[in] is_stderr A boolean value to mark reading from the stderr flow. * * @return The number of bytes read, 0 on end of file or SSH_ERROR - * on error. In nonblocking mode it Can return 0 if no data + * on error. In nonblocking mode it can return 0 if no data * is available or SSH_AGAIN. * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) { @@ -2852,7 +2955,7 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @param[in] channel The channel to read from. * - * @param[in] dest The destination buffer which will get the data. + * @param[out] dest The destination buffer which will get the data. * * @param[in] count The count of bytes to be read. * @@ -2867,8 +2970,6 @@ int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_std * * @warning This function may return less than count bytes of data, and won't * block until count bytes have been read. - * @warning The read function using a buffer has been renamed to - * channel_read_buffer(). */ int ssh_channel_read_timeout(ssh_channel channel, void *dest, @@ -2960,6 +3061,10 @@ int ssh_channel_read_timeout(ssh_channel channel, 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) { @@ -2978,7 +3083,7 @@ int ssh_channel_read_timeout(ssh_channel channel, * * @param[in] channel The channel to read from. * - * @param[in] dest A pointer to a destination buffer. + * @param[out] dest A pointer to a destination buffer. * * @param[in] count The count of bytes of data to be read. * @@ -2997,7 +3102,7 @@ int ssh_channel_read_nonblocking(ssh_channel channel, int is_stderr) { ssh_session session; - ssize_t to_read; + uint32_t to_read; int rc; int blocking; @@ -3011,22 +3116,24 @@ int ssh_channel_read_nonblocking(ssh_channel channel, session = channel->session; - to_read = ssh_channel_poll(channel, is_stderr); + rc = ssh_channel_poll(channel, is_stderr); - if (to_read <= 0) { + if (rc <= 0) { if (session->session_state == SSH_SESSION_STATE_ERROR){ return SSH_ERROR; } - return to_read; /* may be an error code */ + return rc; /* may be an error code */ } - if ((size_t)to_read > count) { - to_read = (ssize_t)count; + to_read = (unsigned int)rc; + + if (to_read > count) { + to_read = count; } blocking = ssh_is_blocking(session); ssh_set_blocking(session, 0); - rc = ssh_channel_read(channel, dest, (uint32_t)to_read, is_stderr); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); ssh_set_blocking(session,blocking); return rc; @@ -3041,15 +3148,18 @@ int ssh_channel_read_nonblocking(ssh_channel channel, * * @return The number of bytes available for reading, 0 if nothing * is available or SSH_ERROR on error. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * @warning When the channel is in EOF state, the function returns SSH_EOF. * * @see ssh_channel_is_eof() */ -int ssh_channel_poll(ssh_channel channel, int is_stderr){ +int ssh_channel_poll(ssh_channel channel, int is_stderr) +{ ssh_buffer stdbuf; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3095,6 +3205,7 @@ int ssh_channel_poll(ssh_channel channel, int is_stderr){ * SSH_ERROR on error. * * @warning When the channel is in EOF state, the function returns SSH_EOF. + * When a channel is freed the function returns SSH_ERROR immediately. * * @see ssh_channel_is_eof() */ @@ -3106,7 +3217,7 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) size_t len; int rc; - if (channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } @@ -3158,15 +3269,17 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) * * @return The session pointer. */ -ssh_session ssh_channel_get_session(ssh_channel channel) { - if(channel == NULL) { +ssh_session ssh_channel_get_session(ssh_channel channel) +{ + if (channel == NULL) { return NULL; } return channel->session; } -static int ssh_channel_exit_status_termination(void *c){ +static int ssh_channel_exit_status_termination(void *c) +{ ssh_channel channel = c; if(channel->exit_status != -1 || /* When a channel is closed, no exit status message can @@ -3188,15 +3301,18 @@ static int ssh_channel_exit_status_termination(void *c){ * (yet), or SSH_ERROR on error. * @warning This function may block until a timeout (or never) * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * SSH_ERROR immediately. * * If you're looking for an async handling of this register a callback for the * exit status. * * @see ssh_channel_exit_status_callback */ -int ssh_channel_get_exit_status(ssh_channel channel) { +int ssh_channel_get_exit_status(ssh_channel channel) +{ int rc; - if(channel == NULL) { + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { return SSH_ERROR; } rc = ssh_handle_packets_termination(channel->session, @@ -3218,8 +3334,11 @@ int ssh_channel_get_exit_status(ssh_channel channel) { * This is made in two parts: protocol select and network select. The protocol * select does not use the network functions at all */ -static int channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, - ssh_channel *echans, ssh_channel *rout, ssh_channel *wout, ssh_channel *eout) { +static int +channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, + ssh_channel *wout, ssh_channel *eout) +{ ssh_channel chan; int i; int j = 0; @@ -3299,7 +3418,8 @@ static size_t count_ptrs(ssh_channel *ptrs) * function, or SSH_ERROR on error. */ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, - ssh_channel *exceptchans, struct timeval * timeout) { + ssh_channel *exceptchans, struct timeval * timeout) +{ ssh_channel *rchans, *wchans, *echans; ssh_channel dummy = NULL; ssh_event event = NULL; @@ -3433,7 +3553,8 @@ int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, * @param[in] counter Counter for bytes handled by the channel. */ void ssh_channel_set_counter(ssh_channel channel, - ssh_counter counter) { + ssh_counter counter) +{ if (channel != NULL) { channel->counter = counter; } @@ -3452,8 +3573,9 @@ void ssh_channel_set_counter(ssh_channel channel, * * @see ssh_channel_read() */ -int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return channel_write_common(channel, data, len, 1); +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 1); } #if WITH_SERVER @@ -3480,10 +3602,11 @@ int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len * * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use ssh_channel_read and ssh_channel_write for this. */ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, - int remoteport, const char *sourcehost, int localport) { + int remoteport, const char *sourcehost, int localport) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3543,10 +3666,11 @@ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost * to be done again. * @warning This function does not bind the local port and does not automatically * forward the content of a socket to the channel. You still have to - * use channel_read and channel_write for this. + * use shh_channel_read and ssh_channel_write for this. */ -int ssh_channel_open_x11(ssh_channel channel, - const char *orig_addr, int orig_port) { +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) +{ ssh_session session; ssh_buffer payload = NULL; int rc = SSH_ERROR; @@ -3595,16 +3719,15 @@ int ssh_channel_open_x11(ssh_channel channel, * * Sends the exit status to the remote process (as described in RFC 4254, * section 6.10). - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send exit status. * * @param[in] exit_status The exit status to send * * @return SSH_OK on success, SSH_ERROR if an error occurred. - * (including attempts to send exit status via SSH-v1 session). */ -int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; @@ -3636,7 +3759,6 @@ int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { * This sends the exit status of the remote process. * Note, that remote system may not support signals concept. * In such a case this request will be silently ignored. - * Only SSH-v2 is supported (I'm not sure about SSH-v1). * * @param[in] channel The channel to send signal. * @@ -3647,10 +3769,10 @@ int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) { * @param[in] lang The language used in the message (format: RFC 3066) * * @return SSH_OK on success, SSH_ERROR if an error occurred - * (including attempts to send signal via SSH-v1 session). */ int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, - int core, const char *errmsg, const char *lang) { + int core, const char *errmsg, const char *lang) +{ ssh_buffer buffer = NULL; int rc = SSH_ERROR; diff --git a/libssh/src/client.c b/libssh/src/client.c index a4a7317..a35a28e 100644 --- a/libssh/src/client.c +++ b/libssh/src/client.c @@ -60,7 +60,8 @@ * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR * @param user is a pointer to session */ -static void socket_callback_connected(int code, int errno_code, void *user){ +static void socket_callback_connected(int code, int errno_code, void *user) +{ ssh_session session=(ssh_session)user; if (session->session_state != SSH_SESSION_STATE_CONNECTING && @@ -76,8 +77,10 @@ static void socket_callback_connected(int code, int errno_code, void *user){ if(code == SSH_SOCKET_CONNECTED_OK) session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; session->session_state=SSH_SESSION_STATE_ERROR; - ssh_set_error(session,SSH_FATAL,"%s",strerror(errno_code)); + ssh_set_error(session,SSH_FATAL,"%s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); } @@ -93,12 +96,12 @@ static void socket_callback_connected(int code, int errno_code, void *user){ * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) +static size_t callback_receive_banner(const void *data, size_t len, void *user) { char *buffer = (char *)data; - ssh_session session=(ssh_session) user; + ssh_session session = (ssh_session) user; char *str = NULL; - size_t i; + uint32_t i; int ret=0; if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { @@ -106,7 +109,7 @@ static int callback_receive_banner(const void *data, size_t len, void *user) "Wrong state in callback_receive_banner : %d", session->session_state); - return SSH_ERROR; + return 0; } for (i = 0; i < len; ++i) { #ifdef WITH_PCAP @@ -243,8 +246,8 @@ int ssh_send_banner(ssh_session session, int server) * @warning this function returning is no proof that DH handshake is * completed */ -static int dh_handshake(ssh_session session) { - +static int dh_handshake(ssh_session session) +{ int rc = SSH_AGAIN; switch (session->dh_handshake_state) { @@ -299,13 +302,15 @@ static int dh_handshake(ssh_session session) { return rc; } -static int ssh_service_request_termination(void *s){ - ssh_session session = (ssh_session)s; - if(session->session_state == SSH_SESSION_STATE_ERROR || - session->auth.service_state != SSH_AUTH_SERVICE_SENT) - return 1; - else - return 0; +static int ssh_service_request_termination(void *s) +{ + ssh_session session = (ssh_session)s; + + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->auth.service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; } /** @@ -323,8 +328,9 @@ static int ssh_service_request_termination(void *s){ * @return SSH_AGAIN No response received yet * @bug actually only works with ssh-userauth */ -int ssh_service_request(ssh_session session, const char *service) { - int rc=SSH_ERROR; +int ssh_service_request(ssh_session session, const char *service) +{ + int rc = SSH_ERROR; if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) goto pending; @@ -481,16 +487,18 @@ static void ssh_client_connection_callback(ssh_session session) /** @internal * @brief describe under which conditions the ssh_connect function may stop */ -static int ssh_connect_termination(void *user){ - ssh_session session = (ssh_session)user; - switch(session->session_state){ +static int ssh_connect_termination(void *user) +{ + ssh_session session = (ssh_session)user; + + switch (session->session_state) { case SSH_SESSION_STATE_ERROR: case SSH_SESSION_STATE_AUTHENTICATING: case SSH_SESSION_STATE_DISCONNECTED: - return 1; + return 1; default: - return 0; - } + return 0; + } } /** @@ -649,12 +657,13 @@ int ssh_connect(ssh_session session) * * @return A newly allocated string with the banner, NULL on error. */ -char *ssh_get_issue_banner(ssh_session session) { - if (session == NULL || session->banner == NULL) { - return NULL; - } +char *ssh_get_issue_banner(ssh_session session) +{ + if (session == NULL || session->banner == NULL) { + return NULL; + } - return ssh_string_to_char(session->banner); + return ssh_string_to_char(session->banner); } /** @@ -675,103 +684,159 @@ char *ssh_get_issue_banner(ssh_session session) { * } * @endcode */ -int ssh_get_openssh_version(ssh_session session) { - if (session == NULL) { - return 0; - } +int ssh_get_openssh_version(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return session->openssh; +} +/** + * @brief Add disconnect message when ssh_session is disconnected + * To add a disconnect message to give peer a better hint. + * @param session The SSH session to use. + * @param message The message to send after the session is disconnected. + * If no message is passed then a default message i.e + * "Bye Bye" will be sent. + */ +int +ssh_session_set_disconnect_message(ssh_session session, const char *message) +{ + if (session == NULL) { + return SSH_ERROR; + } - return session->openssh; + if (message == NULL || strlen(message) == 0) { + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; + } + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup(message); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; } + /** * @brief Disconnect from a session (client or server). + * * The session can then be reused to open a new session. * + * @note Note that this function wont close the socket if it was set with + * @ssh_options_set and SSH_OPTIONS_FD. You're responsible for closing the + * socket. This is new behavior in libssh 0.10. + * * @param[in] session The SSH session to use. */ -void ssh_disconnect(ssh_session session) { - struct ssh_iterator *it; - int rc; +void +ssh_disconnect(ssh_session session) +{ + struct ssh_iterator *it; + int rc; - if (session == NULL) { - return; - } + if (session == NULL) { + return; + } - if (session->socket != NULL && ssh_socket_is_open(session->socket)) { - rc = ssh_buffer_pack(session->out_buffer, - "bdss", - SSH2_MSG_DISCONNECT, - SSH2_DISCONNECT_BY_APPLICATION, - "Bye Bye", - ""); /* language tag */ - if (rc != SSH_OK){ - ssh_set_error_oom(session); - goto error; + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + ssh_packet_send(session); + /* 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_packet_send(session); - ssh_socket_close(session->socket); - } error: - session->recv_seq = 0; - session->send_seq = 0; - session->alive = 0; - if (session->socket != NULL){ - ssh_socket_reset(session->socket); - } - session->opts.fd = SSH_INVALID_SOCKET; - session->session_state=SSH_SESSION_STATE_DISCONNECTED; - session->pending_call_state = SSH_PENDING_CALL_NONE; + session->recv_seq = 0; + session->send_seq = 0; + session->alive = 0; + if (session->socket != NULL){ + ssh_socket_reset(session->socket); + } + session->opts.fd = SSH_INVALID_SOCKET; + session->session_state = SSH_SESSION_STATE_DISCONNECTED; + session->pending_call_state = SSH_PENDING_CALL_NONE; - while ((it=ssh_list_get_iterator(session->channels)) != NULL) { - ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); - ssh_list_remove(session->channels, it); - } - if(session->current_crypto){ - crypto_free(session->current_crypto); - session->current_crypto=NULL; - } - if (session->next_crypto) { - crypto_free(session->next_crypto); - session->next_crypto = crypto_new(); - if (session->next_crypto == NULL) { - ssh_set_error_oom(session); + while ((it = ssh_list_get_iterator(session->channels)) != NULL) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); + ssh_list_remove(session->channels, it); } - } - if (session->in_buffer) { - ssh_buffer_reinit(session->in_buffer); - } - if (session->out_buffer) { - ssh_buffer_reinit(session->out_buffer); - } - if (session->in_hashbuf) { - ssh_buffer_reinit(session->in_hashbuf); - } - if (session->out_hashbuf) { - ssh_buffer_reinit(session->out_hashbuf); - } - session->auth.supported_methods = 0; - SAFE_FREE(session->serverbanner); - SAFE_FREE(session->clientbanner); - - if(session->ssh_message_list){ - ssh_message msg; - while((msg=ssh_list_pop_head(ssh_message ,session->ssh_message_list)) - != NULL){ - ssh_message_free(msg); - } - ssh_list_free(session->ssh_message_list); - session->ssh_message_list=NULL; - } + if (session->current_crypto) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + if (session->next_crypto) { + crypto_free(session->next_crypto); + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + } + } + if (session->in_buffer) { + ssh_buffer_reinit(session->in_buffer); + } + if (session->out_buffer) { + ssh_buffer_reinit(session->out_buffer); + } + if (session->in_hashbuf) { + ssh_buffer_reinit(session->in_hashbuf); + } + if (session->out_hashbuf) { + ssh_buffer_reinit(session->out_hashbuf); + } + session->auth.supported_methods = 0; + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->disconnect_message); - if (session->packet_callbacks){ - ssh_list_free(session->packet_callbacks); - session->packet_callbacks=NULL; - } + if (session->ssh_message_list) { + ssh_message msg = NULL; + + while ((msg = ssh_list_pop_head(ssh_message, + session->ssh_message_list)) != NULL) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + session->ssh_message_list = NULL; + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + session->packet_callbacks = NULL; + } } -const char *ssh_copyright(void) { - return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2021 " +const char *ssh_copyright(void) +{ + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2022 " "Aris Adamantiadis, Andreas Schneider " "and libssh contributors. " "Distributed under the LGPL, please refer to COPYING " diff --git a/libssh/src/config.c b/libssh/src/config.c index 54ada27..41ba105 100644 --- a/libssh/src/config.c +++ b/libssh/src/config.c @@ -32,6 +32,14 @@ #endif #include #include +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +#endif #include "libssh/config_parser.h" #include "libssh/config.h" @@ -40,7 +48,9 @@ #include "libssh/misc.h" #include "libssh/options.h" +#ifndef MAX_LINE_SIZE #define MAX_LINE_SIZE 1024 +#endif struct ssh_config_keyword_table_s { const char *name; @@ -58,7 +68,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "macs", SOC_MACS }, { "compression", SOC_COMPRESSION }, { "connecttimeout", SOC_TIMEOUT }, - { "protocol", SOC_PROTOCOL }, { "stricthostkeychecking", SOC_STRICTHOSTKEYCHECK }, { "userknownhostsfile", SOC_KNOWNHOSTS }, { "proxycommand", SOC_PROXYCOMMAND }, @@ -71,7 +80,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "loglevel", SOC_LOGLEVEL}, { "hostkeyalgorithms", SOC_HOSTKEYALGORITHMS}, { "kexalgorithms", SOC_KEXALGORITHMS}, - { "mac", SOC_UNSUPPORTED}, /* SSHv1 */ { "gssapiauthentication", SOC_GSSAPIAUTHENTICATION}, { "kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION}, { "passwordauthentication", SOC_PASSWORDAUTHENTICATION}, @@ -85,23 +93,18 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "canonicalizemaxdots", SOC_UNSUPPORTED}, { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, { "certificatefile", SOC_UNSUPPORTED}, - { "challengeresponseauthentication", SOC_UNSUPPORTED}, + { "kbdinteractiveauthentication", SOC_UNSUPPORTED}, { "checkhostip", SOC_UNSUPPORTED}, - { "cipher", SOC_UNSUPPORTED}, /* SSHv1 */ - { "compressionlevel", SOC_UNSUPPORTED}, /* SSHv1 */ { "connectionattempts", SOC_UNSUPPORTED}, { "enablesshkeysign", SOC_UNSUPPORTED}, { "fingerprinthash", SOC_UNSUPPORTED}, { "forwardagent", SOC_UNSUPPORTED}, - { "gssapikeyexchange", SOC_UNSUPPORTED}, - { "gssapirenewalforcesrekey", SOC_UNSUPPORTED}, - { "gssapitrustdns", SOC_UNSUPPORTED}, { "hashknownhosts", SOC_UNSUPPORTED}, { "hostbasedauthentication", SOC_UNSUPPORTED}, - { "hostbasedkeytypes", SOC_UNSUPPORTED}, + { "hostbasedacceptedalgorithms", SOC_UNSUPPORTED}, { "hostkeyalias", SOC_UNSUPPORTED}, { "identitiesonly", SOC_UNSUPPORTED}, - { "identityagent", SOC_UNSUPPORTED}, + { "identityagent", SOC_IDENTITYAGENT}, { "ipqos", SOC_UNSUPPORTED}, { "kbdinteractivedevices", SOC_UNSUPPORTED}, { "nohostauthenticationforlocalhost", SOC_UNSUPPORTED}, @@ -110,12 +113,10 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "preferredauthentications", SOC_UNSUPPORTED}, { "proxyjump", SOC_PROXYJUMP}, { "proxyusefdpass", SOC_UNSUPPORTED}, - { "pubkeyacceptedtypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES}, { "rekeylimit", SOC_REKEYLIMIT}, { "remotecommand", SOC_UNSUPPORTED}, { "revokedhostkeys", SOC_UNSUPPORTED}, - { "rhostsrsaauthentication", SOC_UNSUPPORTED}, - { "rsaauthentication", SOC_UNSUPPORTED}, /* SSHv1 */ { "serveralivecountmax", SOC_UNSUPPORTED}, { "serveraliveinterval", SOC_UNSUPPORTED}, { "streamlocalbindmask", SOC_UNSUPPORTED}, @@ -123,7 +124,6 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "syslogfacility", SOC_UNSUPPORTED}, { "tcpkeepalive", SOC_UNSUPPORTED}, { "updatehostkeys", SOC_UNSUPPORTED}, - { "useprivilegedport", SOC_UNSUPPORTED}, { "verifyhostkeydns", SOC_UNSUPPORTED}, { "visualhostkey", SOC_UNSUPPORTED}, { "clearallforwardings", SOC_NA}, @@ -147,7 +147,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "tunnel", SOC_NA}, { "tunneldevice", SOC_NA}, { "xauthlocation", SOC_NA}, - { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDTYPES}, + { "pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES}, { NULL, SOC_UNKNOWN } }; @@ -181,7 +181,7 @@ static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] }; static int ssh_config_parse_line(ssh_session session, const char *line, - unsigned int count, int *parsing); + unsigned int count, int *parsing, unsigned int depth, bool global); static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { int i; @@ -195,16 +195,26 @@ static enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) { return SOC_UNKNOWN; } +#define LIBSSH_CONF_MAX_DEPTH 16 static void local_parse_file(ssh_session session, const char *filename, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { FILE *f; char line[MAX_LINE_SIZE] = {0}; unsigned int count = 0; int rv; + if (depth > LIBSSH_CONF_MAX_DEPTH) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + f = fopen(filename, "r"); if (f == NULL) { SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", @@ -215,7 +225,7 @@ local_parse_file(ssh_session session, SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename); while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, parsing); + rv = ssh_config_parse_line(session, line, count, parsing, depth, global); if (rv < 0) { fclose(f); return; @@ -229,7 +239,9 @@ local_parse_file(ssh_session session, #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) static void local_parse_glob(ssh_session session, const char *fileglob, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { glob_t globbuf = { .gl_flags = 0, @@ -249,7 +261,7 @@ static void local_parse_glob(ssh_session session, } for (i = 0; i < globbuf.gl_pathc; i++) { - local_parse_file(session, globbuf.gl_pathv[i], parsing); + local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global); } globfree(&globbuf); @@ -287,6 +299,134 @@ 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 */ + +static int +ssh_exec_shell(char *cmd) +{ + char *shell = NULL; + pid_t pid; + int status, devnull, rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + shell = (char *)"/bin/sh"; + } + + rc = access(shell, X_OK); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell); + return -1; + } + + /* Need this to redirect subprocess stdin/out */ + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd); + pid = fork(); + if (pid == 0) { /* Child */ + char *argv[4]; + + /* Redirect child stdin and stdout. Leave stderr */ + rc = dup2(devnull, STDIN_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + rc = dup2(devnull, STDOUT_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + if (devnull > STDERR_FILENO) { + close(devnull); + } + + argv[0] = shell; + argv[1] = (char *) "-c"; + argv[2] = strdup(cmd); + argv[3] = NULL; + + rc = execv(argv[0], argv); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + /* Die with signal to make this error apparent to parent. */ + signal(SIGTERM, SIG_DFL); + kill(getpid(), SIGTERM); + _exit(1); + } + } + + /* Parent */ + close(devnull); + if (pid == -1) { /* Error */ + SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + + } + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_WARN, "Command %s exitted abnormally", cmd); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status)); + return WEXITSTATUS(status); +} + +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + int rv, result = 0; + char *cmd = NULL; + + /* TODO There should be more supported expansions */ + cmd = ssh_path_expand_escape(session, command); + if (cmd == NULL) { + return 0; + } + rv = ssh_exec_shell(cmd); + if (rv > 0 && negate == true) { + result = 1; + } else if (rv == 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)", + result == 1 ? "Matched" : "Not matched", cmd, + negate == true ? " (negated)" : "", rv); + free(cmd); + return result; +} +#endif /* _WIN32 */ + /* @brief: Parse the ProxyJump configuration line and if parsing, * stores the result in the configuration option */ @@ -377,11 +517,68 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) return rv; } +static char * +ssh_config_make_absolute(ssh_session session, + const char *path, + bool global) +{ + size_t outlen = 0; + char *out = NULL; + int rv; + + /* Looks like absolute path */ + if (path[0] == '/') { + return strdup(path); + } + + /* relative path */ + if (global) { + /* Parsing global config */ + outlen = strlen(path) + strlen("/etc/ssh/") + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "/etc/ssh/%s", path); + if (rv < 1) { + free(out); + return NULL; + } + return out; + } + + /* paths starting with tilde are already absolute */ + if (path[0] == '~') { + return ssh_path_expand_tilde(path); + } + + /* Parsing user config relative to home directory (generally ~/.ssh) */ + if (session->opts.sshdir == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path); + if (rv < 1) { + free(out); + return NULL; + } + return out; +} + static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, - int *parsing) + int *parsing, + unsigned int depth, + bool global) { enum ssh_config_opcode_e opcode; const char *p = NULL, *p2 = NULL; @@ -440,11 +637,19 @@ ssh_config_parse_line(ssh_session session, p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { + char *path = ssh_config_make_absolute(session, p, global); + if (path == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory " + "for the include path expansion", count); + SAFE_FREE(x); + return -1; + } #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) - local_parse_glob(session, p, parsing); + local_parse_glob(session, path, parsing, depth + 1, global); #else - local_parse_file(session, p, parsing); + local_parse_file(session, path, parsing, depth + 1, global); #endif /* HAVE_GLOB */ + free(path); } break; @@ -494,7 +699,7 @@ ssh_config_parse_line(ssh_session session, case MATCH_FINAL: case MATCH_CANONICAL: - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_INFO, "line %d: Unsupported Match keyword '%s', skipping", count, p); @@ -503,20 +708,22 @@ ssh_config_parse_line(ssh_session session, break; case MATCH_EXEC: - /* Skip to the end of line as unsupported */ - p = ssh_config_get_cmd(&s); + /* Skip one argument (including in quotes) */ + p = ssh_config_get_token(&s); if (p == NULL || p[0] == '\0') { SSH_LOG(SSH_LOG_WARN, "line %d: Match keyword " "'%s' requires argument", count, p2); SAFE_FREE(x); return -1; } + if (result != 1) { + SSH_LOG(SSH_LOG_INFO, "line %d: Skipped match exec " + "'%s' as previous conditions already failed.", + count, p2); + continue; + } + result &= ssh_match_exec(session, p, negate); args++; - SSH_LOG(SSH_LOG_WARN, - "line %d: Unsupported Match keyword '%s', ignoring", - count, - p2); - result = 0; break; case MATCH_LOCALUSER: @@ -551,7 +758,7 @@ ssh_config_parse_line(ssh_session session, return -1; } args++; - SSH_LOG(SSH_LOG_WARN, + SSH_LOG(SSH_LOG_INFO, "line %d: Unsupported Match keyword '%s', ignoring", count, p2); @@ -681,34 +888,6 @@ ssh_config_parse_line(ssh_session session, } } break; - case SOC_PROTOCOL: - p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { - char *a, *b; - b = strdup(p); - if (b == NULL) { - SAFE_FREE(x); - ssh_set_error_oom(session); - return -1; - } - i = 0; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - - for (a = strtok(b, ","); a; a = strtok(NULL, ",")) { - switch (atoi(a)) { - case 1: - break; - case 2: - i = 1; - ssh_options_set(session, SSH_OPTIONS_SSH2, &i); - break; - default: - break; - } - } - SAFE_FREE(b); - } - break; case SOC_TIMEOUT: l = ssh_config_get_long(&s, -1); if (l >= 0 && *parsing) { @@ -809,7 +988,7 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); } break; - case SOC_PUBKEYACCEPTEDTYPES: + case SOC_PUBKEYACCEPTEDKEYTYPES: p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); @@ -986,13 +1165,19 @@ ssh_config_parse_line(ssh_session session, keyword, count); break; case SOC_UNSUPPORTED: - SSH_LOG(SSH_LOG_RARE, "Unsupported option: %s, line: %d", + SSH_LOG(SSH_LOG_INFO, "Unsupported option: %s, line: %d", keyword, count); break; case SOC_UNKNOWN: - SSH_LOG(SSH_LOG_WARN, "Unknown option: %s, line: %d", + SSH_LOG(SSH_LOG_INFO, "Unknown option: %s, line: %d", keyword, count); break; + case SOC_IDENTITYAGENT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); @@ -1018,18 +1203,24 @@ int ssh_config_parse_file(ssh_session session, const char *filename) unsigned int count = 0; FILE *f; int parsing, rv; + bool global = 0; f = fopen(filename, "r"); if (f == NULL) { return 0; } + rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); + if (rv == 0) { + global = true; + } + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); parsing = 1; while (fgets(line, sizeof(line), f)) { count++; - rv = ssh_config_parse_line(session, line, count, &parsing); + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); if (rv < 0) { fclose(f); return -1; @@ -1039,3 +1230,57 @@ int ssh_config_parse_file(ssh_session session, const char *filename) fclose(f); return 0; } + +/* @brief Parse configuration string and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_config_parse_string(ssh_session session, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0, line_len; + int parsing, rv; + + SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parsing = 1; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, "Line %u too long: %u characters", + line_num, line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/libssh/src/config_parser.c b/libssh/src/config_parser.c index ae2aa2c..2f91d39 100644 --- a/libssh/src/config_parser.c +++ b/libssh/src/config_parser.c @@ -31,6 +31,11 @@ #include "libssh/config_parser.h" #include "libssh/priv.h" +/* Returns the original string after skipping the leading whitespace + * and optional quotes. + * This is useful in case we need to get the rest of the line (for example + * external command). + */ char *ssh_config_get_cmd(char **str) { register char *c; @@ -65,23 +70,55 @@ char *ssh_config_get_cmd(char **str) return r; } +/* Returns the next token delimited by whitespace or equal sign (=) + * respecting the quotes creating separate token (including whitespaces). + */ char *ssh_config_get_token(char **str) { register char *c; - char *r; + bool had_equal = false; + char *r = NULL; - c = ssh_config_get_cmd(str); + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } - for (r = c; *c; c++) { - if (isblank(*c) || *c == '=') { - *c = '\0'; - goto out; + /* If we start with quote, return the whole quoted block */ + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"' || *c == '\n') { + *c = '\0'; + c++; + break; + } + /* XXX Unmatched quotes extend to the end of line */ + } + } else { + /* Otherwise terminate on space, equal or newline */ + for (r = c; *c; c++) { + if (*c == '\0') { + goto out; + } else if (isblank(*c) || *c == '=' || *c == '\n') { + had_equal = (*c == '='); + *c = '\0'; + c++; + break; + } } } + /* Skip any other remaining whitespace */ + while (isblank(*c) || *c == '\n' || (!had_equal && *c == '=')) { + if (*c == '=') { + had_equal = true; + } + c++; + } out: - *str = c + 1; - + *str = c; return r; } diff --git a/libssh/src/connect.c b/libssh/src/connect.c index ce4d58d..57e37e6 100644 --- a/libssh/src/connect.c +++ b/libssh/src/connect.c @@ -183,11 +183,13 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, } for (itr = ai; itr != NULL; itr = itr->ai_next) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; /* create socket */ s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); if (s < 0) { ssh_set_error(session, SSH_FATAL, - "Socket create failed: %s", strerror(errno)); + "Socket create failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } @@ -214,7 +216,8 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, { if (bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen) < 0) { ssh_set_error(session, SSH_FATAL, - "Binding local address: %s", strerror(errno)); + "Binding local address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); continue; } else { break; @@ -246,7 +249,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Failed to set TCP_NODELAY on socket: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ssh_connect_socket_close(s); s = -1; continue; @@ -257,7 +260,8 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, rc = connect(s, itr->ai_addr, itr->ai_addrlen); if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) { ssh_set_error(session, SSH_FATAL, - "Failed to connect: %s", strerror(errno)); + "Failed to connect: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ssh_connect_socket_close(s); s = -1; continue; @@ -289,14 +293,14 @@ static int ssh_select_cb (socket_t fd, int revents, void *userdata) /** * @brief A wrapper for the select syscall * - * This functions acts more or less like the select(2) syscall.\n + * This function acts more or less like the select(2) syscall.\n * There is no support for writing or exceptions.\n * * @param[in] channels Arrays of channels pointers terminated by a NULL. * It is never rewritten. * - * @param[out] outchannels Arrays of same size that "channels", there is no need - * to initialize it. + * @param[out] outchannels Arrays of the same size as "channels", there is no + * need to initialize it. * * @param[in] maxfd Maximum +1 file descriptor from readfds. * diff --git a/libssh/src/connector.c b/libssh/src/connector.c index ac84133..c9f2cf0 100644 --- a/libssh/src/connector.c +++ b/libssh/src/connector.c @@ -30,7 +30,9 @@ #include #include +#ifndef CHUNKSIZE #define CHUNKSIZE 4096 +#endif #ifdef _WIN32 # ifdef HAVE_IO_H @@ -83,7 +85,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, void *userdata); static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata); static ssize_t ssh_connector_fd_read(ssh_connector connector, void *buffer, @@ -214,7 +216,7 @@ static void ssh_connector_except_channel(ssh_connector connector, /** * @internal * - * @brief Reset the poll events to be followed for each file descriptors. + * @brief Reset the poll events to be followed for each file descriptor. */ static void ssh_connector_reset_pollevents(ssh_connector connector) { @@ -246,14 +248,14 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) uint32_t toread = CHUNKSIZE; ssize_t r; ssize_t w; - int total = 0; + ssize_t total = 0; int rc; SSH_LOG(SSH_LOG_TRACE, "connector POLLIN event for fd %d", connector->in_fd); if (connector->out_wontblock) { if (connector->out_channel != NULL) { - size_t size = ssh_channel_window_size(connector->out_channel); + uint32_t size = ssh_channel_window_size(connector->out_channel); /* Don't attempt reading more than the window */ toread = MIN(size, CHUNKSIZE); @@ -326,9 +328,9 @@ static void ssh_connector_fd_in_cb(ssh_connector connector) */ static void ssh_connector_fd_out_cb(ssh_connector connector){ unsigned char buffer[CHUNKSIZE]; - int r; - int w; - int total = 0; + ssize_t r; + ssize_t w; + ssize_t total = 0; SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", connector->out_fd); if(connector->in_available){ @@ -430,7 +432,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, { ssh_connector connector = userdata; int w; - size_t window; + uint32_t window; (void) session; (void) channel; @@ -444,11 +446,14 @@ static int ssh_connector_channel_data_cb(ssh_session session, } else if (!is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDOUT)) { /* ignore stdout */ return 0; + } else if (len == 0) { + /* ignore empty data */ + return 0; } if (connector->out_wontblock) { if (connector->out_channel != NULL) { - int window_len; + uint32_t window_len; window = ssh_channel_window_size(connector->out_channel); window_len = MIN(window, len); @@ -512,7 +517,7 @@ static int ssh_connector_channel_data_cb(ssh_session session, */ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, ssh_channel channel, - size_t bytes, + uint32_t bytes, void *userdata) { ssh_connector connector = userdata; @@ -524,7 +529,7 @@ static int ssh_connector_channel_write_wontblock_cb(ssh_session session, SSH_LOG(SSH_LOG_TRACE, "Channel write won't block"); if (connector->in_available) { if (connector->in_channel != NULL) { - size_t len = MIN(CHUNKSIZE, bytes); + uint32_t len = MIN(CHUNKSIZE, bytes); r = ssh_channel_read_nonblocking(connector->in_channel, buffer, diff --git a/libssh/src/crypto_common.c b/libssh/src/crypto_common.c new file mode 100644 index 0000000..5dc883f --- /dev/null +++ b/libssh/src/crypto_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "libssh/crypto.h" + +int secure_memcmp(const void *s1, const void *s2, size_t n) +{ + size_t i; + uint8_t status = 0; + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (i = 0; i < n; i++) { + status |= (p1[i] ^ p2[i]); + } + + return (status != 0); +} diff --git a/libssh/src/curve25519.c b/libssh/src/curve25519.c index d251755..6a930fa 100644 --- a/libssh/src/curve25519.c +++ b/libssh/src/curve25519.c @@ -39,7 +39,7 @@ #include "libssh/pki.h" #include "libssh/bignum.h" -#ifdef HAVE_OPENSSL_X25519 +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_X25519) #include #endif @@ -59,7 +59,7 @@ static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { static int ssh_curve25519_init(ssh_session session) { int rc; -#ifdef HAVE_OPENSSL_X25519 +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_X25519) EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL; size_t pubkey_len = CURVE25519_PUBKEY_SIZE; @@ -136,7 +136,7 @@ static int ssh_curve25519_init(ssh_session session) crypto_scalarmult_base(session->next_crypto->curve25519_client_pubkey, session->next_crypto->curve25519_privkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_X25519) */ return SSH_OK; } @@ -176,7 +176,7 @@ static int ssh_curve25519_build_k(ssh_session session) { ssh_curve25519_pubkey k; -#ifdef HAVE_OPENSSL_X25519 +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_X25519) EVP_PKEY_CTX *pctx = NULL; EVP_PKEY *pkey = NULL, *pubkey = NULL; size_t shared_key_len = sizeof(k); @@ -255,7 +255,7 @@ static int ssh_curve25519_build_k(ssh_session session) crypto_scalarmult(k, session->next_crypto->curve25519_privkey, session->next_crypto->curve25519_server_pubkey); } -#endif /* HAVE_OPENSSL_X25519 */ +#endif /* defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_X25519) */ bignum_bin2bn(k, CURVE25519_PUBKEY_SIZE, &session->next_crypto->shared_secret); if (session->next_crypto->shared_secret == NULL) { diff --git a/libssh/src/dh-gex.c b/libssh/src/dh-gex.c index 88a9714..d0d2890 100644 --- a/libssh/src/dh-gex.c +++ b/libssh/src/dh-gex.c @@ -108,7 +108,11 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) bignum pmin1 = NULL, one = NULL; bignum_CTX ctx = bignum_ctx_new(); bignum modulus = NULL, generator = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ (void) type; (void) user; @@ -212,6 +216,9 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ session->dh_handshake_state = DH_STATE_INIT_SENT; @@ -229,6 +236,9 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) bignum_safe_free(generator); bignum_safe_free(one); bignum_safe_free(pmin1); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ if(!bignum_ctx_invalid(ctx)) { bignum_ctx_free(ctx); } @@ -367,9 +377,9 @@ static bool dhgroup_better_size(uint32_t pmin, * @brief returns 1 with 1/n probability * @returns 1 on with P(1/n), 0 with P(n-1/n). */ -static bool invn_chance(int n) +static bool invn_chance(size_t n) { - uint32_t nounce = 0; + size_t nounce = 0; int ok; ok = ssh_get_random(&nounce, sizeof(nounce), 0); @@ -489,7 +499,8 @@ static int ssh_retrieve_dhgroup_file(FILE *moduli, * @param[out] g generator * @return SSH_OK on success, SSH_ERROR otherwise. */ -static int ssh_retrieve_dhgroup(uint32_t pmin, +static int ssh_retrieve_dhgroup(char *moduli_file, + uint32_t pmin, uint32_t pn, uint32_t pmax, size_t *size, @@ -508,12 +519,17 @@ static int ssh_retrieve_dhgroup(uint32_t pmin, return ssh_fallback_group(pmax, p, g); } - moduli = fopen(MODULI_FILE, "r"); + if (moduli_file != NULL) + moduli = fopen(moduli_file, "r"); + else + moduli = fopen(MODULI_FILE, "r"); + if (moduli == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_WARNING, "Unable to open moduli file: %s", - strerror(errno)); - return ssh_fallback_group(pmax, p, g); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return ssh_fallback_group(pmax, p, g); } *size = 0; @@ -627,7 +643,8 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) pn = pmin; } } - rc = ssh_retrieve_dhgroup(pmin, + rc = ssh_retrieve_dhgroup(session->opts.moduli_file, + pmin, pn, pmax, &size, diff --git a/libssh/src/dh.c b/libssh/src/dh.c index 18b7173..1251eb6 100644 --- a/libssh/src/dh.c +++ b/libssh/src/dh.c @@ -309,7 +309,11 @@ static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { */ int ssh_client_dh_init(ssh_session session){ struct ssh_crypto_struct *crypto = session->next_crypto; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int rc; rc = ssh_dh_init_common(crypto); @@ -330,6 +334,9 @@ int ssh_client_dh_init(ssh_session session){ if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* register the packet callbacks */ ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); @@ -338,6 +345,9 @@ int ssh_client_dh_init(ssh_session session){ rc = ssh_packet_send(session); return rc; error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif ssh_dh_cleanup(crypto); return SSH_ERROR; } @@ -370,7 +380,7 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ if (rc != 0) { goto error; } - + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, &session->next_crypto->shared_secret); @@ -436,7 +446,11 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; bignum client_pubkey; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum server_pubkey; +#else + bignum server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ int packet_type; int rc; @@ -516,6 +530,9 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) sig_blob); SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif if(rc != SSH_OK) { ssh_set_error_oom(session); ssh_buffer_reinit(session->out_buffer); @@ -541,6 +558,9 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) error: SSH_STRING_FREE(sig_blob); SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif session->session_state = SSH_SESSION_STATE_ERROR; ssh_dh_cleanup(session->next_crypto); @@ -640,7 +660,7 @@ ssh_key ssh_dh_get_current_server_publickey(ssh_session session) return session->current_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_current_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -654,7 +674,7 @@ ssh_key ssh_dh_get_next_server_publickey(ssh_session session) return session->next_crypto->server_pubkey; } -/* Caller need to free the blob */ +/* Caller needs to free the blob */ int ssh_dh_get_next_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob) { @@ -683,7 +703,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) char *b64_unpadded = NULL; size_t k; - b64_padded = (char *)bin_to_base64(hash, (int)len); + b64_padded = (char *)bin_to_base64(hash, len); if (b64_padded == NULL) { return NULL; } @@ -699,7 +719,7 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * @brief Get a hash as a human-readable hex- or base64-string. * * This gets an allocated fingerprint hash. If it is a SHA sum, it will - * return an unpadded base64 strings. If it is a MD5 sum, it will return hex + * return an unpadded base64 string. If it is a MD5 sum, it will return a hex * string. Either way, the output is prepended by the hash-type. * * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. @@ -711,7 +731,8 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) * * @param len Length of the buffer to convert. * - * @return Returns the allocated fingerprint hash or NULL on error. + * @return Returns the allocated fingerprint hash or NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). * * @see ssh_string_free_char() */ diff --git a/libssh/src/dh_crypto.c b/libssh/src/dh_crypto.c index 3b3495c..a847c6a 100644 --- a/libssh/src/dh_crypto.c +++ b/libssh/src/dh_crypto.c @@ -30,6 +30,13 @@ #include "openssl/crypto.h" #include "openssl/dh.h" #include "libcrypto-compat.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ extern bignum ssh_dh_generator; extern bignum ssh_dh_group1; @@ -38,13 +45,21 @@ extern bignum ssh_dh_group16; extern bignum ssh_dh_group18; struct dh_ctx { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH *keypair[2]; +#else + EVP_PKEY *keypair[2]; +#endif /* OPENSSL_VERSION_NUMBER */ }; void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) { #ifdef DEBUG_CRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum x = NULL, y = NULL, e = NULL, f = NULL; +#else + bignum x = NULL, y = NULL, e = NULL, f = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); @@ -52,6 +67,12 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) ssh_print_bignum("y", y); ssh_print_bignum("e", e); ssh_print_bignum("f", f); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(x); + bignum_safe_free(y); + bignum_safe_free(e); + bignum_safe_free(f); +#endif /* OPENSSL_VERSION_NUMBER */ ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); @@ -59,9 +80,10 @@ void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) #else (void)c; /* UNUSED_PARAM */ -#endif +#endif /* DEBUG_CRYPTO */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, const_bignum *priv, const_bignum *pub) { @@ -70,22 +92,79 @@ int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, (ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + DH_get0_key(ctx->keypair[peer], pub, priv); + + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#else +/* If set *priv and *pub should be initialized + * to NULL before calling this function*/ +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub) +{ + int rc; + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (rc != 1) { + return SSH_ERROR; + } + } + if (pub) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PUB_KEY, + pub); + if (rc != 1) { + return SSH_ERROR; + } + } if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + if (pub && (*pub != NULL && bignum_num_bits(*pub) != 0)) { + bignum_safe_free(*pub); + *pub = NULL; + } return SSH_ERROR; } if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + if (priv) { + bignum_safe_free(*priv); + *priv = NULL; + } return SSH_ERROR; } return SSH_OK; } +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, const bignum priv, const bignum pub) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L bignum priv_key = NULL; bignum pub_key = NULL; +#else + int rc; + OSSL_PARAM *params = NULL, *out_params = NULL, *merged_params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || @@ -93,17 +172,92 @@ int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rc = EVP_PKEY_todata(ctx->keypair[peer], EVP_PKEY_KEYPAIR, &out_params); + if (rc != 1) { + return SSH_ERROR; + } + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + rc = SSH_ERROR; + goto out; + } + + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (priv) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L priv_key = priv; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ } if (pub) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L pub_key = pub; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pub); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L (void)DH_set0_key(ctx->keypair[peer], pub_key, priv_key); return SSH_OK; +#else + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = SSH_ERROR; + goto out; + } + OSSL_PARAM_BLD_free(param_bld); + + merged_params = OSSL_PARAM_merge(out_params, params); + if (merged_params == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[peer]), + EVP_PKEY_PUBLIC_KEY, + merged_params); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + rc = SSH_OK; +out: + EVP_PKEY_CTX_free(evp_ctx); + OSSL_PARAM_free(out_params); + OSSL_PARAM_free(params); + OSSL_PARAM_free(merged_params); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ } +#if OPENSSL_VERSION_NUMBER < 0x30000000L int ssh_dh_get_parameters(struct dh_ctx *ctx, const_bignum *modulus, const_bignum *generator) { @@ -113,18 +267,51 @@ int ssh_dh_get_parameters(struct dh_ctx *ctx, DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); return SSH_OK; } +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator) +{ + int rc; + + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_P, (BIGNUM**)modulus); + if (rc != 1) { + return SSH_ERROR; + } + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_G, (BIGNUM**)generator); + if (rc != 1) { + bignum_safe_free(*modulus); + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_dh_set_parameters(struct dh_ctx *ctx, const bignum modulus, const bignum generator) { size_t i; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + evp_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL); +#endif + for (i = 0; i < 2; i++) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L bignum p = NULL; bignum g = NULL; @@ -146,16 +333,70 @@ int ssh_dh_set_parameters(struct dh_ctx *ctx, rc = SSH_ERROR; goto done; } +#else + param_bld = OSSL_PARAM_BLD_new(); + + if (param_bld == NULL) { + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, modulus); + OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, generator); + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + OSSL_PARAM_BLD_free(param_bld); + rc = SSH_ERROR; + goto done; + } + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + /* make sure to invalidate existing keys */ + EVP_PKEY_free(ctx->keypair[i]); + ctx->keypair[i] = NULL; + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[i]), + EVP_PKEY_KEY_PARAMETERS, + params); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ } rc = SSH_OK; +#if OPENSSL_VERSION_NUMBER < 0x30000000L done: if (rc != SSH_OK) { DH_free(ctx->keypair[0]); DH_free(ctx->keypair[1]); + } +#else +done: + EVP_PKEY_CTX_free(evp_ctx); + + if (rc != SSH_OK) { + EVP_PKEY_free(ctx->keypair[0]); + EVP_PKEY_free(ctx->keypair[1]); + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (rc != SSH_OK) { ctx->keypair[0] = NULL; ctx->keypair[1] = NULL; } + return rc; } @@ -202,8 +443,13 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto) void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) { if (crypto->dh_ctx != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH_free(crypto->dh_ctx->keypair[0]); DH_free(crypto->dh_ctx->keypair[1]); +#else + EVP_PKEY_free(crypto->dh_ctx->keypair[0]); + EVP_PKEY_free(crypto->dh_ctx->keypair[1]); +#endif /* OPENSSL_VERSION_NUMBER */ free(crypto->dh_ctx); crypto->dh_ctx = NULL; } @@ -221,14 +467,43 @@ void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) { int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *evp_ctx = NULL; +#endif if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { return SSH_ERROR; } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = DH_generate_key(dh_ctx->keypair[peer]); if (rc != 1) { return SSH_ERROR; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(evp_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(evp_ctx, &(dh_ctx->keypair[peer])); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate DH: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + EVP_PKEY_CTX_free(evp_ctx); +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; } @@ -247,8 +522,14 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, bignum *dest) { unsigned char *kstring = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum pub_key = NULL; - int klen, rc; + int klen; +#else + size_t klen; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ if ((dh_ctx == NULL) || (dh_ctx->keypair[local] == NULL) || @@ -256,6 +537,7 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, return SSH_ERROR; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L kstring = malloc(DH_size(dh_ctx->keypair[local])); if (kstring == NULL) { rc = SSH_ERROR; @@ -273,6 +555,43 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_ERROR; goto done; } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[local], NULL); + + rc = EVP_PKEY_derive_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive_set_peer(evp_ctx, dh_ctx->keypair[remote]); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer key: %s", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto done; + } + + /* getting the size of the secret */ + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + kstring = malloc(klen); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } +#endif /* OPENSSL_VERSION_NUMBER */ *dest = BN_bin2bn(kstring, klen, NULL); if (*dest == NULL) { @@ -282,6 +601,9 @@ int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, rc = SSH_OK; done: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX_free(evp_ctx); +#endif free(kstring); return rc; } diff --git a/libssh/src/ecdh_crypto.c b/libssh/src/ecdh_crypto.c index a1de27f..51084b7 100644 --- a/libssh/src/ecdh_crypto.c +++ b/libssh/src/ecdh_crypto.c @@ -30,15 +30,33 @@ #ifdef HAVE_ECDH #include - +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 #define NISTP256 NID_X9_62_prime256v1 #define NISTP384 NID_secp384r1 #define NISTP521 NID_secp521r1 +#else +#include +#include +#include +#include "libcrypto-compat.h" +#endif /* OPENSSL_VERSION_NUMBER */ /** @internal * @brief Map the given key exchange enum value to its curve name. */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#else +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#endif /* OPENSSL_VERSION_NUMBER */ if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { return NISTP256; } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { @@ -46,39 +64,94 @@ static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { return NISTP521; } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 return SSH_ERROR; +#else + return NULL; +#endif } /** @internal * @brief Starts ecdh-sha2-nistp256 key exchange */ int ssh_client_ecdh_init(ssh_session session){ + int rc; + ssh_string client_pubkey; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY *key; const EC_GROUP *group; const EC_POINT *pubkey; - ssh_string client_pubkey; int curve; int len; - int rc; bignum_CTX ctx = BN_CTX_new(); + if (ctx == NULL) { + return SSH_ERROR; + } +#else + const char *curve = NULL; + EVP_PKEY *key = NULL; + OSSL_PARAM *out_params = NULL; + const OSSL_PARAM *pubkey_param = NULL; + const uint8_t *pubkey = NULL; + size_t pubkey_len; +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); if (rc < 0) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 BN_CTX_free(ctx); +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_ERROR; } curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 if (curve == SSH_ERROR) { BN_CTX_free(ctx); return SSH_ERROR; } key = EC_KEY_new_by_curve_name(curve); +#else + if (curve == NULL) { + return SSH_ERROR; + } + + key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ + if (key == NULL) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 BN_CTX_free(ctx); +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_ERROR; } + +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 group = EC_KEY_get0_group(key); EC_KEY_generate_key(key); @@ -97,10 +170,51 @@ int ssh_client_ecdh_init(ssh_session session){ EC_POINT_point2oct(group,pubkey,POINT_CONVERSION_UNCOMPRESSED, ssh_string_data(client_pubkey),len,ctx); BN_CTX_free(ctx); +#else + rc = EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &out_params); + if (rc != 1) { + EVP_PKEY_free(key); + return SSH_ERROR; + } + + pubkey_param = OSSL_PARAM_locate_const(out_params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey_param == NULL) { + EVP_PKEY_free(key); + OSSL_PARAM_free(out_params); + return SSH_ERROR; + } + + rc = OSSL_PARAM_get_octet_string_ptr(pubkey_param, + (const void**)&pubkey, + &pubkey_len); + if (rc != 1) { + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return SSH_ERROR; + } + + client_pubkey = ssh_string_new(pubkey_len); + if (client_pubkey == NULL) { + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return SSH_ERROR; + } + + memcpy(ssh_string_data(client_pubkey), pubkey, pubkey_len); + OSSL_PARAM_free(out_params); +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_buffer_add_ssh_string(session->out_buffer,client_pubkey); if (rc < 0) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY_free(key); +#else + EVP_PKEY_free(key); +#endif /* OPENSSL_VERSION_NUMBER */ SSH_STRING_FREE(client_pubkey); return SSH_ERROR; } @@ -118,7 +232,13 @@ int ssh_client_ecdh_init(ssh_session session){ } int ecdh_build_k(ssh_session session) { - const EC_GROUP *group = EC_KEY_get0_group(session->next_crypto->ecdh_privkey); + struct ssh_crypto_struct *next_crypto = session->next_crypto; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ + #if 1 + const EC_GROUP *group = EC_KEY_get0_group(next_crypto->ecdh_privkey); EC_POINT *pubkey; void *buffer; int rc; @@ -127,7 +247,6 @@ int ecdh_build_k(ssh_session session) { if (ctx == NULL) { return -1; } - pubkey = EC_POINT_new(group); if (pubkey == NULL) { bignum_ctx_free(ctx); @@ -137,14 +256,14 @@ int ecdh_build_k(ssh_session session) { if (session->server) { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_client_pubkey), - ssh_string_len(session->next_crypto->ecdh_client_pubkey), + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey), ctx); } else { rc = EC_POINT_oct2point(group, pubkey, - ssh_string_data(session->next_crypto->ecdh_server_pubkey), - ssh_string_len(session->next_crypto->ecdh_server_pubkey), + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey), ctx); } bignum_ctx_free(ctx); @@ -162,7 +281,7 @@ int ecdh_build_k(ssh_session session) { rc = ECDH_compute_key(buffer, len, pubkey, - session->next_crypto->ecdh_privkey, + next_crypto->ecdh_privkey, NULL); EC_POINT_clear_free(pubkey); if (rc <= 0) { @@ -170,23 +289,121 @@ int ecdh_build_k(ssh_session session) { return -1; } - bignum_bin2bn(buffer, len, &session->next_crypto->shared_secret); + bignum_bin2bn(buffer, len, &next_crypto->shared_secret); free(buffer); - if (session->next_crypto->shared_secret == NULL) { - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; +#else + EVP_PKEY *pubkey = NULL; + void *secret = NULL; + size_t secret_len; + int rc; + OSSL_PARAM params[2]; + EVP_PKEY_CTX *dh_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + EVP_PKEY_CTX *pubkey_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + if (dh_ctx == NULL || pubkey_ctx == NULL) { + EVP_PKEY_CTX_free(dh_ctx); + EVP_PKEY_CTX_free(pubkey_ctx); + return -1; + } + + rc = EVP_PKEY_derive_init(dh_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + EVP_PKEY_CTX_free(pubkey_ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(pubkey_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + EVP_PKEY_CTX_free(pubkey_ctx); + return -1; + } + + if (session->server) { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey)); + } else { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey)); + } + params[1] = OSSL_PARAM_construct_end(); + + rc = EVP_PKEY_fromdata(pubkey_ctx, &pubkey, EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + EVP_PKEY_CTX_free(pubkey_ctx); + return -1; + } + + EVP_PKEY_CTX_free(pubkey_ctx); + + rc = EVP_PKEY_derive_set_peer(dh_ctx, pubkey); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + /* get the max length of the secret */ + rc = EVP_PKEY_derive(dh_ctx, NULL, &secret_len); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + secret = malloc(secret_len); + if (secret == NULL) { + EVP_PKEY_CTX_free(dh_ctx); return -1; } - EC_KEY_free(session->next_crypto->ecdh_privkey); - session->next_crypto->ecdh_privkey = NULL; + + rc = EVP_PKEY_derive(dh_ctx, secret, &secret_len); + if (rc != 1) { + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + EVP_PKEY_CTX_free(dh_ctx); + + bignum_bin2bn(secret, secret_len, &next_crypto->shared_secret); + free(secret); +#endif /* OPENSSL_VERSION_NUMBER */ + if (next_crypto->shared_secret == NULL) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + return -1; + } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; #ifdef DEBUG_CRYPTO ssh_log_hexdump("Session server cookie", - session->next_crypto->server_kex.cookie, 16); + next_crypto->server_kex.cookie, 16); ssh_log_hexdump("Session client cookie", - session->next_crypto->client_kex.cookie, 16); - ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); -#endif + next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", next_crypto->shared_secret); +#endif /* DEBUG_CRYPTO */ return 0; } @@ -200,17 +417,30 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ /* ECDH keys */ ssh_string q_c_string; ssh_string q_s_string; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY *ecdh_key; const EC_GROUP *group; const EC_POINT *ecdh_pubkey; bignum_CTX ctx; + int curve; + int len; +#else + EVP_PKEY *ecdh_key = NULL; + const void *pubkey_ptr = NULL; + size_t len; + OSSL_PARAM *params = NULL; + const OSSL_PARAM *pubkey = NULL; + const char *curve = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ /* SSH host keys (rsa,dsa,ecdsa) */ ssh_key privkey; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; ssh_string pubkey_blob = NULL; - int curve; - int len; int rc; (void)type; (void)user; @@ -224,23 +454,47 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } session->next_crypto->ecdh_client_pubkey = q_c_string; - /* Build server's keypair */ - - ctx = BN_CTX_new(); - curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 if (curve == SSH_ERROR) { - BN_CTX_free(ctx); return SSH_ERROR; } +#else + if (curve == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 ecdh_key = EC_KEY_new_by_curve_name(curve); +#else + ecdh_key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ if (ecdh_key == NULL) { ssh_set_error_oom(session); - BN_CTX_free(ctx); goto error; } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + /* Build server's keypair */ + ctx = BN_CTX_new(); + if (ctx == NULL) { + EC_KEY_free(ecdh_key); + return SSH_ERROR; + } + group = EC_KEY_get0_group(ecdh_key); EC_KEY_generate_key(ecdh_key); @@ -251,14 +505,47 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ NULL, 0, ctx); +#else + rc = EVP_PKEY_todata(ecdh_key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_free(ecdh_key); + return SSH_ERROR; + } + pubkey = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey == NULL) { + OSSL_PARAM_free(params); + EVP_PKEY_free(ecdh_key); + return SSH_ERROR; + } + + rc = OSSL_PARAM_get_octet_string_ptr(pubkey, &pubkey_ptr, &len); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_free(ecdh_key); + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ q_s_string = ssh_string_new(len); if (q_s_string == NULL) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY_free(ecdh_key); BN_CTX_free(ctx); +#else + EVP_PKEY_free(ecdh_key); +#endif /* OPENSSL_VERSION_NUMBER */ goto error; } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_POINT_point2oct(group, ecdh_pubkey, POINT_CONVERSION_UNCOMPRESSED, @@ -266,6 +553,15 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ len, ctx); BN_CTX_free(ctx); +#else + if (memcpy(ssh_string_data(q_s_string), pubkey_ptr, len)) { + OSSL_PARAM_free(params); + EVP_PKEY_free(ecdh_key); + return SSH_ERROR; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ session->next_crypto->ecdh_privkey = ecdh_key; session->next_crypto->ecdh_server_pubkey = q_s_string; diff --git a/libssh/src/ecdh_mbedcrypto.c b/libssh/src/ecdh_mbedcrypto.c index 718f152..cfe017a 100644 --- a/libssh/src/ecdh_mbedcrypto.c +++ b/libssh/src/ecdh_mbedcrypto.c @@ -34,6 +34,7 @@ #include #include +#include "mbedcrypto-compat.h" #ifdef HAVE_ECDH @@ -54,6 +55,10 @@ int ssh_client_ecdh_init(ssh_session session) mbedtls_ecp_group grp; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -70,7 +75,9 @@ int ssh_client_ecdh_init(ssh_session session) return SSH_ERROR; } - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + ecdh_privkey = session->next_crypto->ecdh_privkey; + + mbedtls_ecp_keypair_init(ecdh_privkey); mbedtls_ecp_group_init(&grp); rc = mbedtls_ecp_group_load(&grp, curve); @@ -80,10 +87,10 @@ int ssh_client_ecdh_init(ssh_session session) } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; @@ -91,7 +98,7 @@ int ssh_client_ecdh_init(ssh_session session) } client_pubkey = make_ecpoint_string(&grp, - &session->next_crypto->ecdh_privkey->Q); + &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (client_pubkey == NULL) { rc = SSH_ERROR; goto out; @@ -124,6 +131,10 @@ int ecdh_build_k(ssh_session session) mbedtls_ecp_point pubkey; int rc; mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); if (curve == MBEDTLS_ECP_DP_NONE) { @@ -162,19 +173,20 @@ int ecdh_build_k(ssh_session session) mbedtls_mpi_init(session->next_crypto->shared_secret); + ecdh_privkey = session->next_crypto->ecdh_privkey; rc = mbedtls_ecdh_compute_shared(&grp, - session->next_crypto->shared_secret, - &pubkey, - &session->next_crypto->ecdh_privkey->d, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + session->next_crypto->shared_secret, + &pubkey, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } out: - mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_free(ecdh_privkey); SAFE_FREE(session->next_crypto->ecdh_privkey); mbedtls_ecp_group_free(&grp); mbedtls_ecp_point_free(&pubkey); @@ -187,6 +199,8 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ ssh_string q_c_string = NULL; ssh_string q_s_string = NULL; mbedtls_ecp_group grp; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; ssh_key privkey = NULL; enum ssh_digest_e digest = SSH_DIGEST_AUTO; ssh_string sig_blob = NULL; @@ -214,10 +228,14 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ return SSH_ERROR; } + ecdh_privkey = session->next_crypto->ecdh_privkey; + session->next_crypto->ecdh_client_pubkey = q_c_string; + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + mbedtls_ecp_group_init(&grp); - mbedtls_ecp_keypair_init(session->next_crypto->ecdh_privkey); + mbedtls_ecp_keypair_init(ecdh_privkey); rc = mbedtls_ecp_group_load(&grp, curve); if (rc != 0) { @@ -226,16 +244,16 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ } rc = mbedtls_ecp_gen_keypair(&grp, - &session->next_crypto->ecdh_privkey->d, - &session->next_crypto->ecdh_privkey->Q, - mbedtls_ctr_drbg_random, - ssh_get_mbedtls_ctr_drbg_context()); + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); if (rc != 0) { rc = SSH_ERROR; goto out; } - q_s_string = make_ecpoint_string(&grp, &session->next_crypto->ecdh_privkey->Q); + q_s_string = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); if (q_s_string == NULL) { rc = SSH_ERROR; goto out; diff --git a/libssh/src/error.c b/libssh/src/error.c index 2218040..8930d26 100644 --- a/libssh/src/error.c +++ b/libssh/src/error.c @@ -142,7 +142,7 @@ const char *ssh_get_error(void *error) { * SSH_FATAL A fatal error occurred. This could be an unexpected * disconnection\n * - * Other error codes are internal but can be considered same than + * Other error codes are internal but can be considered the same as * SSH_FATAL. */ int ssh_get_error_code(void *error) { diff --git a/libssh/src/external/bcrypt_pbkdf.c b/libssh/src/external/bcrypt_pbkdf.c index 2208654..75c0215 100644 --- a/libssh/src/external/bcrypt_pbkdf.c +++ b/libssh/src/external/bcrypt_pbkdf.c @@ -63,9 +63,8 @@ #define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) static void -bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) { - ssh_blf_ctx state; uint8_t ciphertext[BCRYPT_HASHSIZE] = "OxychromaticBlowfishSwatDynamite"; uint32_t cdata[BCRYPT_BLOCKS]; @@ -74,11 +73,11 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) uint16_t shalen = SHA512_DIGEST_LENGTH; /* key expansion */ - Blowfish_initstate(&state); - Blowfish_expandstate(&state, sha2salt, shalen, sha2pass, shalen); + Blowfish_initstate(state); + Blowfish_expandstate(state, sha2salt, shalen, sha2pass, shalen); for (i = 0; i < 64; i++) { - Blowfish_expand0state(&state, sha2salt, shalen); - Blowfish_expand0state(&state, sha2pass, shalen); + Blowfish_expand0state(state, sha2salt, shalen); + Blowfish_expand0state(state, sha2pass, shalen); } /* encryption */ @@ -87,7 +86,7 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), &j); for (i = 0; i < 64; i++) - ssh_blf_enc(&state, cdata, BCRYPT_BLOCKS/2); + ssh_blf_enc(state, cdata, BCRYPT_BLOCKS/2); /* copy out */ for (i = 0; i < BCRYPT_BLOCKS; i++) { @@ -100,7 +99,6 @@ bcrypt_hash(uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) /* zap */ explicit_bzero(ciphertext, sizeof(ciphertext)); explicit_bzero(cdata, sizeof(cdata)); - ZERO_STRUCT(state); } int @@ -115,6 +113,7 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl size_t i, j, amt, stride; uint32_t count; size_t origkeylen = keylen; + ssh_blf_ctx *state; SHA512CTX ctx; /* nothing crazy */ @@ -130,6 +129,12 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl memcpy(countsalt, salt, saltlen); + state = malloc(sizeof(*state)); + if (state == NULL) { + free(countsalt); + return -1; + } + /* collapse password */ ctx = sha512_init(); sha512_update(ctx, pass, passlen); @@ -147,7 +152,7 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl sha512_update(ctx, countsalt, saltlen + 4); sha512_final(sha2salt, ctx); - bcrypt_hash(sha2pass, sha2salt, tmpout); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); memcpy(out, tmpout, sizeof(out)); for (i = 1; i < rounds; i++) { @@ -155,13 +160,13 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl ctx = sha512_init(); sha512_update(ctx, tmpout, sizeof(tmpout)); sha512_final(sha2salt, ctx); - bcrypt_hash(sha2pass, sha2salt, tmpout); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); for (j = 0; j < sizeof(out); j++) out[j] ^= tmpout[j]; } /* - * pbkdf2 deviation: ouput the key material non-linearly. + * pbkdf2 deviation: output the key material non-linearly. */ amt = MIN(amt, keylen); for (i = 0; i < amt; i++) { @@ -176,6 +181,9 @@ bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltl /* zap */ explicit_bzero(out, sizeof(out)); + explicit_bzero(state, sizeof(*state)); + + free(state); free(countsalt); return 0; diff --git a/libssh/src/getpass.c b/libssh/src/getpass.c index c00d0f5..6be33c7 100644 --- a/libssh/src/getpass.c +++ b/libssh/src/getpass.c @@ -44,7 +44,8 @@ * * @return 1 on success, 0 on error. */ -static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) { +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) +{ char *tmp; char *ptr = NULL; int ok = 0; @@ -121,7 +122,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ HANDLE h; DWORD mode = 0; int ok; @@ -213,7 +215,8 @@ int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, - int verify) { + int verify) +{ struct termios attr; struct termios old_attr; int ok = 0; diff --git a/libssh/src/getrandom_crypto.c b/libssh/src/getrandom_crypto.c new file mode 100644 index 0000000..078560e --- /dev/null +++ b/libssh/src/getrandom_crypto.c @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include + +/** + * @brief Get random bytes + * + * Make sure to always check the return code of this function! + * + * @param[in] where The buffer to fill with random bytes + * + * @param[in] len The size of the buffer to fill. + * + * @param[in] strong Use a strong or private RNG source. + * + * @return 1 on success, 0 on error. + */ +int +ssh_get_random(void *where, int len, int strong) +{ +#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES + if (strong) { + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_priv_bytes(where, len); + } +#else + (void)strong; +#endif /* HAVE_RAND_PRIV_BYTES */ + + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_bytes(where, len); +} diff --git a/libssh/tests/test_pcap.c b/libssh/src/getrandom_gcrypt.c similarity index 53% rename from libssh/tests/test_pcap.c rename to libssh/src/getrandom_gcrypt.c index 01aa714..da72640 100644 --- a/libssh/tests/test_pcap.c +++ b/libssh/src/getrandom_gcrypt.c @@ -2,6 +2,7 @@ * This file is part of the SSH Library * * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH * * The SSH Library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -19,32 +20,19 @@ * MA 02111-1307, USA. */ -/* Simple test for the pcap functions */ +#include "config.h" -#include -#include -#include +#include "libssh/crypto.h" +#include -#include -#include -#include +int +ssh_get_random(void *where, int len, int strong) +{ + /* variable not used in gcrypt */ + (void)strong; -int main(int argc, char **argv){ - ssh_pcap_file pcap; - ssh_pcap_context ctx; - ssh_buffer buffer=ssh_buffer_new(); - char *str="Hello, this is a test string to test the capabilities of the" - "pcap file writer."; - printf("Simple pcap tester\n"); - pcap=ssh_pcap_file_new(); - if(ssh_pcap_file_open(pcap,"test.cap") != SSH_OK){ - printf("error happened\n"); - return EXIT_FAILURE; - } - buffer_add_data(buffer,str,strlen(str)); - ctx=ssh_pcap_context_new(NULL); - ssh_pcap_context_set_file(ctx,pcap); - ssh_pcap_context_write(ctx,SSH_PCAP_DIR_OUT,str,strlen(str),strlen(str)); + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where, len, GCRY_STRONG_RANDOM); - return EXIT_SUCCESS; + return 1; } diff --git a/libssh/src/getrandom_mbedcrypto.c b/libssh/src/getrandom_mbedcrypto.c new file mode 100644 index 0000000..7e87b6a --- /dev/null +++ b/libssh/src/getrandom_mbedcrypto.c @@ -0,0 +1,52 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "mbedcrypto-compat.h" + +mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +int +ssh_mbedtls_random(void *where, int len, int strong) +{ + int rc = 0; + if (strong) { + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_ON); + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_OFF); + } else { + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + } + + return !rc; +} + +int +ssh_get_random(void *where, int len, int strong) +{ + return ssh_mbedtls_random(where, len, strong); +} diff --git a/libssh/src/gssapi.c b/libssh/src/gssapi.c index 1d0fb6a..bbfcb6e 100644 --- a/libssh/src/gssapi.c +++ b/libssh/src/gssapi.c @@ -68,7 +68,8 @@ struct ssh_gssapi_struct{ /** @internal * @initializes a gssapi context for authentication */ -static int ssh_gssapi_init(ssh_session session){ +static int ssh_gssapi_init(ssh_session session) +{ if (session->gssapi != NULL) return SSH_OK; session->gssapi = malloc(sizeof(struct ssh_gssapi_struct)); @@ -87,7 +88,8 @@ static int ssh_gssapi_init(ssh_session session){ /** @internal * @frees a gssapi context */ -static void ssh_gssapi_free(ssh_session session){ +static void ssh_gssapi_free(ssh_session session) +{ OM_uint32 min; if (session->gssapi == NULL) return; @@ -114,7 +116,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ * @brief sends a SSH_MSG_USERAUTH_GSSAPI_RESPONSE packet * @param[in] oid the OID that was selected for authentication */ -static int ssh_gssapi_send_response(ssh_session session, ssh_string oid){ +static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) +{ if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) < 0 || ssh_buffer_add_ssh_string(session->out_buffer,oid) < 0) { ssh_set_error_oom(session); @@ -184,8 +187,11 @@ static void ssh_gssapi_log_error(int verb, /** @internal * @brief handles an user authentication using GSSAPI */ -int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids){ - char service_name[]="host"; +int +ssh_gssapi_handle_userauth(ssh_session session, const char *user, + uint32_t n_oid, ssh_string *oids) +{ + char service_name[] = "host"; gss_buffer_desc name_buf; gss_name_t server_name; /* local server fqdn */ OM_uint32 maj_stat, min_stat; @@ -327,7 +333,8 @@ int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n return ssh_gssapi_send_response(session, oids[i]); } -static char *ssh_gssapi_name_to_char(gss_name_t name){ +static char *ssh_gssapi_name_to_char(gss_name_t name) +{ gss_buffer_desc buffer; OM_uint32 maj_stat, min_stat; char *ptr; @@ -572,7 +579,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) * @returns gssapi credentials handle. * @returns NULL if no forwardable token is available. */ -ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ +ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) +{ if (!session || !session->gssapi || session->gssapi->client_creds == GSS_C_NO_CREDENTIAL) return NULL; return (ssh_gssapi_creds)session->gssapi->client_creds; @@ -581,7 +589,7 @@ ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session){ #endif /* SERVER */ /** - * @brief Set the forwadable ticket to be given to the server for authentication. + * @brief Set the forwardable ticket to be given to the server for authentication. * Unlike ssh_gssapi_get_creds() this is called on the client side of an ssh * connection. * @@ -602,7 +610,9 @@ void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) session->gssapi->client.client_deleg_creds = (gss_cred_id_t)creds; } -static int ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid){ +static int +ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) +{ int rc; int i; @@ -711,7 +721,8 @@ static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again * later. */ -int ssh_gssapi_auth_mic(ssh_session session){ +int ssh_gssapi_auth_mic(ssh_session session) +{ size_t i; gss_OID_set selected; /* oid selected for authentication */ ssh_string *oids = NULL; @@ -902,7 +913,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ return SSH_PACKET_USED; } -static int ssh_gssapi_send_mic(ssh_session session){ +static int ssh_gssapi_send_mic(ssh_session session) +{ OM_uint32 maj_stat, min_stat; gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; diff --git a/libssh/src/gzip.c b/libssh/src/gzip.c index f981de9..148cc21 100644 --- a/libssh/src/gzip.c +++ b/libssh/src/gzip.c @@ -33,7 +33,9 @@ #include "libssh/crypto.h" #include "libssh/session.h" +#ifndef BLOCKSIZE #define BLOCKSIZE 4092 +#endif static z_stream *initcompress(ssh_session session, int level) { z_stream *stream = NULL; @@ -60,10 +62,10 @@ static ssh_buffer gzip_compress(ssh_session session, ssh_buffer source, int leve struct ssh_crypto_struct *crypto = NULL; z_stream *zout = NULL; void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); + uint32_t in_size = ssh_buffer_get_len(source); ssh_buffer dest = NULL; unsigned char out_buf[BLOCKSIZE] = {0}; - unsigned long len; + uint32_t len; int status; crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); @@ -155,10 +157,10 @@ static ssh_buffer gzip_decompress(ssh_session session, ssh_buffer source, size_t struct ssh_crypto_struct *crypto = NULL; z_stream *zin = NULL; void *in_ptr = ssh_buffer_get(source); - unsigned long in_size = ssh_buffer_get_len(source); + uint32_t in_size = ssh_buffer_get_len(source); unsigned char out_buf[BLOCKSIZE] = {0}; ssh_buffer dest = NULL; - unsigned long len; + uint32_t len; int status; crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); diff --git a/libssh/src/init.c b/libssh/src/init.c index 2ebcedf..7f184b9 100644 --- a/libssh/src/init.c +++ b/libssh/src/init.c @@ -104,7 +104,7 @@ static int _ssh_init(unsigned constructor) { /** * @brief Initialize global cryptographic data structures. * - * This functions is automatically called when the library is loaded. + * This function is automatically called when the library is loaded. * */ void libssh_constructor(void) @@ -127,7 +127,7 @@ void libssh_constructor(void) * The libssh library is implementing the SSH protocols and some of its * extensions. This group of functions is mostly used to implement an SSH * client. - * Some function are needed to implement an SSH server too. + * Some functions are needed to implement an SSH server too. * * @{ */ @@ -136,8 +136,8 @@ void libssh_constructor(void) * @brief Initialize global cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary - * to call this function on systems which are fully supported with regards to - * threading (that is, system with pthreads available). + * to call this function on systems that fully support threading (that is, + * systems with pthreads available). * * If libssh is statically linked, it is necessary to explicitly call ssh_init() * before calling any other provided API, and it is necessary to explicitly call @@ -161,12 +161,14 @@ static int _ssh_finalize(unsigned destructor) { if (_ssh_initialized > 1) { _ssh_initialized--; - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } if (_ssh_initialized == 1) { if (_ssh_init_ret < 0) { - goto _ret; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; } } } @@ -181,15 +183,22 @@ static int _ssh_finalize(unsigned destructor) { _ssh_initialized = 0; -_ret: if (!destructor) { ssh_mutex_unlock(&ssh_init_mutex); } + +#if (defined(_WIN32) && !defined(HAVE_PTHREAD)) + if (ssh_init_mutex != NULL) { + DeleteCriticalSection(ssh_init_mutex); + SAFE_FREE(ssh_init_mutex); + } +#endif + return 0; } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * This function is automatically called when the library is unloaded. * @@ -206,7 +215,7 @@ void libssh_destructor(void) } /** - * @brief Finalize and cleanup all libssh and cryptographic data structures. + * @brief Finalize and clean up all libssh and cryptographic data structures. * * Since version 0.8.0, when libssh is dynamically linked, it is not necessary * to call this function, since it is automatically called when the library is diff --git a/libssh/src/kdf.c b/libssh/src/kdf.c index 0964473..44f0663 100644 --- a/libssh/src/kdf.c +++ b/libssh/src/kdf.c @@ -39,7 +39,7 @@ /* The following implements the SSHKDF for crypto backend that - * do not have a native implementations */ + * do not have a native implementation */ struct ssh_mac_ctx_struct { enum ssh_kdf_digest digest_type; union { @@ -116,14 +116,13 @@ static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) int sshkdf_derive_key(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { /* Can't use VLAs with Visual Studio, so allocate the biggest * digest buffer we can possibly need */ unsigned char digest[DIGEST_MAX_LEN]; size_t output_len = crypto->digest_len; - char letter = key_type; ssh_mac_ctx ctx; if (DIGEST_MAX_LEN < crypto->digest_len) { @@ -137,7 +136,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, ssh_mac_update(ctx, key, key_len); ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, &letter, 1); + ssh_mac_update(ctx, &key_type, 1); ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); ssh_mac_final(digest, ctx); diff --git a/libssh/src/kex.c b/libssh/src/kex.c index 82071c7..192eb88 100644 --- a/libssh/src/kex.c +++ b/libssh/src/kex.c @@ -57,8 +57,8 @@ #ifdef HAVE_LIBGCRYPT # define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ - "aes256-ctr,aes192-ctr,aes128-ctr," \ - "aes256-cbc,aes192-cbc,aes128-cbc," + "aes256-ctr,aes192-ctr,aes128-ctr," +# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," # define DES "3des-cbc" # define DES_SUPPORTED "3des-cbc" @@ -68,25 +68,19 @@ # else # define GCM "" # endif /* MBEDTLS_GCM_C */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," \ - "aes256-cbc,aes192-cbc,aes128-cbc," +# 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" #elif defined(HAVE_LIBCRYPTO) # ifdef HAVE_OPENSSL_AES_H -# ifdef HAVE_OPENSSL_EVP_AES_GCM -# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," -# else -# define GCM "" -# endif /* HAVE_OPENSSL_EVP_AES_GCM */ -# ifdef BROKEN_AES_CTR -# define AES GCM "aes256-cbc,aes192-cbc,aes128-cbc," -# else /* BROKEN_AES_CTR */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc," -# endif /* BROKEN_AES_CTR */ +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," +# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," # else /* HAVE_OPENSSL_AES_H */ # define AES "" +# define AES_CBC "" # endif /* HAVE_OPENSSL_AES_H */ # define DES "3des-cbc" @@ -94,16 +88,16 @@ #endif /* HAVE_LIBCRYPTO */ #ifdef WITH_ZLIB -#define ZLIB "none,zlib,zlib@openssh.com" +#define ZLIB "none,zlib@openssh.com,zlib" #else #define ZLIB "none" -#endif +#endif /* WITH_ZLIB */ #ifdef HAVE_CURVE25519 #define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," #else #define CURVE25519 "" -#endif +#endif /* HAVE_CURVE25519 */ #ifdef HAVE_ECDH #define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," @@ -115,7 +109,7 @@ #define EC_HOSTKEYS "" #define EC_PUBLIC_KEY_ALGORITHMS "" #define ECDH "" -#endif +#endif /* HAVE_ECDH */ #ifdef HAVE_DSA #define DSA_HOSTKEYS ",ssh-dss" @@ -123,7 +117,13 @@ #else #define DSA_HOSTKEYS "" #define DSA_PUBLIC_KEY_ALGORITHMS "" -#endif +#endif /* HAVE_DSA */ + +#ifdef WITH_INSECURE_NONE +#define NONE ",none" +#else +#define NONE +#endif /* WITH_INSECURE_NONE */ #define HOSTKEYS "ssh-ed25519," \ EC_HOSTKEYS \ @@ -131,6 +131,11 @@ "rsa-sha2-256," \ "ssh-rsa" \ DSA_HOSTKEYS +#define DEFAULT_HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + #define PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ EC_PUBLIC_KEY_ALGORITHMS \ "rsa-sha2-512-cert-v01@openssh.com," \ @@ -138,6 +143,11 @@ "ssh-rsa-cert-v01@openssh.com" \ DSA_PUBLIC_KEY_ALGORITHMS "," \ HOSTKEYS +#define DEFAULT_PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + DEFAULT_HOSTKEYS #ifdef WITH_GEX #define GEX_SHA256 "diffie-hellman-group-exchange-sha256," @@ -149,16 +159,17 @@ #define CHACHA20 "chacha20-poly1305@openssh.com," -#define KEY_EXCHANGE \ +#define DEFAULT_KEY_EXCHANGE \ CURVE25519 \ ECDH \ "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ GEX_SHA256 \ - "diffie-hellman-group14-sha256," \ - "diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + "diffie-hellman-group14-sha256" \ + #define KEY_EXCHANGE_SUPPORTED \ GEX_SHA1 \ - KEY_EXCHANGE + DEFAULT_KEY_EXCHANGE \ + ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" /* RFC 8308 */ #define KEX_EXTENSION_CLIENT "ext-info-c" @@ -212,27 +223,27 @@ static const char *fips_methods[] = { /* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ static const char *default_methods[] = { - KEY_EXCHANGE, - PUBLIC_KEY_ALGORITHMS, - AES BLOWFISH DES, - AES BLOWFISH DES, - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - "none", - "none", - "", - "", - NULL + DEFAULT_KEY_EXCHANGE, + DEFAULT_PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES, + CHACHA20 AES, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + ZLIB, + ZLIB, + "", + "", + NULL }; /* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ static const char *supported_methods[] = { KEY_EXCHANGE_SUPPORTED, PUBLIC_KEY_ALGORITHMS, - CHACHA20 AES BLOWFISH DES_SUPPORTED, - CHACHA20 AES BLOWFISH DES_SUPPORTED, - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", - "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, ZLIB, ZLIB, "", @@ -954,13 +965,13 @@ char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) /** * @internal * - * @brief Return a new allocated string containing only the FIPS allowed + * @brief Return a newly allocated string containing only the FIPS allowed * algorithms from the list. * * @param[in] algo The type of the methods to filter * @param[in] list The list to be filtered * - * @return A new allocated list containing only the FIPS allowed algorithms from + * @return A newly allocated list containing only the FIPS allowed algorithms from * the list; NULL in case of error. */ char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) @@ -979,10 +990,18 @@ int ssh_make_sessionid(ssh_session session) ssh_buffer client_hash = NULL; ssh_buffer buf = NULL; ssh_string server_pubkey_blob = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum client_pubkey, server_pubkey; +#else + bignum client_pubkey = NULL, server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef WITH_GEX +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L const_bignum modulus, generator; -#endif +#else + bignum modulus = NULL, generator = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* WITH_GEX */ int rc = SSH_ERROR; buf = ssh_buffer_new(); @@ -1075,6 +1094,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ break; #ifdef WITH_GEX case SSH_KEX_DH_GEX_SHA1: @@ -1106,6 +1129,10 @@ int ssh_make_sessionid(ssh_session session) if (rc != SSH_OK) { goto error; } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif /* OPENSSL_VERSION_NUMBER */ break; #endif /* WITH_GEX */ #ifdef HAVE_ECDH @@ -1125,7 +1152,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_ECDH */ #ifdef HAVE_CURVE25519 case SSH_KEX_CURVE25519_SHA256: case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: @@ -1140,7 +1167,7 @@ int ssh_make_sessionid(ssh_session session) goto error; } break; -#endif +#endif /* HAVE_CURVE25519 */ } rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); if (rc != SSH_OK) { @@ -1226,10 +1253,10 @@ int ssh_make_sessionid(ssh_session session) session->next_crypto->session_id_len = session->next_crypto->digest_len; } #ifdef DEBUG_CRYPTO - printf("Session hash: \n"); + SSH_LOG(SSH_LOG_DEBUG, "Session hash: \n"); ssh_log_hexdump("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->session_id_len); -#endif +#endif /* DEBUG_CRYPTO */ rc = SSH_OK; error: @@ -1241,6 +1268,10 @@ int ssh_make_sessionid(ssh_session session) session->out_hashbuf = NULL; SSH_STRING_FREE(num); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ return rc; } @@ -1425,7 +1456,7 @@ int ssh_generate_session_keys(ssh_session session) intkey_cli_to_srv_len); ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, intkey_srv_to_cli_len); -#endif +#endif /* DEBUG_CRYPTO */ rc = 0; error: diff --git a/libssh/src/known_hosts.c b/libssh/src/known_hosts.c index ec6da30..84e1557 100644 --- a/libssh/src/known_hosts.c +++ b/libssh/src/known_hosts.c @@ -45,6 +45,10 @@ # include #endif +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif + /** * @addtogroup libssh_session * @@ -61,7 +65,7 @@ * @param[out] file A pointer to the known host file. Could be pointing to * NULL at start. * - * @param[in] filename The file name of the known host file. + * @param[in] filename The filename of the known host file. * * @param[out] found_type A pointer to a string to be set with the found key * type. @@ -74,7 +78,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, const char *filename, const char **found_type) { - char buffer[4096] = {0}; + char buffer[MAX_LINE_SIZE] = {0}; char *ptr; struct ssh_tokens_st *tokens; @@ -206,8 +210,8 @@ static int match_hashed_host(const char *host, const char *sourcehash) HMACCTX mac; char *source; char *b64hash; - int match; - unsigned int size; + int match, rc; + size_t size; if (strncmp(sourcehash, "|1|", 3) != 0) { return 0; @@ -252,8 +256,20 @@ static int match_hashed_host(const char *host, const char *sourcehash) return 0; } size = sizeof(buffer); - hmac_update(mac, host, strlen(host)); - hmac_final(mac, buffer, &size); + rc = hmac_update(mac, host, strlen(host)); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + rc = hmac_final(mac, buffer, &size); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } if (size == ssh_buffer_get_len(hash) && memcmp(buffer, ssh_buffer_get(hash), size) == 0) { @@ -431,7 +447,6 @@ char * ssh_dump_knownhost(ssh_session session) { ssh_key server_pubkey = NULL; char *host; char *hostport; - size_t len = 4096; char *buffer; char *b64_key; int rc; @@ -467,7 +482,7 @@ char * ssh_dump_knownhost(ssh_session session) { return NULL; } - buffer = calloc (1, 4096); + buffer = calloc (1, MAX_LINE_SIZE); if (!buffer) { SAFE_FREE(host); return NULL; @@ -480,7 +495,7 @@ char * ssh_dump_knownhost(ssh_session session) { return NULL; } - snprintf(buffer, len, + snprintf(buffer, MAX_LINE_SIZE, "%s %s %s\n", host, server_pubkey->type_c, @@ -513,10 +528,12 @@ int ssh_write_knownhost(ssh_session session) errno = 0; file = fopen(session->opts.knownhosts, "a"); if (file == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, + "%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -524,7 +541,7 @@ int ssh_write_knownhost(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -536,13 +553,15 @@ int ssh_write_knownhost(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } diff --git a/libssh/src/knownhosts.c b/libssh/src/knownhosts.c index f2ef088..49bdf57 100644 --- a/libssh/src/knownhosts.c +++ b/libssh/src/knownhosts.c @@ -44,6 +44,10 @@ #include "libssh/knownhosts.h" #include "libssh/token.h" +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 8192 +#endif + /** * @addtogroup libssh_session * @@ -54,8 +58,9 @@ static int hash_hostname(const char *name, unsigned char *salt, unsigned int salt_size, unsigned char **hash, - unsigned int *hash_size) + size_t *hash_size) { + int rc; HMACCTX mac_ctx; mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); @@ -63,8 +68,13 @@ static int hash_hostname(const char *name, return SSH_ERROR; } - hmac_update(mac_ctx, name, strlen(name)); - hmac_final(mac_ctx, *hash, hash_size); + rc = hmac_update(mac_ctx, name, strlen(name)); + if (rc != 1) + return SSH_ERROR; + + rc = hmac_final(mac_ctx, *hash, hash_size); + if (rc != 1) + return SSH_ERROR; return SSH_OK; } @@ -77,7 +87,7 @@ static int match_hashed_hostname(const char *host, const char *hashed_host) ssh_buffer hash = NULL; unsigned char hashed_buf[256] = {0}; unsigned char *hashed_buf_ptr = hashed_buf; - unsigned int hashed_buf_size = sizeof(hashed_buf); + size_t hashed_buf_size = sizeof(hashed_buf); int cmp; int rc; int match = 0; @@ -216,7 +226,7 @@ static int ssh_known_hosts_read_entries(const char *match, const char *filename, struct ssh_list **entries) { - char line[8192]; + char line[MAX_LINE_SIZE]; size_t lineno = 0; size_t len = 0; FILE *fp; @@ -224,8 +234,9 @@ static int ssh_known_hosts_read_entries(const char *match, fp = fopen(filename, "r"); if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); /* The missing file is not an error here */ return SSH_OK; } @@ -472,6 +483,9 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ #ifdef HAVE_DSA case SSH_KEYTYPE_DSS: return "ssh-dss"; +#else + SSH_LOG(SSH_LOG_WARN, "DSS keys are not supported by this build"); + break; #endif #ifdef HAVE_ECDH case SSH_KEYTYPE_ECDSA_P256: @@ -480,13 +494,22 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ return "ecdsa-sha2-nistp384"; case SSH_KEYTYPE_ECDSA_P521: return "ecdsa-sha2-nistp521"; +#else + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); + break; #endif case SSH_KEYTYPE_UNKNOWN: default: - SSH_LOG(SSH_LOG_WARN, "The given type %d is not a base private key type " - "or is unsupported", type); - return NULL; + SSH_LOG(SSH_LOG_WARN, + "The given type %d is not a base private key type " + "or is unsupported", + type); } + + return NULL; } /** @@ -568,6 +591,8 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); if (algo == NULL) { + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); continue; } @@ -596,7 +621,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) /** * @brief Parse a line from a known_hosts entry into a structure * - * This parses an known_hosts entry into a structure with the key in a libssh + * This parses a known_hosts entry into a structure with the key in a libssh * consumeable form. You can use the PKI key function to further work with it. * * @param[in] hostname The hostname to match the line to @@ -604,7 +629,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) * @param[in] line The line to compare and parse if we have a hostname * match. * - * @param[in] entry A pointer to store the the allocated known_hosts + * @param[in] entry A pointer to store the allocated known_hosts * entry structure. The user needs to free the memory * using SSH_KNOWNHOSTS_ENTRY_FREE(). * @@ -617,6 +642,7 @@ int ssh_known_hosts_parse_line(const char *hostname, struct ssh_knownhosts_entry *e = NULL; char *known_host = NULL; char *p; + char *save_tok = NULL; enum ssh_keytypes_e key_type; int match = 0; int rc = SSH_OK; @@ -627,7 +653,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* match pattern for hostname or hashed hostname */ - p = strtok(known_host, " "); + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { free(known_host); return SSH_ERROR; @@ -648,14 +674,16 @@ int ssh_known_hosts_parse_line(const char *hostname, match = match_hashed_hostname(hostname, p); } - for (q = strtok(p, ","); + save_tok = NULL; + + for (q = strtok_r(p, ",", &save_tok); q != NULL; - q = strtok(NULL, ",")) { + q = strtok_r(NULL, ",", &save_tok)) { int cmp; if (q[0] == '[' && hostname[0] != '[') { /* Corner case: We have standard port so we do not have - * hostname in square braces. But the patern is enclosed + * hostname in square braces. But the pattern is enclosed * in braces with, possibly standard or wildcard, port. * We need to test against [host]:port pair here. */ @@ -698,7 +726,9 @@ int ssh_known_hosts_parse_line(const char *hostname, goto out; } - p = strtok(known_host, " "); + save_tok = NULL; + + p = strtok_r(known_host, " ", &save_tok); if (p == NULL ) { rc = SSH_ERROR; goto out; @@ -711,7 +741,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* pubkey type */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -725,7 +755,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* public key */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p == NULL) { rc = SSH_ERROR; goto out; @@ -743,7 +773,7 @@ int ssh_known_hosts_parse_line(const char *hostname, } /* comment */ - p = strtok(NULL, " "); + p = strtok_r(NULL, " ", &save_tok); if (p != NULL) { p = strstr(line, p); if (p != NULL) { @@ -766,12 +796,12 @@ int ssh_known_hosts_parse_line(const char *hostname, } /** - * @brief Check if the set hostname and port matches an entry in known_hosts. + * @brief Check if the set hostname and port match an entry in known_hosts. * - * This check if the set hostname and port has an entry in the known_hosts file. + * This check if the set hostname and port have an entry in the known_hosts file. * You need to set at least the hostname using ssh_options_set(). * - * @param[in] session The session with with the values set to check. + * @param[in] session The session with the values set to check. * * @return A ssh_known_hosts_e return value. */ @@ -884,18 +914,18 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) * * @param[in] session The session with information to export. * - * @param[in] pentry_string A pointer to a string to store the alloocated + * @param[in] pentry_string A pointer to a string to store the allocated * line of the entry. The user must free it using * ssh_string_free_char(). * - * @return SSH_OK on succcess, SSH_ERROR otherwise. + * @return SSH_OK on success, SSH_ERROR otherwise. */ int ssh_session_export_known_hosts_entry(ssh_session session, char **pentry_string) { ssh_key server_pubkey = NULL; char *host = NULL; - char entry_buf[4096] = {0}; + char entry_buf[MAX_LINE_SIZE] = {0}; char *b64_key = NULL; int rc; @@ -953,7 +983,7 @@ int ssh_session_export_known_hosts_entry(ssh_session session, } /** - * @brief Add the current connected server to the user known_hosts file. + * @brief Adds the currently connected server to the user known_hosts file. * * This adds the currently connected server to the known_hosts file by * appending a new line at the end. The global known_hosts file is considered @@ -971,6 +1001,7 @@ int ssh_session_update_known_hosts(ssh_session session) size_t nwritten; size_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session->opts.knownhosts == NULL) { rc = ssh_options_apply(session); @@ -986,7 +1017,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (errno == ENOENT) { dir = ssh_dirname(session->opts.knownhosts); if (dir == NULL) { - ssh_set_error(session, SSH_FATAL, "%s", strerror(errno)); + ssh_set_error(session, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -994,7 +1026,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (rc < 0) { ssh_set_error(session, SSH_FATAL, "Cannot create %s directory: %s", - dir, strerror(errno)); + dir, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); SAFE_FREE(dir); return SSH_ERROR; } @@ -1006,7 +1039,8 @@ int ssh_session_update_known_hosts(ssh_session session) ssh_set_error(session, SSH_FATAL, "Couldn't open known_hosts file %s" " for appending: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } } else { @@ -1029,7 +1063,8 @@ int ssh_session_update_known_hosts(ssh_session session) if (nwritten != len || ferror(fp)) { ssh_set_error(session, SSH_FATAL, "Couldn't append to known_hosts file %s: %s", - session->opts.knownhosts, strerror(errno)); + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); fclose(fp); return SSH_ERROR; } @@ -1104,7 +1139,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, } /** - * @brief Get the known_hosts entry for the current connected session. + * @brief Get the known_hosts entry for the currently connected session. * * @param[in] session The session to validate. * @@ -1123,7 +1158,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n - * SSH_KNOWN_HOSTS_ERROR: There had been an eror checking the host. + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. * * @see ssh_knownhosts_entry_free() */ diff --git a/libssh/src/legacy.c b/libssh/src/legacy.c index 8ae3d92..7b165db 100644 --- a/libssh/src/legacy.c +++ b/libssh/src/legacy.c @@ -20,7 +20,7 @@ */ /** functions in that file are wrappers to the newly named functions. All - * of them are depreciated, but these wrapper will avoid breaking backward + * of them are depreciated, but these wrappers will avoid breaking backward * compatibility */ @@ -83,12 +83,20 @@ 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; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = privatekey->dsa_priv; key->rsa = privatekey->rsa_priv; +#else + key->key = privatekey->key_priv; +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_userauth_publickey(session, username, key); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = NULL; key->rsa = NULL; +#else + key->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(key); return rc; @@ -164,7 +172,7 @@ int channel_change_pty_size(ssh_channel channel,int cols,int rows){ } ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ - return ssh_channel_accept_forward(session, timeout_ms, NULL); + return ssh_channel_open_forward_port(session, timeout_ms, NULL, NULL, NULL); } int channel_close(ssh_channel channel){ @@ -354,18 +362,26 @@ void publickey_free(ssh_public_key key) { #ifdef HAVE_LIBGCRYPT gcry_sexp_release(key->dsa_pub); #elif defined HAVE_LIBCRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA_free(key->dsa_pub); -#endif +#else + EVP_PKEY_free(key->key_pub); +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_LIBGCRYPT */ break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(key->rsa_pub); #elif defined HAVE_LIBCRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L RSA_free(key->rsa_pub); +#else + EVP_PKEY_free(key->key_pub); +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(key->rsa_pub); SAFE_FREE(key->rsa_pub); -#endif +#endif /* HAVE_LIBGCRYPT */ break; default: break; @@ -387,12 +403,20 @@ 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; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L privkey->dsa = prv->dsa_priv; privkey->rsa = prv->rsa_priv; +#else + privkey->key = prv->key_priv; +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L privkey->dsa = NULL; privkey->rsa = NULL; +#else + privkey->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(privkey); if (rc < 0) { return NULL; @@ -438,11 +462,17 @@ ssh_private_key privatekey_from_file(ssh_session session, } privkey->type = key->type; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L privkey->dsa_priv = key->dsa; privkey->rsa_priv = key->rsa; key->dsa = NULL; key->rsa = NULL; +#else + privkey->key_priv = key->key; + + key->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(key); @@ -464,12 +494,16 @@ void privatekey_free(ssh_private_key prv) { gcry_sexp_release(prv->dsa_priv); gcry_sexp_release(prv->rsa_priv); #elif defined HAVE_LIBCRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA_free(prv->dsa_priv); RSA_free(prv->rsa_priv); +#else + EVP_PKEY_free(prv->key_priv); +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_LIBMBEDCRYPTO mbedtls_pk_free(prv->rsa_priv); SAFE_FREE(prv->rsa_priv); -#endif +#endif /* HAVE_LIBGCRYPT */ memset(prv, 0, sizeof(struct ssh_private_key_struct)); SAFE_FREE(prv); } @@ -530,10 +564,15 @@ ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) { pubkey->type = key->type; pubkey->type_c = key->type_c; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L pubkey->dsa_pub = key->dsa; key->dsa = NULL; pubkey->rsa_pub = key->rsa; key->rsa = NULL; +#else + pubkey->key_pub = key->key; + key->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(key); @@ -557,16 +596,24 @@ ssh_string publickey_to_string(ssh_public_key pubkey) { key->type = pubkey->type; key->type_c = pubkey->type_c; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = pubkey->dsa_pub; key->rsa = pubkey->rsa_pub; +#else + key->key = pubkey->key_pub; +#endif /* OPENSSL_VERSION_NUMBER */ rc = ssh_pki_export_pubkey_blob(key, &key_blob); if (rc < 0) { key_blob = NULL; } +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = NULL; key->rsa = NULL; +#else + key->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(key); return key_blob; @@ -622,8 +669,12 @@ int ssh_publickey_to_file(ssh_session session, fp = fopen(file, "w+"); if (fp == NULL) { - ssh_set_error(session, SSH_REQUEST_DENIED, - "Error opening %s: %s", file, strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Error opening %s: %s", + file, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -735,7 +786,7 @@ int ssh_accept(ssh_session session) { } int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { - return ssh_channel_write(channel, data, len); + return ssh_channel_write_stderr(channel, data, len); } /** @deprecated diff --git a/libssh/src/libcrypto-compat.c b/libssh/src/libcrypto-compat.c index 01ca70e..33b8dff 100644 --- a/libssh/src/libcrypto-compat.c +++ b/libssh/src/libcrypto-compat.c @@ -12,19 +12,6 @@ #include #include "libcrypto-compat.h" -#ifndef OPENSSL_NO_ENGINE -#include -#endif - -static void *OPENSSL_zalloc(size_t num) -{ - void *ret = OPENSSL_malloc(num); - - if (ret != NULL) - memset(ret, 0, num); - return ret; -} - int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { /* If the fields n and e in r are NULL, the corresponding input @@ -236,111 +223,18 @@ int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) EVP_MD_CTX *EVP_MD_CTX_new(void) { - return OPENSSL_zalloc(sizeof(EVP_MD_CTX)); -} - -static void OPENSSL_clear_free(void *str, size_t num) -{ - if (str == NULL) - return; - if (num) - OPENSSL_cleanse(str, num); - OPENSSL_free(str); -} - -/* This call frees resources associated with the context */ -int EVP_MD_CTX_reset(EVP_MD_CTX *ctx) -{ - if (ctx == NULL) - return 1; - - /* - * Don't assume ctx->md_data was cleaned in EVP_Digest_Final, because - * sometimes only copies of the context are ever finalised. - */ - if (ctx->digest && ctx->digest->cleanup - && !EVP_MD_CTX_test_flags(ctx, EVP_MD_CTX_FLAG_CLEANED)) - ctx->digest->cleanup(ctx); - if (ctx->digest && ctx->digest->ctx_size && ctx->md_data - && !EVP_MD_CTX_test_flags(ctx, EVP_MD_CTX_FLAG_REUSE)) { - OPENSSL_clear_free(ctx->md_data, ctx->digest->ctx_size); - } - EVP_PKEY_CTX_free(ctx->pctx); -#ifndef OPENSSL_NO_ENGINE - ENGINE_finish(ctx->engine); -#endif - OPENSSL_cleanse(ctx, sizeof(*ctx)); - - return 1; -} - -void EVP_MD_CTX_free(EVP_MD_CTX *ctx) -{ - EVP_MD_CTX_reset(ctx); - OPENSSL_free(ctx); -} - -int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx) -{ - EVP_CIPHER_CTX_init(ctx); - return 1; -} - -HMAC_CTX *HMAC_CTX_new(void) -{ - HMAC_CTX *ctx = OPENSSL_zalloc(sizeof(HMAC_CTX)); - + EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(EVP_MD_CTX)); if (ctx != NULL) { - if (!HMAC_CTX_reset(ctx)) { - HMAC_CTX_free(ctx); - return NULL; - } + EVP_MD_CTX_init(ctx); } return ctx; } -static void hmac_ctx_cleanup(HMAC_CTX *ctx) -{ - EVP_MD_CTX_reset(&ctx->i_ctx); - EVP_MD_CTX_reset(&ctx->o_ctx); - EVP_MD_CTX_reset(&ctx->md_ctx); - ctx->md = NULL; - ctx->key_length = 0; - OPENSSL_cleanse(ctx->key, sizeof(ctx->key)); -} - -void HMAC_CTX_free(HMAC_CTX *ctx) -{ - if (ctx != NULL) { - hmac_ctx_cleanup(ctx); -#if OPENSSL_VERSION_NUMBER > 0x10100000L - EVP_MD_CTX_free(&ctx->i_ctx); - EVP_MD_CTX_free(&ctx->o_ctx); - EVP_MD_CTX_free(&ctx->md_ctx); -#endif - OPENSSL_free(ctx); - } -} - -int HMAC_CTX_reset(HMAC_CTX *ctx) -{ - HMAC_CTX_init(ctx); - return 1; -} - -#ifndef HAVE_OPENSSL_EVP_CIPHER_CTX_NEW -EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void) -{ - return OPENSSL_zalloc(sizeof(EVP_CIPHER_CTX)); -} - -void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) +void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { - /* EVP_CIPHER_CTX_reset(ctx); alias */ - EVP_CIPHER_CTX_init(ctx); + EVP_MD_CTX_cleanup(ctx); OPENSSL_free(ctx); } -#endif void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) diff --git a/libssh/src/libcrypto-compat.h b/libssh/src/libcrypto-compat.h index 0082e20..48e30bd 100644 --- a/libssh/src/libcrypto-compat.h +++ b/libssh/src/libcrypto-compat.h @@ -2,6 +2,11 @@ #define LIBCRYPTO_COMPAT_H #include + +#define NISTP256 "P-256" +#define NISTP384 "P-384" +#define NISTP521 "P-521" + #if OPENSSL_VERSION_NUMBER < 0x10100000L #include @@ -30,16 +35,9 @@ int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s); void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps); int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s); -int EVP_MD_CTX_reset(EVP_MD_CTX *ctx); EVP_MD_CTX *EVP_MD_CTX_new(void); void EVP_MD_CTX_free(EVP_MD_CTX *ctx); -int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx); - -HMAC_CTX *HMAC_CTX_new(void); -int HMAC_CTX_reset(HMAC_CTX *ctx); -void HMAC_CTX_free(HMAC_CTX *ctx); - void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); diff --git a/libssh/src/libcrypto.c b/libssh/src/libcrypto.c index 3db75df..468b63f 100644 --- a/libssh/src/libcrypto.c +++ b/libssh/src/libcrypto.c @@ -26,34 +26,45 @@ #include #ifdef HAVE_SYS_TIME_H #include -#endif +#endif /* HAVE_SYS_TIME_H */ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" #include "libssh/wrapper.h" #include "libssh/libcrypto.h" +#include "libssh/pki.h" +#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#endif #ifdef HAVE_LIBCRYPTO +#include #include #include +#if OPENSSL_VERSION_NUMBER < 0x30000000L #include #include #include -#include +#else +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ #include +#include #include "libcrypto-compat.h" #ifdef HAVE_OPENSSL_AES_H #define HAS_AES #include -#endif +#endif /* HAVE_OPENSSL_AES_H */ #ifdef HAVE_OPENSSL_DES_H #define HAS_DES #include -#endif +#endif /* HAVE_OPENSSL_DES_H */ #if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) #include @@ -62,18 +73,20 @@ #include "libssh/crypto.h" -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX #include -#endif - -#ifdef HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT -#include -#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ #include "libssh/crypto.h" static int libcrypto_initialized = 0; +static ENGINE *engine = NULL; + void ssh_reseed(void){ #ifndef _WIN32 struct timeval tv; @@ -82,70 +95,34 @@ void ssh_reseed(void){ #endif } -/** - * @brief Get random bytes - * - * Make sure to always check the return code of this function! - * - * @param[in] where The buffer to fill with random bytes - * - * @param[in] len The size of the buffer to fill. - * - * @param[in] strong Use a strong or private RNG source. - * - * @return 1 on success, 0 on error. - */ -int ssh_get_random(void *where, int len, int strong) +ENGINE *pki_get_engine(void) { -#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES - if (strong) { - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_priv_bytes(where, len); - } -#else - (void)strong; -#endif /* HAVE_RAND_PRIV_BYTES */ + int ok; - /* Returns -1 when not supported, 0 on error, 1 on success */ - return !!RAND_bytes(where, len); -} + if (engine == NULL) { + ENGINE_load_builtin_engines(); -SHACTX sha1_init(void) -{ - int rc; - SHACTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Could not load the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + SSH_LOG(SSH_LOG_INFO, "Engine loaded successfully"); + + ok = ENGINE_init(engine); + if (!ok) { + SSH_LOG(SSH_LOG_WARN, + "Could not initialize the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + ENGINE_free(engine); + return NULL; + } -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - SHACTX c = sha1_init(); - if (c != NULL) { - sha1_update(c, digest, len); - sha1_final(hash, c); + SSH_LOG(SSH_LOG_INFO, "Engine init success"); } + return engine; } #ifdef HAVE_OPENSSL_ECC @@ -165,7 +142,7 @@ static const EVP_MD *nid_to_evpmd(int nid) return NULL; } -void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) +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(); @@ -190,7 +167,7 @@ EVPCTX evp_init(int nid) return ctx; } -void evp_update(EVPCTX ctx, const void *data, unsigned long len) +void evp_update(EVPCTX ctx, const void *data, size_t len) { EVP_DigestUpdate(ctx, data, len); } @@ -200,152 +177,10 @@ void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) EVP_DigestFinal(ctx, md, mdlen); EVP_MD_CTX_free(ctx); } -#endif - -SHA256CTX sha256_init(void) -{ - int rc; - SHA256CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha256_update(SHA256CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha256_final(unsigned char *md, SHA256CTX c) -{ - unsigned int mdlen = 0; +#endif /* HAVE_OPENSSL_ECC */ - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA256CTX c = sha256_init(); - if (c != NULL) { - sha256_update(c, digest, len); - sha256_final(hash, c); - } -} - -SHA384CTX sha384_init(void) -{ - int rc; - SHA384CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha384_update(SHA384CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha384_final(unsigned char *md, SHA384CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA384CTX c = sha384_init(); - if (c != NULL) { - sha384_update(c, digest, len); - sha384_final(hash, c); - } -} - -SHA512CTX sha512_init(void) -{ - int rc = 0; - SHA512CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); - if (rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void sha512_update(SHA512CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void sha512_final(unsigned char *md, SHA512CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - SHA512CTX c = sha512_init(); - if (c != NULL) { - sha512_update(c, digest, len); - sha512_final(hash, c); - } -} - -MD5CTX md5_init(void) -{ - int rc; - MD5CTX c = EVP_MD_CTX_create(); - if (c == NULL) { - return NULL; - } - EVP_MD_CTX_init(c); - rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); - if(rc == 0) { - EVP_MD_CTX_destroy(c); - c = NULL; - } - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) -{ - EVP_DigestUpdate(c, data, len); -} - -void md5_final(unsigned char *md, MD5CTX c) -{ - unsigned int mdlen = 0; - - EVP_DigestFinal(c, md, &mdlen); - EVP_MD_CTX_destroy(c); -} - -#ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#if OPENSSL_VERSION_NUMBER < 0x30000000L static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) { switch (digest_type) { @@ -360,19 +195,50 @@ static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) } return NULL; } +#else +static const char *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return SN_sha1; + case SSH_KDF_SHA256: + return SN_sha256; + case SSH_KDF_SHA384: + return SN_sha384; + case SSH_KDF_SHA512: + return SN_sha512; + } + return NULL; +} +#endif /* OPENSSL_VERSION_NUMBER */ int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { + int rc = -1; +#if OPENSSL_VERSION_NUMBER < 0x30000000L EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); - int rc; +#else + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM *params = NULL; + const char *md = sshkdf_digest_to_md(crypto->digest_type); + + EVP_KDF_free(kdf); + if (param_bld == NULL) { + EVP_KDF_CTX_free(ctx); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ if (ctx == NULL) { - return -1; + goto out; } +#if OPENSSL_VERSION_NUMBER < 0x30000000L rc = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_MD, sshkdf_digest_to_md(crypto->digest_type)); if (rc != 1) { @@ -400,8 +266,60 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, if (rc != 1) { goto out; } +#else + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_DIGEST, + md, strlen(md)); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_KDF_PARAM_KEY, + key, key_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rc != 1) { + rc = -1; + goto out; + } + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char*)&key_type, 1); + if (rc != 1) { + rc = -1; + goto out; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = -1; + goto out; + } + + rc = EVP_KDF_derive(ctx, output, requested_len, params); + if (rc != 1) { + rc = -1; + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(params); +#endif EVP_KDF_CTX_free(ctx); if (rc < 0) { return rc; @@ -412,64 +330,83 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, #else int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -#endif +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { - HMACCTX ctx = NULL; +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc = -1; - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - return NULL; - } + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, len); + if (pkey == NULL) { + goto error; + } - switch(type) { + switch (type) { case SSH_HMAC_SHA1: - HMAC_Init_ex(ctx, key, len, EVP_sha1(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha1(), NULL, pkey); + break; case SSH_HMAC_SHA256: - HMAC_Init_ex(ctx, key, len, EVP_sha256(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); + break; case SSH_HMAC_SHA512: - HMAC_Init_ex(ctx, key, len, EVP_sha512(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha512(), NULL, pkey); + break; case SSH_HMAC_MD5: - HMAC_Init_ex(ctx, key, len, EVP_md5(), NULL); - break; + rc = EVP_DigestSignInit(ctx, NULL, EVP_md5(), NULL, pkey); + break; default: - HMAC_CTX_free(ctx); - ctx = NULL; - } + rc = -1; + break; + } - return ctx; + EVP_PKEY_free(pkey); + if (rc != 1) { + goto error; + } + return ctx; + +error: + EVP_MD_CTX_free(ctx); + return NULL; } -void hmac_update(HMACCTX ctx, const void *data, unsigned long len) { - HMAC_Update(ctx, data, len); +int hmac_update(HMACCTX ctx, const void *data, size_t len) +{ + return EVP_DigestSignUpdate(ctx, data, len); } -void hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, unsigned int *len) { - HMAC_Final(ctx,hashmacbuf,len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len) +{ + size_t res = *len; + int rc; + rc = EVP_DigestSignFinal(ctx, hashmacbuf, &res); + EVP_MD_CTX_free(ctx); + if (rc == 1) { + *len = res; + } -#if OPENSSL_VERSION_NUMBER > 0x10100000L - HMAC_CTX_free(ctx); - ctx = NULL; -#else - HMAC_cleanup(ctx); - SAFE_FREE(ctx); - ctx = NULL; -#endif + return rc; } -static void evp_cipher_init(struct ssh_cipher_struct *cipher) { +static void evp_cipher_init(struct ssh_cipher_struct *cipher) +{ if (cipher->ctx == NULL) { cipher->ctx = EVP_CIPHER_CTX_new(); + } else { + EVP_CIPHER_CTX_init(cipher->ctx); } switch(cipher->ciphertype){ @@ -482,7 +419,6 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { case SSH_AES256_CBC: cipher->cipher = EVP_aes_256_cbc(); break; -#ifdef HAVE_OPENSSL_EVP_AES_CTR case SSH_AES128_CTR: cipher->cipher = EVP_aes_128_ctr(); break; @@ -492,26 +428,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { case SSH_AES256_CTR: cipher->cipher = EVP_aes_256_ctr(); break; -#else - case SSH_AES128_CTR: - case SSH_AES192_CTR: - case SSH_AES256_CTR: - SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); - break; -#endif -#ifdef HAVE_OPENSSL_EVP_AES_GCM case SSH_AEAD_AES128_GCM: cipher->cipher = EVP_aes_128_gcm(); break; case SSH_AEAD_AES256_GCM: cipher->cipher = EVP_aes_256_gcm(); break; -#else - case SSH_AEAD_AES128_GCM: - case SSH_AEAD_AES256_GCM: - SSH_LOG(SSH_LOG_WARNING, "This cipher is not available in evp_cipher_init"); - break; -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ case SSH_3DES_CBC: cipher->cipher = EVP_des_ede3_cbc(); break; @@ -520,7 +442,7 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) { cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ -#endif +#endif /* WITH_BLOWFISH_CIPHER */ case SSH_AEAD_CHACHA20_POLY1305: SSH_LOG(SSH_LOG_WARNING, "The ChaCha cipher cannot be handled here"); break; @@ -536,7 +458,6 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, int rc; evp_cipher_init(cipher); - EVP_CIPHER_CTX_reset(cipher->ctx); rc = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ @@ -544,7 +465,6 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, return SSH_ERROR; } -#ifdef HAVE_OPENSSL_EVP_AES_GCM /* For AES-GCM we need to set IV in specific way */ if (cipher->ciphertype == SSH_AEAD_AES128_GCM || cipher->ciphertype == SSH_AEAD_AES256_GCM) { @@ -557,7 +477,6 @@ static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, return SSH_ERROR; } } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); @@ -569,7 +488,6 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, int rc; evp_cipher_init(cipher); - EVP_CIPHER_CTX_reset(cipher->ctx); rc = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); if (rc != 1){ @@ -577,7 +495,6 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, return SSH_ERROR; } -#ifdef HAVE_OPENSSL_EVP_AES_GCM /* For AES-GCM we need to set IV in specific way */ if (cipher->ciphertype == SSH_AEAD_AES128_GCM || cipher->ciphertype == SSH_AEAD_AES256_GCM) { @@ -590,7 +507,6 @@ static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, return SSH_ERROR; } } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); @@ -656,68 +572,6 @@ static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { } } -#ifndef HAVE_OPENSSL_EVP_AES_CTR -/* Some OS (osx, OpenIndiana, ...) have no support for CTR ciphers in EVP_aes */ - -struct ssh_aes_key_schedule { - AES_KEY key; - uint8_t IV[AES_BLOCK_SIZE]; -}; - -static int aes_ctr_set_key(struct ssh_cipher_struct *cipher, void *key, - void *IV) { - int rc; - - if (cipher->aes_key == NULL) { - cipher->aes_key = malloc(sizeof (struct ssh_aes_key_schedule)); - } - if (cipher->aes_key == NULL) { - return SSH_ERROR; - } - ZERO_STRUCTP(cipher->aes_key); - /* CTR doesn't need a decryption key */ - rc = AES_set_encrypt_key(key, cipher->keysize, &cipher->aes_key->key); - if (rc < 0) { - SAFE_FREE(cipher->aes_key); - return SSH_ERROR; - } - memcpy(cipher->aes_key->IV, IV, AES_BLOCK_SIZE); - return SSH_OK; -} - -static void -aes_ctr_encrypt(struct ssh_cipher_struct *cipher, - void *in, - void *out, - size_t len) -{ - unsigned char tmp_buffer[AES_BLOCK_SIZE]; - unsigned int num=0; - /* Some things are special with ctr128 : - * In this case, tmp_buffer is not being used, because it is used to store temporary data - * when an encryption is made on lengths that are not multiple of blocksize. - * Same for num, which is being used to store the current offset in blocksize in CTR - * function. - */ -#ifdef HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT - CRYPTO_ctr128_encrypt(in, out, len, &cipher->aes_key->key, cipher->aes_key->IV, tmp_buffer, &num, (block128_f)AES_encrypt); -#else - AES_ctr128_encrypt(in, out, len, &cipher->aes_key->key, cipher->aes_key->IV, tmp_buffer, &num); -#endif /* HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT */ -} - -static void aes_ctr_cleanup(struct ssh_cipher_struct *cipher){ - if (cipher != NULL) { - if (cipher->aes_key != NULL) { - explicit_bzero(cipher->aes_key, sizeof(*cipher->aes_key)); - } - SAFE_FREE(cipher->aes_key); - } -} - -#endif /* HAVE_OPENSSL_EVP_AES_CTR */ - -#ifdef HAVE_OPENSSL_EVP_AES_GCM static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, void *in, @@ -888,7 +742,462 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, return SSH_OK; } -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ +#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) + +struct chacha20_poly1305_keysched { + /* cipher handle used for encrypting the packets */ + EVP_CIPHER_CTX *main_evp; + /* cipher handle used for encrypting the length field */ + EVP_CIPHER_CTX *header_evp; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* mac handle used for authenticating the packets */ + EVP_PKEY_CTX *pctx; + /* Poly1305 key */ + EVP_PKEY *key; + /* MD context for digesting data in poly1305 */ + EVP_MD_CTX *mctx; +#else + /* MAC context used to do poly1305 */ + EVP_MAC_CTX *mctx; +#endif /* OPENSSL_VERSION_NUMBER */ +}; + +static void +chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + EVP_CIPHER_CTX_free(ctx->main_evp); + ctx->main_evp = NULL; + EVP_CIPHER_CTX_free(ctx->header_evp); + ctx->header_evp = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* ctx->pctx is freed as part of MD context */ + EVP_PKEY_free(ctx->key); + ctx->key = NULL; + EVP_MD_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#else + EVP_MAC_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + SAFE_FREE(cipher->chacha20_schedule); +} + +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; + int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC *mac = NULL; +#endif + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + /* ChaCha20 initialization */ + /* K2 uses the first half of the key */ + ctx->main_evp = EVP_CIPHER_CTX_new(); + if (ctx->main_evp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + goto out; + } + rv = EVP_EncryptInit_ex(ctx->main_evp, EVP_chacha20(), NULL, u8key, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + goto out; + } + /* K1 uses the second half of the key */ + ctx->header_evp = EVP_CIPHER_CTX_new(); + if (ctx->header_evp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CIPHER_CTX_new failed"); + goto out; + } + ret = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, + u8key + CHACHA20_KEYLEN, NULL); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit failed"); + goto out; + } + + /* The Poly1305 key initialization is delayed to the time we know + * the actual key for packet so we do not need to create a bogus keys + */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ctx->mctx = EVP_MD_CTX_new(); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MD_CTX_new failed"); + return SSH_ERROR; + } +#else + mac = EVP_MAC_fetch(NULL, "poly1305", NULL); + if (mac == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_fetch failed"); + goto out; + } + ctx->mctx = EVP_MAC_CTX_new(mac); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_CTX_new failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_free(mac); +#endif + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +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) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[16] = {0}; + int ret; + + /* Prepare the IV for OpenSSL -- it needs to be 128 b long. First 32 b is + * counter the rest is nonce. The memory is initialized to zeros + * (counter starts from 0) and we set the sequence number in the second half + */ + PUSH_BE_U64(seqbuf, 8, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = EVP_CipherInit_ex(ctx->header_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(header_evp) failed"); + return SSH_ERROR; + } + + ret = EVP_CipherInit_ex(ctx->main_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherInit_ex(main_evp) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +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]; + int ret = SSH_ERROR, len, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq, do_encrypt); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = EVP_CipherUpdate(ctx->main_evp, poly_key, &len, + (unsigned char *)zero_block, sizeof(zero_block)); + if (rv != 1 || len != CHACHA20_BLOCKSIZE) { + SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptUpdate failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + + /* Set the Poly1305 key */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (ctx->key == NULL) { + /* Poly1305 Initialization needs to know the actual key */ + ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, NULL, + poly_key, POLY1305_KEYLEN); + if (ctx->key == NULL) { + SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_new_mac_key failed"); + goto out; + } + rv = EVP_DigestSignInit(ctx->mctx, &ctx->pctx, NULL, NULL, ctx->key); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignInit failed"); + goto out; + } + } 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); + if (rv <= 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_PKEY_CTX_ctrl failed"); + goto out; + } + } +#else + rv = EVP_MAC_init(ctx->mctx, poly_key, POLY1305_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_init failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: + explicit_bzero(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv, outlen; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header EVP */ + rv = chacha20_poly1305_set_iv(cipher, seq, 0); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, len); + if (rv != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + rv = EVP_CipherFinal_ex(ctx->header_evp, out + outlen, &outlen); + if (rv != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + return SSH_ERROR; + } + + 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) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + 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; + size_t taglen = POLY1305_TAGLEN; + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + goto out; + } + + rv = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "poly1305 verify error"); + goto out; + } +#else + rv = EVP_MAC_update(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_update failed"); + goto out; + } + + rv = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_final failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = CRYPTO_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = EVP_CipherUpdate(ctx->main_evp, out, &len, + (uint8_t *)complete_packet + sizeof(uint32_t), + encrypted_size); + if (rv != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + goto out; + } + + rv = EVP_CipherFinal_ex(ctx->main_evp, out + len, &len); + if (rv != 1 || len != 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherFinal_ex failed"); + goto out; + } + + ret = SSH_OK; +out: + 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) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + size_t taglen = POLY1305_TAGLEN; + int ret, outlen = 0; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = EVP_CipherUpdate(ctx->header_evp, + (unsigned char *)&out_packet->length, + &outlen, + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); + if (ret != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (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) { + SSH_LOG(SSH_LOG_PACKET, "EVP_EncryptFinal_ex failed"); + return; + } + + /* step 3, encrypt packet payload (main_evp counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = EVP_CipherUpdate(ctx->main_evp, + out_packet->payload, + &outlen, + in_packet->payload, + len - sizeof(uint32_t)); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_CipherUpdate failed"); + return; + } + + /* step 4, compute the MAC */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); + if (ret <= 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignUpdate failed"); + return; + } + ret = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (ret <= 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DigestSignFinal failed"); + return; + } +#else + ret = EVP_MAC_update(ctx->mctx, (void*)out_packet, len); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_update failed"); + return; + } + + ret = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (ret != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_MAC_final failed"); + return; + } +#endif /* OPENSSL_VERSION_NUMBER */ +} +#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ /* * The table of supported ciphers @@ -906,13 +1215,8 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#endif +#endif /* WITH_BLOWFISH_CIPHER */ #ifdef HAS_AES -#ifndef BROKEN_AES_CTR -/* OpenSSL until 0.9.7c has a broken AES_ctr128_encrypt implementation which - * increments the counter from 2^64 instead of 1. It's better not to use it - */ -#ifdef HAVE_OPENSSL_EVP_AES_CTR { .name = "aes128-ctr", .blocksize = AES_BLOCK_SIZE, @@ -946,42 +1250,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#else /* HAVE_OPENSSL_EVP_AES_CTR */ - { - .name = "aes128-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES128_CTR, - .keysize = 128, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, - { - .name = "aes192-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES192_CTR, - .keysize = 192, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, - { - .name = "aes256-ctr", - .blocksize = AES_BLOCK_SIZE, - .ciphertype = SSH_AES256_CTR, - .keysize = 256, - .set_encrypt_key = aes_ctr_set_key, - .set_decrypt_key = aes_ctr_set_key, - .encrypt = aes_ctr_encrypt, - .decrypt = aes_ctr_encrypt, - .cleanup = aes_ctr_cleanup - }, -#endif /* HAVE_OPENSSL_EVP_AES_CTR */ -#endif /* BROKEN_AES_CTR */ { .name = "aes128-cbc", .blocksize = AES_BLOCK_SIZE, @@ -1015,7 +1283,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#ifdef HAVE_OPENSSL_EVP_AES_GCM { .name = "aes128-gcm@openssh.com", .blocksize = AES_BLOCK_SIZE, @@ -1044,7 +1311,6 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .aead_decrypt = evp_cipher_aead_decrypt, .cleanup = evp_cipher_cleanup }, -#endif /* HAVE_OPENSSL_EVP_AES_GCM */ #endif /* HAS_AES */ #ifdef HAS_DES { @@ -1060,8 +1326,33 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { }, #endif /* HAS_DES */ { +#if defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) + .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" +#endif /* defined(HAVE_OPENSSL_EVP_CHACHA20) && defined(HAVE_OPENSSL_EVP_POLY1305) */ + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, }, +#endif /* WITH_INSECURE_NONE */ { .name = NULL } @@ -1078,7 +1369,9 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) */ int ssh_crypto_init(void) { +#if !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) size_t i; +#endif if (libcrypto_initialized) { return SSH_OK; @@ -1100,11 +1393,12 @@ int ssh_crypto_init(void) /* Bit #57 denotes AES-NI instruction set extension */ OPENSSL_ia32cap &= ~(1LL << 57); } -#endif +#endif /* CAN_DISABLE_AESNI */ #if OPENSSL_VERSION_NUMBER < 0x10100000L OpenSSL_add_all_algorithms(); -#endif +#endif /* OPENSSL_VERSION_NUMBER */ +#if !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1116,6 +1410,7 @@ int ssh_crypto_init(void) break; } } +#endif /* !defined(HAVE_OPENSSL_EVP_CHACHA20) || !defined(HAVE_OPENSSL_EVP_POLY1305) */ libcrypto_initialized = 1; @@ -1132,12 +1427,166 @@ void ssh_crypto_finalize(void) return; } +/* TODO this should finalize engine if it was started, but during atexit calls, + * we are crashing. AFAIK this is related to the dlopened pkcs11 modules calling + * the crypto cleanups earlier. */ +#if 0 + if (engine != NULL) { + ENGINE_finish(engine); + ENGINE_free(engine); + engine = NULL; + } +#endif + #if OPENSSL_VERSION_NUMBER < 0x10100000L + ENGINE_cleanup(); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); -#endif +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ libcrypto_initialized = 0; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +/** + * @internal + * @brief Create EVP_PKEY from parameters + * + * @param[in] name Algorithm to use. For more info see manpage of EVP_PKEY_CTX_new_from_name + * + * @param[in] param_bld Constructed param builder for the pkey + * + * @param[out] pkey Created EVP_PKEY variable + * + * @param[in] selection Reference selections at man EVP_PKEY_FROMDATA + * + * @return 0 on success, -1 on error + */ +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, + EVP_PKEY **pkey, int selection) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +/** + * @brief creates a copy of EVP_PKEY + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] key Key being duplicated from + * + * @param[in] demote Same as at pki_key_dup, only the public + * part of the key gets duplicated if true + * + * @param[out] new_key The key where the duplicate is saved + * + * @return 0 on success, -1 on error + */ +static int evp_dup_pkey(const char* name, const ssh_key key, int demote, + ssh_key new_key) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_KEYPAIR, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } else { + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new, int demote) +{ + return evp_dup_pkey("DSA", key, demote, new); +} + +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new, int demote) +{ + return evp_dup_pkey("RSA", key, demote, new); +} + +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new, int demote) +{ + return evp_dup_pkey("EC", key, demote, new); +} +#endif /* OPENSSL_VERSION_NUMBER */ + #endif /* LIBCRYPTO */ diff --git a/libssh/src/libgcrypt.c b/libssh/src/libgcrypt.c index 8fbf215..da5588a 100644 --- a/libssh/src/libgcrypt.c +++ b/libssh/src/libgcrypt.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "libssh/priv.h" #include "libssh/session.h" @@ -32,10 +33,28 @@ #include "libssh/wrapper.h" #include "libssh/string.h" #include "libssh/misc.h" +#ifdef HAVE_GCRYPT_CHACHA_POLY +#include "libssh/chacha20-poly1305-common.h" +#endif #ifdef HAVE_LIBGCRYPT #include +#ifdef HAVE_GCRYPT_CHACHA_POLY + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + gcry_cipher_hd_t main_hd; + /* cipher handle used for encrypting the length field */ + gcry_cipher_hd_t header_hd; + /* mac handle used for authenticating the packets */ + gcry_mac_hd_t mac_hd; +}; + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + static int libgcrypt_initialized = 0; static int alloc_key(struct ssh_cipher_struct *cipher) { @@ -50,38 +69,6 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { void ssh_reseed(void){ } -int ssh_get_random(void *where, int len, int strong) -{ - /* variable not used in gcrypt */ - (void) strong; - - /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ - gcry_randomize(where,len,GCRY_STRONG_RANDOM); - - return 1; -} - -SHACTX sha1_init(void) { - SHACTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA1, 0); - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); - gcry_md_close(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); -} - #ifdef HAVE_GCRYPT_ECC static int nid_to_md_algo(int nid) { @@ -96,7 +83,7 @@ static int nid_to_md_algo(int nid) return GCRY_MD_NONE; } -void evp(int nid, unsigned char *digest, int len, +void evp(int nid, unsigned char *digest, size_t len, unsigned char *hash, unsigned int *hlen) { int algo = nid_to_md_algo(nid); @@ -121,7 +108,7 @@ EVPCTX evp_init(int nid) return ctx; } -void evp_update(EVPCTX ctx, const void *data, unsigned long len) +void evp_update(EVPCTX ctx, const void *data, size_t len) { gcry_md_write(ctx, data, len); } @@ -135,96 +122,16 @@ void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) } #endif -SHA256CTX sha256_init(void) { - SHA256CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA256, 0); - - return ctx; -} - -void sha256_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha256_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); - gcry_md_close(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash){ - gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); -} - -SHA384CTX sha384_init(void) { - SHA384CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA384, 0); - - return ctx; -} - -void sha384_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha384_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); - gcry_md_close(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); -} - -SHA512CTX sha512_init(void) { - SHA512CTX ctx = NULL; - gcry_md_open(&ctx, GCRY_MD_SHA512, 0); - - return ctx; -} - -void sha512_update(SHACTX c, const void *data, unsigned long len) { - gcry_md_write(c, data, len); -} - -void sha512_final(unsigned char *md, SHACTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); - gcry_md_close(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) { - gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); -} - -MD5CTX md5_init(void) { - MD5CTX c = NULL; - gcry_md_open(&c, GCRY_MD_MD5, 0); - - return c; -} - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - gcry_md_write(c,data,len); -} - -void md5_final(unsigned char *md, MD5CTX c) { - gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); - gcry_md_close(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX c = NULL; switch(type) { @@ -249,14 +156,17 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) { return c; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) { +int hmac_update(HMACCTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return 1; } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) { - *len = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { + unsigned int tmp = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + *len = (size_t)tmp; memcpy(hashmacbuf, gcry_md_read(c, 0), *len); gcry_md_close(c); + return 1; } #ifdef WITH_BLOWFISH_CIPHER @@ -273,10 +183,12 @@ static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *I return -1; } if (gcry_cipher_setkey(cipher->key[0], key, 16)) { + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } @@ -334,12 +246,13 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { return -1; } if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) { + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } if(mode == GCRY_CIPHER_MODE_CBC){ if (gcry_cipher_setiv(cipher->key[0], IV, 16)) { - + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } @@ -350,6 +263,7 @@ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); } else { if(gcry_cipher_setctr(cipher->key[0],IV,16)){ + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } @@ -411,7 +325,7 @@ aes_gcm_encrypt(struct ssh_cipher_struct *cipher, err = gcry_cipher_setiv(cipher->key[0], cipher->last_iv, AES_GCM_IVLEN); - /* This actualy does not increment the packet counter for the + /* This actually does not increment the packet counter for the * current encryption operation, but for the next one. The first * operation needs to be completed with the derived IV. * @@ -540,10 +454,12 @@ static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { return -1; } if (gcry_cipher_setkey(cipher->key[0], key, 24)) { + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); SAFE_FREE(cipher->key); return -1; } @@ -562,6 +478,311 @@ static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } +#ifdef HAVE_GCRYPT_CHACHA_POLY +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + gcry_mac_close(ctx->mac_hd); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + gpg_error_t err; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + /* Open cipher/mac handles. */ + err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + + ctx->initialized = true; + } + + err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN, + CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + return 0; +} + +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; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + size_t taglen = POLY1305_TAGLEN; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * payload encryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, encrypt length field */ + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_cipher_encrypt(ctx->header_hd, + (uint8_t *)&out_packet->length, + sizeof(uint32_t), + (uint8_t *)&in_packet->length, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, encrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_encrypt(ctx->main_hd, + out_packet->payload, + len - sizeof(uint32_t), + in_packet->payload, + len - sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 4, compute the MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_read(ctx->mac_hd, tag, &taglen); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_read failed: %s", + gpg_strerror(err)); + goto out; + } + +out: + explicit_bzero(poly_key, sizeof(poly_key)); +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + gpg_error_t err; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + err = gcry_cipher_decrypt(ctx->header_hd, + out, + sizeof(uint32_t), + in, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + 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) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * decryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, check MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet, + encrypted_size + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN); + if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + goto out; + } else if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_mac_verify failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, decrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_decrypt(ctx->main_hd, + out, + encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + encrypted_size); + if (err != 0) { + SSH_LOG(SSH_LOG_WARNING, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + ret = SSH_OK; + +out: + explicit_bzero(poly_key, sizeof(poly_key)); + return ret; +} +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + /* the table of supported ciphers */ static struct ssh_cipher_struct ssh_ciphertab[] = { #ifdef WITH_BLOWFISH_CIPHER @@ -683,8 +904,33 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = des3_decrypt }, { +#ifdef HAVE_GCRYPT_CHACHA_POLY + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +#else .name = "chacha20-poly1305@openssh.com" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt }, +#endif /* WITH_INSECURE_NONE */ { .name = NULL, .blocksize = 0, @@ -761,7 +1007,7 @@ ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, */ int ssh_crypto_init(void) { - size_t i; + UNUSED_VAR(size_t i); if (libgcrypt_initialized) { return SSH_OK; @@ -780,6 +1026,7 @@ int ssh_crypto_init(void) /* Re-enable warning */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); +#ifndef HAVE_GCRYPT_CHACHA_POLY for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); @@ -790,6 +1037,7 @@ int ssh_crypto_init(void) break; } } +#endif libgcrypt_initialized = 1; diff --git a/libssh/src/libmbedcrypto.c b/libssh/src/libmbedcrypto.c index a2e74d3..6d84bd5 100644 --- a/libssh/src/libmbedcrypto.c +++ b/libssh/src/libmbedcrypto.c @@ -27,6 +27,13 @@ #include "libssh/crypto.h" #include "libssh/priv.h" #include "libssh/misc.h" +#include "mbedcrypto-compat.h" +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#include +#include +#endif #ifdef HAVE_LIBMBEDCRYPTO #include @@ -35,7 +42,7 @@ #endif /* MBEDTLS_GCM_C */ static mbedtls_entropy_context ssh_mbedtls_entropy; -static mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; +extern mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; static int libmbedcrypto_initialized = 0; @@ -44,65 +51,6 @@ void ssh_reseed(void) mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); } -int ssh_get_random(void *where, int len, int strong) -{ - return ssh_mbedtls_random(where, len, strong); -} - -SHACTX sha1_init(void) -{ - SHACTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha1_update(SHACTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha1_final(unsigned char *md, SHACTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha1(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - static mbedtls_md_type_t nid_to_md_algo(int nid) { switch (nid) { @@ -116,7 +64,7 @@ static mbedtls_md_type_t nid_to_md_algo(int nid) return MBEDTLS_MD_NONE; } -void evp(int nid, unsigned char *digest, int len, +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); @@ -164,234 +112,29 @@ EVPCTX evp_init(int nid) return ctx; } -void evp_update(EVPCTX ctx, const void *data, unsigned long len) +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->md_info); + *mdlen = mbedtls_md_get_size(ctx->MBEDTLS_PRIVATE(md_info)); mbedtls_md_finish(ctx, md); mbedtls_md_free(ctx); SAFE_FREE(ctx); } -SHA256CTX sha256_init(void) -{ - SHA256CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if(ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha256_update(SHA256CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha256_final(unsigned char *md, SHA256CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha256(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA384CTX sha384_init(void) -{ - SHA384CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha384_update(SHA384CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha384_final(unsigned char *md, SHA384CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha384(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -SHA512CTX sha512_init(void) -{ - SHA512CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void sha512_update(SHA512CTX c, const void *data, unsigned long len) -{ - mbedtls_md_update(c, data, len); -} - -void sha512_final(unsigned char *md, SHA512CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - -void sha512(const unsigned char *digest, int len, unsigned char *hash) -{ - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); - } -} - -MD5CTX md5_init(void) -{ - MD5CTX ctx = NULL; - int rc; - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_MD5); - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - - -void md5_update(MD5CTX c, const void *data, unsigned long len) { - mbedtls_md_update(c, data, len); -} - -void md5_final(unsigned char *md, MD5CTX c) -{ - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, - int key_type, unsigned char *output, + uint8_t key_type, unsigned char *output, size_t requested_len) { return sshkdf_derive_key(crypto, key, key_len, key_type, output, requested_len); } -HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { HMACCTX ctx = NULL; const mbedtls_md_info_t *md_info = NULL; @@ -440,17 +183,21 @@ HMACCTX hmac_init(const void *key, int len, enum ssh_hmac_e type) return NULL; } -void hmac_update(HMACCTX c, const void *data, unsigned long len) +/* mbedtls returns 0 on success, but in this context + * success is 1 */ +int hmac_update(HMACCTX c, const void *data, size_t len) { - mbedtls_md_hmac_update(c, data, len); + return !mbedtls_md_hmac_update(c, data, len); } -void hmac_final(HMACCTX c, unsigned char *hashmacbuf, unsigned int *len) +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { - *len = mbedtls_md_get_size(c->md_info); - mbedtls_md_hmac_finish(c, hashmacbuf); + int rc; + *len = (unsigned int)mbedtls_md_get_size(c->md_info); + rc = !mbedtls_md_hmac_finish(c, hashmacbuf); mbedtls_md_free(c); SAFE_FREE(c); + return rc; } static int @@ -461,6 +208,8 @@ cipher_init(struct ssh_cipher_struct *cipher, { const mbedtls_cipher_info_t *cipher_info = NULL; mbedtls_cipher_context_t *ctx; + size_t key_bitlen = 0; + size_t iv_size = 0; int rc; if (operation == MBEDTLS_ENCRYPT) { @@ -481,15 +230,15 @@ cipher_init(struct ssh_cipher_struct *cipher, goto error; } - rc = mbedtls_cipher_setkey(ctx, key, - cipher_info->key_bitlen, - operation); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_cipher_setkey(ctx, key, key_bitlen, operation); if (rc != 0) { SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_setkey failed"); goto error; } - rc = mbedtls_cipher_set_iv(ctx, IV, cipher_info->iv_size); + iv_size = mbedtls_cipher_info_get_iv_size(cipher_info); + rc = mbedtls_cipher_set_iv(ctx, IV, iv_size); if (rc != 0) { SSH_LOG(SSH_LOG_WARNING, "mbedtls_cipher_set_iv failed"); goto error; @@ -567,15 +316,16 @@ cipher_set_key_gcm(struct ssh_cipher_struct *cipher, void *IV) { const mbedtls_cipher_info_t *cipher_info = NULL; + size_t key_bitlen = 0; int rc; mbedtls_gcm_init(&cipher->gcm_ctx); cipher_info = mbedtls_cipher_info_from_type(cipher->type); - rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, - MBEDTLS_CIPHER_ID_AES, - key, - cipher_info->key_bitlen); + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, MBEDTLS_CIPHER_ID_AES, + key, key_bitlen); + if (rc != 0) { SSH_LOG(SSH_LOG_WARNING, "mbedtls_gcm_setkey failed"); goto error; @@ -881,6 +631,326 @@ cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, } #endif /* MBEDTLS_GCM_C */ +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + mbedtls_chacha20_context main_ctx; + /* cipher handle used for encrypting the length field */ + mbedtls_chacha20_context header_ctx; + /* Poly1305 key */ + mbedtls_poly1305_context poly_ctx; +}; + +static void +chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + mbedtls_chacha20_free(&ctx->main_ctx); + mbedtls_chacha20_free(&ctx->header_ctx); + mbedtls_poly1305_free(&ctx->poly_ctx); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +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; + int ret = SSH_ERROR, rv; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + mbedtls_chacha20_init(&ctx->main_ctx); + mbedtls_chacha20_init(&ctx->header_ctx); + mbedtls_poly1305_init(&ctx->poly_ctx); + ctx->initialized = true; + } + + /* ChaCha20 keys initialization */ + /* K2 uses the first half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(main_ctx) failed"); + goto out; + } + + /* K1 uses the second half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_setkey(header_ctx) failed"); + goto out; + } + + ret = SSH_OK; +out: + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int +chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[12] = {0}; + int ret; + + /* The nonce in mbedTLS is 96 b long. The counter is passed through separate + * parameter of 32 b size. + * Encode the seqence number into the last 8 bytes. + */ + PUSH_BE_U64(seqbuf, 4, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(header_ctx) failed"); + return SSH_ERROR; + } + + ret = mbedtls_chacha20_starts(&ctx->main_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_starts(main_ctx) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +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]; + int ret = SSH_ERROR, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), + zero_block, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + + /* Set the Poly1305 key */ + rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_starts failed"); + goto out; + } + + ret = SSH_OK; +out: + explicit_bzero(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header context */ + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + 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) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + 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 = 0; + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ + rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + goto out; + } + + rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + out); + if (rv != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + goto out; + } + + ret = SSH_OK; +out: + 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) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int ret; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), + (unsigned char *)&in_packet->length, + (unsigned char *)&out_packet->length); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* step 3, encrypt packet payload (main_ctx counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), + in_packet->payload, out_packet->payload); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_chacha20_update failed"); + return; + } + + /* step 4, compute the MAC */ + ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_update failed"); + return; + } + ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (ret != 0) { + SSH_LOG(SSH_LOG_WARNING, "mbedtls_poly1305_finish failed"); + return; + } +} +#endif /* defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) */ + + static void cipher_cleanup(struct ssh_cipher_struct *cipher) { mbedtls_cipher_free(&cipher->encrypt_ctx); @@ -890,6 +960,17 @@ static void cipher_cleanup(struct ssh_cipher_struct *cipher) #endif /* MBEDTLS_GCM_C */ } +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + static struct ssh_cipher_struct ssh_ciphertab[] = { #ifdef WITH_BLOWFISH_CIPHER { @@ -1012,8 +1093,33 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .cleanup = cipher_cleanup }, { +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .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" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, }, +#endif /* WITH_INSECURE_NONE */ { .name = NULL, .blocksize = 0, @@ -1033,7 +1139,7 @@ struct ssh_cipher_struct *ssh_get_ciphertab(void) int ssh_crypto_init(void) { - size_t i; + UNUSED_VAR(size_t i); int rc; if (libmbedcrypto_initialized) { @@ -1049,6 +1155,7 @@ int ssh_crypto_init(void) mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); } +#if !(defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C)) for (i = 0; ssh_ciphertab[i].name != NULL; i++) { int cmp; @@ -1060,28 +1167,13 @@ int ssh_crypto_init(void) break; } } +#endif libmbedcrypto_initialized = 1; return SSH_OK; } -int ssh_mbedtls_random(void *where, int len, int strong) -{ - int rc = 0; - if (strong) { - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_ON); - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, - MBEDTLS_CTR_DRBG_PR_OFF); - } else { - rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); - } - - return !rc; -} - mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void) { return &ssh_mbedtls_ctr_drbg; diff --git a/libssh/src/libssh.map b/libssh/src/libssh.map index c9bedee..eeb625c 100644 --- a/libssh/src/libssh.map +++ b/libssh/src/libssh.map @@ -1,4 +1,4 @@ -# This map file was updated with abimap-0.3.1 +# This map file was updated with abimap-0.3.2 LIBSSH_4_5_0 # Released { @@ -447,3 +447,15 @@ LIBSSH_4_8_1 # Released ssh_session_get_known_hosts_entry; ssh_threads_get_default; } LIBSSH_4_8_0; + +LIBSSH_4_9_0 # Released +{ + global: + ssh_channel_open_forward_port; + ssh_key_dup; + ssh_send_issue_banner; + ssh_session_set_disconnect_message; + ssh_userauth_publickey_auto_get_current_identity; + ssh_vlog; +} LIBSSH_4_8_1; + diff --git a/libssh/src/log.c b/libssh/src/log.c index a8664b1..8ce1e71 100644 --- a/libssh/src/log.c +++ b/libssh/src/log.c @@ -38,6 +38,10 @@ #include "libssh/misc.h" #include "libssh/session.h" +#ifndef LOG_SIZE +#define LOG_SIZE 1024 +#endif + static LIBSSH_THREAD int ssh_log_level; static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; static LIBSSH_THREAD void *ssh_log_userdata; @@ -94,38 +98,52 @@ static void ssh_log_stderr(int verbosity, fprintf(stderr, " %s\n", buffer); } +static void ssh_log_custom(ssh_logging_callback log_fn, + int verbosity, + const char *function, + const char *buffer) +{ + char buf[LOG_SIZE + 64]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + log_fn(verbosity, function, buf, ssh_get_log_userdata()); +} + void ssh_log_function(int verbosity, const char *function, const char *buffer) { ssh_logging_callback log_fn = ssh_get_log_callback(); - if (log_fn) { - char buf[1024]; - snprintf(buf, sizeof(buf), "%s: %s", function, buffer); - - log_fn(verbosity, - function, - buf, - ssh_get_log_userdata()); + if (log_fn) { + ssh_log_custom(log_fn, verbosity, function, buffer); return; } ssh_log_stderr(verbosity, function, buffer); } +void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) +{ + char buffer[LOG_SIZE]; + + vsnprintf(buffer, sizeof(buffer), format, *va); + ssh_log_function(verbosity, function, buffer); +} + void _ssh_log(int verbosity, const char *function, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= ssh_get_log_level()) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, function, format, &va); va_end(va); - ssh_log_function(verbosity, function, buffer); } } @@ -135,14 +153,12 @@ void ssh_log(ssh_session session, int verbosity, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= session->common.log_verbosity) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, "", format, &va); va_end(va); - ssh_log_function(verbosity, "", buffer); } } @@ -157,14 +173,12 @@ void ssh_log_common(struct ssh_common_struct *common, const char *function, const char *format, ...) { - char buffer[1024]; va_list va; if (verbosity <= common->log_verbosity) { va_start(va, format); - vsnprintf(buffer, sizeof(buffer), format, va); + ssh_vlog(verbosity, function, format, &va); va_end(va); - ssh_log_function(verbosity, function, buffer); } } diff --git a/libssh/src/match.c b/libssh/src/match.c index 1a60d73..3e58f73 100644 --- a/libssh/src/match.c +++ b/libssh/src/match.c @@ -43,7 +43,7 @@ #include "libssh/priv.h" -#define MAX_MATCH_RECURSION 32 +#define MAX_MATCH_RECURSION 16 /* * Returns true if the given string matches the pattern (which may contain ? @@ -51,74 +51,77 @@ */ static int match_pattern(const char *s, const char *pattern, size_t limit) { - bool had_asterisk = false; - if (s == NULL || pattern == NULL || limit <= 0) { - return 0; - } + bool had_asterisk = false; - for (;;) { - /* If at end of pattern, accept if also at end of string. */ - if (*pattern == '\0') { - return (*s == '\0'); + if (s == NULL || pattern == NULL || limit <= 0) { + return 0; } - while (*pattern == '*') { - /* Skip the asterisk. */ - had_asterisk = true; - pattern++; - } + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (*pattern == '\0') { + return (*s == '\0'); + } - if (had_asterisk) { - /* If at end of pattern, accept immediately. */ - if (!*pattern) - return 1; + /* Skip all the asterisks and adjacent question marks */ + while (*pattern == '*' || (had_asterisk && *pattern == '?')) { + if (*pattern == '*') { + had_asterisk = true; + } + pattern++; + } - /* If next character in pattern is known, optimize. */ - if (*pattern != '?') { + if (had_asterisk) { + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?') { + /* + * Look instances of the next character in + * pattern, and try to match starting from + * those. + */ + for (; *s; s++) + if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { + return 1; + } + /* Failed. */ + return 0; + } + /* + * Move ahead one character at a time and try to + * match at each position. + */ + for (; *s; s++) { + if (match_pattern(s, pattern, limit - 1)) { + return 1; + } + } + /* Failed. */ + return 0; + } /* - * Look instances of the next character in - * pattern, and try to match starting from - * those. + * There must be at least one more character in the string. + * If we are at the end, fail. */ - for (; *s; s++) - if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { - return 1; - } - /* Failed. */ - return 0; - } - /* - * Move ahead one character at a time and try to - * match at each position. - */ - for (; *s; s++) { - if (match_pattern(s, pattern, limit - 1)) { - return 1; + if (!*s) { + return 0; } - } - /* Failed. */ - return 0; - } - /* - * There must be at least one more character in the string. - * If we are at the end, fail. - */ - if (!*s) { - return 0; - } - /* Check if the next character of the string is acceptable. */ - if (*pattern != '?' && *pattern != *s) { - return 0; - } + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && *pattern != *s) { + return 0; + } - /* Move to the next character, both in string and in pattern. */ - s++; - pattern++; - } + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } - /* NOTREACHED */ - return 0; + /* NOTREACHED */ + return 0; } /* @@ -128,11 +131,11 @@ static int match_pattern(const char *s, const char *pattern, size_t limit) * no match at all. */ int match_pattern_list(const char *string, const char *pattern, - unsigned int len, int dolower) { + size_t len, int dolower) { char sub[1024]; int negated; int got_positive; - unsigned int i, subi; + size_t i, subi; got_positive = 0; for (i = 0; i < len;) { diff --git a/libssh/src/mbedcrypto-compat.h b/libssh/src/mbedcrypto-compat.h new file mode 100644 index 0000000..705294a --- /dev/null +++ b/libssh/src/mbedcrypto-compat.h @@ -0,0 +1,39 @@ +#ifndef MBEDCRYPTO_COMPAT_H +#define MBEDCRYPTO_COMPAT_H + +/* mbedtls/version.h should be available for both v2 and v3 + * v3 defines the version inside build_info.h so if it isn't defined + * in version.h we should have v3 + */ +#include +#include +#ifdef MBEDTLS_VERSION_MAJOR +#if MBEDTLS_VERSION_MAJOR < 3 + +static inline size_t mbedtls_cipher_info_get_key_bitlen( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return info->key_bitlen; +} + +static inline size_t mbedtls_cipher_info_get_iv_size( + const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return (size_t)info->iv_size; +} + +#define MBEDTLS_PRIVATE(X) X +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#else /* MBEDTLS_VERSION_MAJOR */ +#include +#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/libssh/src/md_crypto.c b/libssh/src/md_crypto.c new file mode 100644 index 0000000..f5104f0 --- /dev/null +++ b/libssh/src/md_crypto.c @@ -0,0 +1,225 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libcrypto-compat.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include +#include +#include + +SHACTX +sha1_init(void) +{ + int rc; + SHACTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha1_update(SHACTX c, const void *data, size_t len) +{ + EVP_DigestUpdate(c, data, len); +} + +void +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned int mdlen = 0; + + EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_free(c); +} + +void +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); + } +} + +SHA256CTX +sha256_init(void) +{ + int rc; + SHA256CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + EVP_DigestUpdate(c, data, len); +} + +void +sha256_final(unsigned char *md, SHA256CTX c) +{ + unsigned int mdlen = 0; + + EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_free(c); +} + +void +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA256CTX c = sha256_init(); + if (c != NULL) { + sha256_update(c, digest, len); + sha256_final(hash, c); + } +} + +SHA384CTX +sha384_init(void) +{ + int rc; + SHA384CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + EVP_DigestUpdate(c, data, len); +} + +void +sha384_final(unsigned char *md, SHA384CTX c) +{ + unsigned int mdlen = 0; + + EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_free(c); +} + +void +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA384CTX c = sha384_init(); + if (c != NULL) { + sha384_update(c, digest, len); + sha384_final(hash, c); + } +} + +SHA512CTX +sha512_init(void) +{ + int rc = 0; + SHA512CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + EVP_DigestUpdate(c, data, len); +} + +void +sha512_final(unsigned char *md, SHA512CTX c) +{ + unsigned int mdlen = 0; + + EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_free(c); +} + +void +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA512CTX c = sha512_init(); + if (c != NULL) { + sha512_update(c, digest, len); + sha512_final(hash, c); + } +} + +MD5CTX +md5_init(void) +{ + int rc; + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +md5_update(MD5CTX c, const void *data, size_t len) +{ + EVP_DigestUpdate(c, data, len); +} + +void +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned int mdlen = 0; + + EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_free(c); +} diff --git a/libssh/src/md_gcrypt.c b/libssh/src/md_gcrypt.c new file mode 100644 index 0000000..1f0a71f --- /dev/null +++ b/libssh/src/md_gcrypt.c @@ -0,0 +1,167 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +void +sha1_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); +} + +void +sha1_final(unsigned char *md, SHACTX c) +{ + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); + gcry_md_close(c); +} + +void +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA256, 0); + + return ctx; +} + +void +sha256_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); +} + +void +sha256_final(unsigned char *md, SHACTX c) +{ + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); + gcry_md_close(c); +} + +void +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA384, 0); + + return ctx; +} + +void +sha384_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); +} + +void +sha384_final(unsigned char *md, SHACTX c) +{ + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); + gcry_md_close(c); +} + +void +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA512, 0); + + return ctx; +} + +void +sha512_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); +} + +void +sha512_final(unsigned char *md, SHACTX c) +{ + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); + gcry_md_close(c); +} + +void +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); +} + +MD5CTX +md5_init(void) +{ + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void +md5_update(MD5CTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); +} + +void +md5_final(unsigned char *md, MD5CTX c) +{ + gcry_md_final(c); + memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); + gcry_md_close(c); +} diff --git a/libssh/src/md_mbedcrypto.c b/libssh/src/md_mbedcrypto.c new file mode 100644 index 0000000..227e20a --- /dev/null +++ b/libssh/src/md_mbedcrypto.c @@ -0,0 +1,308 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "mbedcrypto-compat.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha1_update(SHACTX c, const void *data, size_t len) +{ + mbedtls_md_update(c, data, len); +} + +void +sha1_final(unsigned char *md, SHACTX c) +{ + mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); +} + +void +sha1(const unsigned char *digest, 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); + } +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + mbedtls_md_update(c, data, len); +} + +void +sha256_final(unsigned char *md, SHA256CTX c) +{ + mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); +} + +void +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (md_info != NULL) { + mbedtls_md(md_info, digest, len, hash); + } +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + mbedtls_md_update(c, data, len); +} + +void +sha384_final(unsigned char *md, SHA384CTX c) +{ + mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); +} + +void +sha384(const unsigned char *digest, 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); + } +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + mbedtls_md_update(c, data, len); +} + +void +sha512_final(unsigned char *md, SHA512CTX c) +{ + mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); +} + +void +sha512(const unsigned char *digest, 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); + } +} + +MD5CTX +md5_init(void) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +md5_update(MD5CTX c, const void *data, size_t len) +{ + mbedtls_md_update(c, data, len); +} + +void +md5_final(unsigned char *md, MD5CTX c) +{ + mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); +} diff --git a/libssh/src/messages.c b/libssh/src/messages.c index a772d48..ff199f9 100644 --- a/libssh/src/messages.c +++ b/libssh/src/messages.c @@ -54,7 +54,7 @@ * This file contains the message parsing utilities for client and server * programs using libssh. * - * On the server the the main loop of the program will call + * On the server the main loop of the program will call * ssh_message_get(session) to get messages as they come. They are not 1-1 with * the protocol messages. Then, the user will know what kind of a message it is * and use the appropriate functions to handle it (or use the default handlers @@ -79,7 +79,7 @@ static ssh_message ssh_message_new(ssh_session session) #ifndef WITH_SERVER -/* Reduced version of the reply default that only reply with +/* Reduced version of the reply default that only replies with * SSH_MSG_UNIMPLEMENTED */ static int ssh_message_reply_default(ssh_message msg) { @@ -593,6 +593,7 @@ void ssh_message_free(ssh_message msg){ switch(msg->type) { case SSH_REQUEST_AUTH: SAFE_FREE(msg->auth_request.username); + SAFE_FREE(msg->auth_request.sigtype); if (msg->auth_request.password) { explicit_bzero(msg->auth_request.password, strlen(msg->auth_request.password)); @@ -852,6 +853,14 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_request){ goto error; } msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + msg->auth_request.sigtype = strdup(ssh_string_get_char(algo)); + if (msg->auth_request.sigtype == NULL) { + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + // has a valid signature ? if(has_sign) { ssh_string sig_blob = NULL; @@ -1160,7 +1169,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); goto error; } - + if (strcmp(type_c,"session") == 0) { msg->channel_request_open.type = SSH_CHANNEL_SESSION; SAFE_FREE(type_c); @@ -1239,7 +1248,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ * * @param[in] chan The channel the request is made on. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan) { ssh_session session; @@ -1330,7 +1339,7 @@ ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) { * * @param[in] want_reply The want_reply field from the request. * - * @returns SSH_OK on success, SSH_ERROR if an error occured. + * @returns SSH_OK on success, SSH_ERROR if an error occurred. */ int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, const char *request, uint8_t want_reply) { diff --git a/libssh/src/misc.c b/libssh/src/misc.c index 0f1a7d4..3f2652d 100644 --- a/libssh/src/misc.c +++ b/libssh/src/misc.c @@ -104,8 +104,9 @@ */ #ifdef _WIN32 -char *ssh_get_user_home_dir(void) { - char tmp[MAX_PATH] = {0}; +char *ssh_get_user_home_dir(void) +{ + char tmp[PATH_MAX] = {0}; char *szPath = NULL; if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { @@ -122,12 +123,13 @@ char *ssh_get_user_home_dir(void) { } /* we have read access on file */ -int ssh_file_readaccess_ok(const char *file) { - if (_access(file, 4) < 0) { - return 0; - } +int ssh_file_readaccess_ok(const char *file) +{ + if (_access(file, 4) < 0) { + return 0; + } - return 1; + return 1; } /** @@ -158,7 +160,8 @@ int ssh_dir_writeable(const char *path) #define SSH_USEC_IN_SEC 1000000LL #define SSH_SECONDS_SINCE_1601 11644473600LL -int gettimeofday(struct timeval *__p, void *__t) { +int ssh_gettimeofday(struct timeval *__p, void *__t) +{ union { unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ FILETIME ft; @@ -171,7 +174,8 @@ int gettimeofday(struct timeval *__p, void *__t) { return (0); } -char *ssh_get_local_username(void) { +char *ssh_get_local_username(void) +{ DWORD size = 0; char *user; @@ -190,7 +194,8 @@ char *ssh_get_local_username(void) { return NULL; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ struct sockaddr_storage ss; int sslen = sizeof(ss); int rc = SOCKET_ERROR; @@ -212,7 +217,8 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = SOCKET_ERROR; if (strchr(str, ':')) { @@ -319,7 +325,8 @@ char *ssh_get_local_username(void) return name; } -int ssh_is_ipaddr_v4(const char *str) { +int ssh_is_ipaddr_v4(const char *str) +{ int rc = -1; struct in_addr dest; @@ -331,7 +338,8 @@ int ssh_is_ipaddr_v4(const char *str) { return 0; } -int ssh_is_ipaddr(const char *str) { +int ssh_is_ipaddr(const char *str) +{ int rc = -1; if (strchr(str, ':')) { @@ -349,7 +357,8 @@ int ssh_is_ipaddr(const char *str) { #endif /* _WIN32 */ -char *ssh_lowercase(const char* str) { +char *ssh_lowercase(const char* str) +{ char *new, *p; if (str == NULL) { @@ -392,15 +401,17 @@ char *ssh_hostport(const char *host, int port) * @brief Convert a buffer into a colon separated hex string. * The caller has to free the memory. * - * @param what What should be converted to a hex string. + * @param[in] what What should be converted to a hex string. * - * @param len Length of the buffer to convert. + * @param[in] len Length of the buffer to convert. * - * @return The hex string or NULL on error. + * @return The hex string or NULL on error. The memory needs + * to be freed using ssh_string_free_char(). * * @see ssh_string_free_char() */ -char *ssh_get_hexa(const unsigned char *what, size_t len) { +char *ssh_get_hexa(const unsigned char *what, size_t len) +{ const char h[] = "0123456789abcdef"; char *hexa; size_t i; @@ -428,7 +439,8 @@ char *ssh_get_hexa(const unsigned char *what, size_t len) { /** * @deprecated Please use ssh_print_hash() instead */ -void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) +{ char *hexa = ssh_get_hexa(what, len); if (hexa == NULL) { @@ -455,7 +467,7 @@ void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { * " 00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................" * * The value for each byte as corresponding ASCII character is printed at the - * end if the value is printable. Otherwise it is replace with '.'. + * end if the value is printable. Otherwise, it is replaced with '.'. * * @param[in] descr A description for the content to be logged * @param[in] what The buffer to be logged @@ -649,48 +661,54 @@ void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) * } * @endcode */ -const char *ssh_version(int req_version) { - if (req_version <= LIBSSH_VERSION_INT) { - return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING MBED_STRING - ZLIB_STRING; - } +const char *ssh_version(int req_version) +{ + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + MBED_STRING ZLIB_STRING; + } - return NULL; + return NULL; } -struct ssh_list *ssh_list_new(void) { - struct ssh_list *ret=malloc(sizeof(struct ssh_list)); - if(!ret) - return NULL; - ret->root=ret->end=NULL; - return ret; +struct ssh_list *ssh_list_new(void) +{ + struct ssh_list *ret = malloc(sizeof(struct ssh_list)); + if (!ret) + return NULL; + ret->root = ret->end = NULL; + return ret; } -void ssh_list_free(struct ssh_list *list){ - struct ssh_iterator *ptr,*next; - if(!list) - return; - ptr=list->root; - while(ptr){ - next=ptr->next; - SAFE_FREE(ptr); - ptr=next; - } - SAFE_FREE(list); +void ssh_list_free(struct ssh_list *list) +{ + struct ssh_iterator *ptr, *next; + if (!list) + return; + ptr = list->root; + while (ptr) { + next = ptr->next; + SAFE_FREE(ptr); + ptr = next; + } + SAFE_FREE(list); } -struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list){ - if(!list) - return NULL; - return list->root; +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) +{ + if (!list) + return NULL; + return list->root; } -struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ - struct ssh_iterator *it; - for(it = ssh_list_get_iterator(list); it != NULL ;it=it->next) - if(it->data==value) - return it; - return NULL; +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) +{ + struct ssh_iterator *it; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) + if (it->data == value) + return it; + return NULL; } /** @@ -703,7 +721,7 @@ struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value){ size_t ssh_list_count(const struct ssh_list *list) { struct ssh_iterator *it = NULL; - int count = 0; + size_t count = 0; for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { count++; @@ -712,16 +730,19 @@ size_t ssh_list_count(const struct ssh_list *list) return count; } -static struct ssh_iterator *ssh_iterator_new(const void *data){ - struct ssh_iterator *iterator=malloc(sizeof(struct ssh_iterator)); - if(!iterator) - return NULL; - iterator->next=NULL; - iterator->data=data; - return iterator; +static struct ssh_iterator *ssh_iterator_new(const void *data) +{ + struct ssh_iterator *iterator = malloc(sizeof(struct ssh_iterator)); + + if (!iterator) + return NULL; + iterator->next = NULL; + iterator->data = data; + return iterator; } -int ssh_list_append(struct ssh_list *list,const void *data){ +int ssh_list_append(struct ssh_list *list,const void *data) +{ struct ssh_iterator *iterator = NULL; if (list == NULL) { @@ -744,7 +765,8 @@ int ssh_list_append(struct ssh_list *list,const void *data){ return SSH_OK; } -int ssh_list_prepend(struct ssh_list *list, const void *data){ +int ssh_list_prepend(struct ssh_list *list, const void *data) +{ struct ssh_iterator *it = NULL; if (list == NULL) { @@ -768,8 +790,9 @@ int ssh_list_prepend(struct ssh_list *list, const void *data){ return SSH_OK; } -void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ - struct ssh_iterator *ptr,*prev; +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) +{ + struct ssh_iterator *ptr, *prev; if (list == NULL) { return; @@ -808,7 +831,8 @@ void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator){ * @returns A pointer to the element being stored in head, or NULL * if the list is empty. */ -const void *_ssh_list_pop_head(struct ssh_list *list){ +const void *_ssh_list_pop_head(struct ssh_list *list) +{ struct ssh_iterator *iterator = NULL; const void *data = NULL; @@ -834,17 +858,21 @@ const void *_ssh_list_pop_head(struct ssh_list *list){ * dirname breaks a null-terminated pathname string into a directory component. * In the usual case, ssh_dirname() returns the string up to, but not including, * the final '/'. Trailing '/' characters are not counted as part of the - * pathname. The caller must free the memory. + * pathname. The caller must free the memory using ssh_string_free_char(). * * @param[in] path The path to parse. * * @return The dirname of path or NULL if we can't allocate memory. * If path does not contain a slash, c_dirname() returns - * the string ".". If path is the string "/", it returns + * the string ".". If path is a string "/", it returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The memory needs to be freed using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_dirname (const char *path) { +char *ssh_dirname (const char *path) +{ char *new = NULL; size_t len; @@ -895,11 +923,15 @@ char *ssh_dirname (const char *path) { * @param[in] path The path to parse. * * @return The filename of path or NULL if we can't allocate - * memory. If path is a the string "/", basename returns + * memory. If path is the string "/", basename returns * the string "/". If path is NULL or an empty string, - * "." is returned. + * "." is returned. The caller needs to free this memory + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_basename (const char *path) { +char *ssh_basename (const char *path) +{ char *new = NULL; const char *s; size_t len; @@ -1032,9 +1064,13 @@ int ssh_mkdirs(const char *pathname, mode_t mode) * * @param[in] d The directory to expand. * - * @return The expanded directory, NULL on error. + * @return The expanded directory, NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_tilde(const char *d) { +char *ssh_path_expand_tilde(const char *d) +{ char *h = NULL, *r; const char *p; size_t ld; @@ -1101,12 +1137,17 @@ char *ssh_path_expand_tilde(const char *d) { * %l local hostname * %r remote username * %p remote port - * @returns Expanded string. + * @returns Expanded string. The caller needs to free the memory using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() */ -char *ssh_path_expand_escape(ssh_session session, const char *s) { - char host[NI_MAXHOST]; - char buf[MAX_BUF_SIZE]; - char *r, *x = NULL; +char *ssh_path_expand_escape(ssh_session session, const char *s) +{ + char host[NI_MAXHOST] = {0}; + char *buf = NULL; + char *r = NULL; + char *x = NULL; const char *p; size_t i, l; @@ -1122,6 +1163,13 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { return NULL; } + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + p = r; buf[0] = '\0'; @@ -1131,6 +1179,7 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { buf[i] = *p; i++; if (i >= MAX_BUF_SIZE) { + free(buf); free(r); return NULL; } @@ -1147,7 +1196,15 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { case '%': goto escape; case 'd': - x = strdup(session->opts.sshdir); + if (session->opts.sshdir) { + x = strdup(session->opts.sshdir); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand sshdir"); + free(buf); + free(r); + return NULL; + } break; case 'u': x = ssh_get_local_username(); @@ -1158,31 +1215,48 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { } break; case 'h': - x = strdup(session->opts.host); + if (session->opts.host) { + x = strdup(session->opts.host); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand host"); + free(buf); + free(r); + return NULL; + } break; case 'r': - x = strdup(session->opts.username); + if (session->opts.username) { + x = strdup(session->opts.username); + } else { + ssh_set_error(session, SSH_FATAL, + "Cannot expand username"); + free(buf); + free(r); + return NULL; + } break; case 'p': - if (session->opts.port < 65536) { - char tmp[6]; - - snprintf(tmp, - sizeof(tmp), - "%u", - session->opts.port > 0 ? session->opts.port : 22); - x = strdup(tmp); + { + char tmp[6]; + + snprintf(tmp, sizeof(tmp), "%hu", + (uint16_t)(session->opts.port > 0 ? session->opts.port + : 22)); + x = strdup(tmp); } break; default: ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); free(r); return NULL; } if (x == NULL) { ssh_set_error_oom(session); + free(buf); free(r); return NULL; } @@ -1191,19 +1265,26 @@ char *ssh_path_expand_escape(ssh_session session, const char *s) { if (i >= MAX_BUF_SIZE) { ssh_set_error(session, SSH_FATAL, "String too long"); + free(buf); free(x); free(r); return NULL; } l = strlen(buf); - strncpy(buf + l, x, sizeof(buf) - l - 1); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); buf[i] = '\0'; SAFE_FREE(x); } free(r); - return strdup(buf); -#undef MAX_BUF_SIZE + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + } + return x; } /** @@ -1279,28 +1360,33 @@ int ssh_analyze_banner(ssh_session session, int server) * 012345678901234567890 */ if (strlen(openssh) > 9) { + errno = 0; major = strtoul(openssh + 8, &tmp, 10); if ((tmp == (openssh + 8)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || ((major < 1) || (major > 100))) { /* invalid major */ + errno = 0; goto done; } + errno = 0; minor = strtoul(openssh + 10, &tmp, 10); if ((tmp == (openssh + 10)) || ((errno == ERANGE) && (major == ULONG_MAX)) || ((errno != 0) && (major == 0)) || (minor > 100)) { /* invalid minor */ + errno = 0; goto done; } session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); SSH_LOG(SSH_LOG_PROTOCOL, - "We are talking to an OpenSSH client version: %lu.%lu (%x)", + "We are talking to an OpenSSH %s version: %lu.%lu (%x)", + server ? "client" : "server", major, minor, session->openssh); } } @@ -1321,7 +1407,8 @@ int ssh_analyze_banner(ssh_session session, int server) * @brief initializes a timestamp to the current time * @param[out] ts pointer to an allocated ssh_timestamp structure */ -void ssh_timestamp_init(struct ssh_timestamp *ts){ +void ssh_timestamp_init(struct ssh_timestamp *ts) +{ #ifdef HAVE_CLOCK_GETTIME struct timespec tp; clock_gettime(CLOCK, &tp); @@ -1344,17 +1431,18 @@ void ssh_timestamp_init(struct ssh_timestamp *ts){ * @returns difference in milliseconds */ -static int ssh_timestamp_difference(struct ssh_timestamp *old, - struct ssh_timestamp *new){ - long seconds, usecs, msecs; - seconds = new->seconds - old->seconds; - usecs = new->useconds - old->useconds; - if (usecs < 0){ - seconds--; - usecs += 1000000; - } - msecs = seconds * 1000 + usecs/1000; - return msecs; +static int +ssh_timestamp_difference(struct ssh_timestamp *old, struct ssh_timestamp *new) +{ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; } /** @@ -1365,14 +1453,20 @@ static int ssh_timestamp_difference(struct ssh_timestamp *old, * @param[in] usec number of microseconds * @returns milliseconds, or 10000 if user supplied values are equal to zero */ -int ssh_make_milliseconds(long sec, long usec) { - int res = usec ? (usec / 1000) : 0; +int ssh_make_milliseconds(unsigned long sec, unsigned long usec) +{ + unsigned long res = usec ? (usec / 1000) : 0; res += (sec * 1000); if (res == 0) { res = 10 * 1000; /* use a reasonable default value in case * SSH_OPTIONS_TIMEOUT is not set in options. */ } - return res; + + if (res > INT_MAX) { + return SSH_TIMEOUT_INFINITE; + } else { + return (int)res; + } } /** @@ -1385,7 +1479,8 @@ int ssh_make_milliseconds(long sec, long usec) { * @returns 1 if timeout is elapsed * 0 otherwise */ -int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; switch(timeout) { @@ -1417,7 +1512,8 @@ int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) { * timeout * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. */ -int ssh_timeout_update(struct ssh_timestamp *ts, int timeout){ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) +{ struct ssh_timestamp now; int ms, ret; if (timeout <= 0) { @@ -1610,13 +1706,13 @@ int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) *dst++ = '\\'; break; case SINGLE_QUOTE: - /* Close the current quoted string and replace '!' for unquoted + /* Close the currently quoted string and replace '!' for unquoted * "\!" */ *dst++ = '\''; *dst++ = '\\'; break; case DOUBLE_QUOTE: - /* Close current quoted string and replace "!" for unquoted + /* Close currently quoted string and replace "!" for unquoted * "\!" */ *dst++ = '"'; *dst++ = '\\'; @@ -1735,4 +1831,147 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) return out - buf; } +/** + * @internal + * + * @brief Replaces the last 6 characters of a string from 'X' to 6 random hexdigits. + * + * @param[in,out] template Any input string with last 6 characters as 'X'. + * @returns -1 as error when the last 6 characters of the input to be replaced are not 'X' + * 0 otherwise. + */ +int ssh_tmpname(char *template) +{ + char *tmp = NULL; + size_t i = 0; + int rc = 0; + uint8_t random[6]; + + if (template == NULL) { + goto err; + } + + tmp = template + strlen(template) - 6; + if (tmp < template) { + goto err; + } + + for (i = 0; i < 6; i++) { + if (tmp[i] != 'X') { + SSH_LOG(SSH_LOG_WARNING, + "Invalid input. Last six characters of the input must be \'X\'"); + goto err; + } + } + + rc = ssh_get_random(random, 6, 0); + if (!rc) { + SSH_LOG(SSH_LOG_WARNING, + "Could not generate random data\n"); + goto err; + } + + for (i = 0; i < 6; i++) { + /* Limit the random[i] < 32 */ + random[i] &= 0x1f; + /* For values from 0 to 9 use numbers, otherwise use letters */ + tmp[i] = random[i] > 9 ? random[i] + 'a' - 10 : random[i] + '0'; + } + + return 0; + +err: + errno = EINVAL; + return -1; +} + +/** + * @internal + * + * @brief Finds the first occurrence of a pattern in a string and replaces it. + * + * @param[in] src Source string containing the pattern to be replaced. + * @param[in] pattern Pattern to be replaced in the source string. + * Note: this function replaces the first occurrence of + * pattern only. + * @param[in] replace String to be replaced is stored in replace. + * + * @returns src_replaced a pointer that points to the replaced string. + * NULL if allocation fails or if src is NULL. The returned memory needs to be + * freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_strreplace(const char *src, const char *pattern, const char *replace) +{ + char *p = NULL; + char *src_replaced = NULL; + + if (src == NULL) { + return NULL; + } + + if (pattern == NULL || replace == NULL) { + return strdup(src); + } + + p = strstr(src, pattern); + + if (p != NULL) { + size_t offset = p - src; + size_t pattern_len = strlen(pattern); + size_t replace_len = strlen(replace); + size_t len = strlen(src); + size_t len_replaced = len + replace_len - pattern_len + 1; + + src_replaced = (char *)malloc(len_replaced); + + if (src_replaced == NULL) { + return NULL; + } + + memset(src_replaced, 0, len_replaced); + memcpy(src_replaced, src, offset); + memcpy(src_replaced + offset, replace, replace_len); + memcpy(src_replaced + offset + replace_len, src + offset + pattern_len, len - offset - pattern_len); + return src_replaced; /* free in the caller */ + } else { + return strdup(src); + } +} + +/** + * @internal + * + * @brief Processes errno into error string + * + * @param[in] err_num The errno value + * @param[out] buf Pointer to a place where the string could be saved + * @param[in] buflen The allocated size of buf + * + * @return error string + */ +char *ssh_strerror(int err_num, char *buf, size_t buflen) +{ +#if defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) + /* GNU extension on Linux */ + return strerror_r(err_num, buf, buflen); +#else + int rv; + +#if defined(_WIN32) + rv = strerror_s(buf, buflen, err_num); +#else + /* POSIX version available for example on FreeBSD or in musl libc */ + rv = strerror_r(err_num, buf, buflen); +#endif /* _WIN32 */ + + /* make sure the buffer is initialized and terminated with NULL */ + if (-rv == ERANGE) { + buf[0] = '\0'; + } + return buf; +#endif /* defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) */ +} + /** @} */ diff --git a/libssh/src/options.c b/libssh/src/options.c index b5f951a..49aaefa 100644 --- a/libssh/src/options.c +++ b/libssh/src/options.c @@ -32,6 +32,7 @@ #include #endif #include +#include "libssh/pki_priv.h" #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/misc.h" @@ -56,11 +57,12 @@ * @param src The session to use to copy the options. * * @param dest A pointer to store the allocated session with duplicated - * options. You have to free the memory. + * options. You have to free the memory using ssh_free() * - * @returns 0 on sucess, -1 on error with errno set. + * @returns 0 on success, -1 on error with errno set. * * @see ssh_session_connect() + * @see ssh_free() */ int ssh_options_copy(ssh_session src, ssh_session *dest) { @@ -263,9 +265,10 @@ int ssh_options_set_algo(ssh_session session, * The file descriptor to use (socket_t).\n * \n * If you wish to open the socket yourself for a reason - * or another, set the file descriptor. Don't forget to - * set the hostname as the hostname is used as a key in - * the known_host mechanism. + * or another, set the file descriptor and take care of closing + * it (this is new behavior in libssh 0.10). + * Don't forget to set the hostname as the hostname is used + * as a key in the known_host mechanism. * * - SSH_OPTIONS_BINDADDR: * The address to bind the client to (const char *). @@ -340,7 +343,7 @@ int ssh_options_set_algo(ssh_session session, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, packet level * - SSH_LOG_FUNCTIONS: Every function path * * - SSH_OPTIONS_LOG_VERBOSITY_STR: @@ -465,6 +468,20 @@ int ssh_options_set_algo(ssh_session session, * in seconds. RFC 4253 Section 9 recommends one hour. * (uint32_t, 0=off) * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 768 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int *) + + * - SSH_OPTIONS_IDENTITY_AGENT + * Set the path to the SSH agent socket. If unset, the + * SSH_AUTH_SOCK environment is consulted. + * (const char *) + * * @param value The value to set. This is a generic pointer and the * datatype which is used should be set according to the * type set. @@ -472,7 +489,8 @@ int ssh_options_set_algo(ssh_session session, * @return 0 on success, < 0 on error. */ int ssh_options_set(ssh_session session, enum ssh_options_e type, - const void *value) { + const void *value) +{ const char *v; char *p, *q; long int i; @@ -495,7 +513,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } - p = strchr(q, '@'); + p = strrchr(q, '@'); SAFE_FREE(session->opts.host); @@ -844,10 +862,10 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } else { if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib") < 0) + if(ssh_options_set_algo(session,SSH_COMP_C_S,"zlib@openssh.com,zlib,none") < 0) return -1; } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_C_S,"none") < 0) + if(ssh_options_set_algo(session,SSH_COMP_C_S,"none,zlib@openssh.com,zlib") < 0) return -1; } else { if (ssh_options_set_algo(session, SSH_COMP_C_S, v) < 0) @@ -862,10 +880,10 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } else { if (strcasecmp(value,"yes")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib") < 0) + if(ssh_options_set_algo(session,SSH_COMP_S_C,"zlib@openssh.com,zlib,none") < 0) return -1; } else if (strcasecmp(value,"no")==0){ - if(ssh_options_set_algo(session,SSH_COMP_S_C,"none") < 0) + if(ssh_options_set_algo(session,SSH_COMP_S_C,"none,zlib@openssh.com,zlib") < 0) return -1; } else { if (ssh_options_set_algo(session, SSH_COMP_S_C, v) < 0) @@ -906,7 +924,6 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; } - session->opts.StrictHostKeyChecking = *(int*)value; break; case SSH_OPTIONS_PROXYCOMMAND: v = value; @@ -1029,6 +1046,37 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, session->opts.rekey_time = (*x) * 1000; } break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; + case SSH_OPTIONS_IDENTITY_AGENT: + v = value; + SAFE_FREE(session->opts.agent_socket); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.agent_socket = ssh_path_expand_tilde(v); + if (session->opts.agent_socket == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -1070,7 +1118,7 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { /** * @brief This function can get ssh options, it does not support all options provided for * ssh options set, but mostly those which a user-space program may care about having - * trusted the ssh driver to infer these values from underlaying configuration files. + * trusted the ssh driver to infer these values from underlying configuration files. * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive * the port then please use ssh_options_get_port() which returns an unsigned int. * @@ -1175,10 +1223,10 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) * This is a helper for your application to generate the appropriate * options from the command line arguments.\n * The argv array and argc value are changed so that the parsed - * arguments wont appear anymore in them.\n + * arguments won't appear anymore in them.\n * The single arguments (without switches) are not parsed. thus, * myssh -l user localhost\n - * The command wont set the hostname value of options to localhost. + * The command won't set the hostname value of options to localhost. * * @param session The session to configure. * @@ -1217,6 +1265,11 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) int saveopterr = opterr; int opt; + /* Nothing to do here */ + if (argc <= 1) { + return SSH_OK; + } + opterr = 0; /* shut up getopt */ while((opt = getopt(argc, argv, "c:i:Cl:p:vb:rd12")) != -1) { switch(opt) { @@ -1250,8 +1303,6 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) break; default: { - char optv[3] = "- "; - optv[1] = optopt; tmp = realloc(save, (current + 1) * sizeof(char*)); if (tmp == NULL) { SAFE_FREE(save); @@ -1259,15 +1310,21 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) return -1; } save = tmp; - save[current] = strdup(optv); - if (save[current] == NULL) { - SAFE_FREE(save); - ssh_set_error_oom(session); - return -1; - } + save[current] = argv[optind-1]; current++; - if (optarg) { - save[current++] = argv[optind + 1]; + /* We can not use optarg here as getopt does not set it for + * unknown options. We need to manually extract following + * option and skip it manually from further processing */ + if (optind < argc && argv[optind][0] != '-') { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current++] = argv[optind]; + optind++; } } } /* switch */ @@ -1361,18 +1418,19 @@ int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) * * This should be the last call of all options, it may overwrite options which * are already set. It requires that the host name is already set with - * ssh_options_set_host(). + * ssh_options_set(SSH_OPTIONS_HOST). * * @param session SSH session handle * * @param filename The options file to use, if NULL the default - * ~/.ssh/config will be used. + * ~/.ssh/config and /etc/ssh/ssh_config will be used. * * @return 0 on success, < 0 on error. * - * @see ssh_options_set_host() + * @see ssh_options_set() */ -int ssh_options_parse_config(ssh_session session, const char *filename) { +int ssh_options_parse_config(ssh_session session, const char *filename) +{ char *expanded_filename; int r; @@ -1417,7 +1475,8 @@ int ssh_options_parse_config(ssh_session session, const char *filename) { return r; } -int ssh_options_apply(ssh_session session) { +int ssh_options_apply(ssh_session session) +{ struct ssh_iterator *it; char *tmp; int rc; @@ -1459,7 +1518,23 @@ int ssh_options_apply(ssh_session session) { session->opts.global_knownhosts = tmp; if (session->opts.ProxyCommand != NULL) { - tmp = ssh_path_expand_escape(session, session->opts.ProxyCommand); + char *p = NULL; + size_t plen = strlen(session->opts.ProxyCommand) + + 5 /* strlen("exec ") */; + + p = malloc(plen + 1 /* \0 */); + if (p == NULL) { + return -1; + } + + rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand); + if ((size_t)rc != plen) { + free(p); + return -1; + } + + tmp = ssh_path_expand_escape(session, p); + free(p); if (tmp == NULL) { return -1; } @@ -1492,12 +1567,27 @@ int ssh_options_apply(ssh_session session) { /** @} */ #ifdef WITH_SERVER +static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = sshbind->rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @addtogroup libssh_server * @{ */ -static int ssh_bind_set_key(ssh_bind sshbind, char **key_loc, - const void *value) { +static int +ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) +{ if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; @@ -1546,7 +1636,7 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * * - SSH_BIND_OPTIONS_HOSTKEY: * Set the path to an ssh host key, regardless - * of type. Only one key from per key type + * of type. Only one key from each key type * (RSA, DSA, ECDSA) is allowed in an ssh_bind * at a time, and later calls to this function * with this option for the same key type will @@ -1571,7 +1661,7 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol infomations, packet level + * - SSH_LOG_PACKET: Lower level protocol information, packet level * - SSH_LOG_FUNCTIONS: Every function path * * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: @@ -1647,6 +1737,21 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * set and then filtered against this list. * (const char *, comma-separated list). * + * - SSH_BIND_OPTIONS_MODULI + * Set the path to the moduli file. Defaults to + * /etc/ssh/moduli if not specified (const char *). + * + * - SSH_BIND_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by + * the server for both authentication and hostkey + * operations. The values under 768 bits are not accepted + * even with this configuration option as they are + * considered completely broken. Setting 0 will revert + * the value to defaults. + * Default is 1024 bits or 2048 bits in FIPS mode. + * (int) + * + * * @param value The value to set. This is a generic pointer and the * datatype which should be used is described at the * corresponding value of type above. @@ -1656,6 +1761,7 @@ static int ssh_bind_set_algo(ssh_bind sshbind, int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, const void *value) { + bool allowed; char *p, *q; const char *v; int i, rc; @@ -1679,6 +1785,16 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, if (rc != SSH_OK) { return -1; } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + ssh_key_free(key); + return -1; + } + key_type = ssh_key_type(key); switch (key_type) { case SSH_KEYTYPE_DSS: @@ -1687,9 +1803,9 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, bind_key_path_loc = &sshbind->dsakey; #else ssh_set_error(sshbind, - SSH_FATAL, - "DSS key used and libssh compiled " - "without DSA support"); + SSH_FATAL, + "DSS key used and libssh compiled " + "without DSA support"); #endif break; case SSH_KEYTYPE_ECDSA_P256: @@ -1710,9 +1826,9 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, bind_key_path_loc = &sshbind->rsakey; break; case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - bind_key_path_loc = &sshbind->ed25519key; - break; + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; default: ssh_set_error(sshbind, SSH_FATAL, @@ -1744,6 +1860,15 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, ssh_key *bind_key_loc = NULL; ssh_key key = (ssh_key)value; + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + return -1; + } + key_type = ssh_key_type(key); switch (key_type) { case SSH_KEYTYPE_DSS: @@ -1994,6 +2119,34 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, sshbind->config_processed = !(*x); } break; + case SSH_BIND_OPTIONS_MODULI: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->moduli_file); + sshbind->moduli_file = strdup(value); + if (sshbind->moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + if (*x > 0 && *x < 768) { + ssh_set_error(sshbind, SSH_REQUEST_DENIED, + "The provided value (%u) for minimal RSA key " + "size is too small. Use at least 768 bits.", *x); + return -1; + } + sshbind->rsa_min_size = *x; + } + break; default: ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return -1; @@ -2005,8 +2158,9 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) { - char buf[MAX_BUF_SIZE]; - char *r, *x = NULL; + char *buf = NULL; + char *r = NULL; + char *x = NULL; const char *p; size_t i, l; @@ -2022,6 +2176,13 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) return NULL; } + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(sshbind); + free(r); + return NULL; + } + p = r; buf[0] = '\0'; @@ -2030,6 +2191,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) buf[i] = *p; i++; if (i >= MAX_BUF_SIZE) { + free(buf); free(r); return NULL; } @@ -2049,12 +2211,14 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) default: ssh_set_error(sshbind, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); free(r); return NULL; } if (x == NULL) { ssh_set_error_oom(sshbind); + free(buf); free(r); return NULL; } @@ -2063,18 +2227,26 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) if (i >= MAX_BUF_SIZE) { ssh_set_error(sshbind, SSH_FATAL, "String too long"); + free(buf); free(x); free(r); return NULL; } l = strlen(buf); - strncpy(buf + l, x, sizeof(buf) - l - 1); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); buf[i] = '\0'; SAFE_FREE(x); } free(r); - return strdup(buf); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + } + return x; } /** @@ -2087,7 +2259,7 @@ static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) * @param sshbind SSH bind handle * * @param filename The options file to use; if NULL only the global - * configuration is parsed and applied (if it haven't been + * configuration is parsed and applied (if it hasn't been * processed before). * * @return 0 on success, < 0 on error. diff --git a/libssh/src/packet.c b/libssh/src/packet.c index ec4a720..e01351a 100644 --- a/libssh/src/packet.c +++ b/libssh/src/packet.c @@ -113,7 +113,7 @@ static ssh_packet_callback default_packet_handlers[]= { ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 #else /* WITH_SERVER */ NULL, -#endif /* WITH_SERVER */ +#endif /* WITH_SERVER */ ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 @@ -363,7 +363,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * Transitions: * - session->dh_handshake_state = DH_STATE_INIT_SENT * then calls dh_handshake_server which triggers: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ if (session->session_state != SSH_SESSION_STATE_DH) { @@ -391,7 +391,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) * * Transitions: - * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT * */ if (session->session_state != SSH_SESSION_STATE_DH) { @@ -425,7 +425,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * * Transitions: * - if authentication was successful: @@ -454,7 +454,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT @@ -492,7 +492,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se /* * States required: * - session_state == SSH_SESSION_STATE_AUTHENTICATING - * - dh_hanshake_state == DH_STATE_FINISHED + * - dh_handshake_state == DH_STATE_FINISHED * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT @@ -1034,7 +1034,7 @@ static bool ssh_packet_need_rekey(ssh_session session, in_cipher->blocks + next_blocks > in_cipher->max_blocks); SSH_LOG(SSH_LOG_PACKET, - "packet: [data_rekey_needed=%d, out_blocks=%" PRIu64 ", in_blocks=%" PRIu64, + "rekey: [data_rekey_needed=%d, out_blocks=%" PRIu64 ", in_blocks=%" PRIu64 "]", data_rekey_needed, out_cipher->blocks + next_blocks, in_cipher->blocks + next_blocks); @@ -1054,14 +1054,14 @@ static bool ssh_packet_need_rekey(ssh_session session, * @len length of data received. It might not be enough for a complete packet * @returns number of bytes read and processed. */ -int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) +size_t ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) { ssh_session session = (ssh_session)user; uint32_t blocksize = 8; uint32_t lenfield_blocksize = 8; size_t current_macsize = 0; uint8_t *ptr = NULL; - int to_be_read; + long to_be_read; int rc; uint8_t *cleartext_packet = NULL; uint8_t *packet_second_block = NULL; @@ -1069,7 +1069,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) size_t packet_remaining; uint32_t packet_len, compsize, payloadsize; uint8_t padding; - size_t processed = 0; /* number of byte processed from the callback */ + size_t processed = 0; /* number of bytes processed from the callback */ enum ssh_packet_filter_result_e filter_result; struct ssh_crypto_struct *crypto = NULL; bool etm = false; @@ -1169,7 +1169,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) /* remote sshd sends invalid sizes? */ ssh_set_error(session, SSH_FATAL, - "Given numbers of bytes left to be read < 0 (%d)!", + "Given numbers of bytes left to be read < 0 (%ld)!", to_be_read); goto error; } @@ -1183,11 +1183,11 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) to_be_read = packet_len + sizeof(uint32_t) + current_macsize; /* if to_be_read is zero, the whole packet was blocksize bytes. */ if (to_be_read != 0) { - if (receivedlen < (unsigned int)to_be_read) { + if (receivedlen < (unsigned long)to_be_read) { /* give up, not enough data in buffer */ SSH_LOG(SSH_LOG_PACKET, "packet: partial packet (read len) " - "[len=%d, receivedlen=%d, to_be_read=%d]", + "[len=%d, receivedlen=%d, to_be_read=%ld]", packet_len, (int)receivedlen, to_be_read); @@ -1211,7 +1211,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (crypto != NULL) { mac = packet_second_block + packet_remaining; - if (etm) { + if (crypto->in_hmac != SSH_HMAC_NONE && etm) { rc = ssh_packet_hmac_verify(session, data, processed, @@ -1241,7 +1241,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } } - if (!etm) { + if (crypto->in_hmac != SSH_HMAC_NONE && !etm) { rc = ssh_packet_hmac_verify(session, ssh_buffer_get(session->in_buffer), ssh_buffer_get_len(session->in_buffer), @@ -1350,7 +1350,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) if (processed < receivedlen) { /* Handle a potential packet left in socket buffer */ SSH_LOG(SSH_LOG_PACKET, - "Processing %" PRIdS " bytes left in socket buffer", + "Processing %zu bytes left in socket buffer", receivedlen-processed); ptr = ((uint8_t*)data) + processed; @@ -1382,7 +1382,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) error: session->session_state= SSH_SESSION_STATE_ERROR; - SSH_LOG(SSH_LOG_PACKET,"Packet: processed %" PRIdS " bytes", processed); + SSH_LOG(SSH_LOG_PACKET,"Packet: processed %zu bytes", processed); return processed; } @@ -1684,6 +1684,9 @@ static int packet_send2(ssh_session session) hmac = ssh_packet_encrypt(session, ssh_buffer_get(session->out_buffer), ssh_buffer_get_len(session->out_buffer)); + /* XXX This returns null before switching on crypto, with none MAC + * and on various errors. + * We should distinguish between these cases to avoid hiding errors. */ if (hmac != NULL) { rc = ssh_buffer_add_data(session->out_buffer, hmac, @@ -1877,6 +1880,7 @@ int ssh_packet_set_newkeys(ssh_session session, enum ssh_crypto_direction_e direction) { + struct ssh_cipher_struct *in_cipher = NULL, *out_cipher = NULL; int rc; SSH_LOG(SSH_LOG_TRACE, @@ -1954,41 +1958,42 @@ ssh_packet_set_newkeys(ssh_session session, return SSH_ERROR; } - if (session->next_crypto->in_cipher == NULL || - session->next_crypto->out_cipher == NULL) { + in_cipher = session->next_crypto->in_cipher; + out_cipher = session->next_crypto->out_cipher; + if (in_cipher == NULL || out_cipher == NULL) { return SSH_ERROR; } /* Initialize rekeying states */ - ssh_init_rekey_state(session, - session->next_crypto->out_cipher); - ssh_init_rekey_state(session, - session->next_crypto->in_cipher); + ssh_init_rekey_state(session, out_cipher); + ssh_init_rekey_state(session, in_cipher); if (session->opts.rekey_time != 0) { ssh_timestamp_init(&session->last_rekey_time); SSH_LOG(SSH_LOG_PROTOCOL, "Set rekey after %" PRIu32 " seconds", session->opts.rekey_time/1000); } - /* Initialize the encryption and decryption keys in next_crypto */ - rc = session->next_crypto->in_cipher->set_decrypt_key( - session->next_crypto->in_cipher, - session->next_crypto->decryptkey, - session->next_crypto->decryptIV); - if (rc < 0) { - /* On error, make sure it is not used */ - session->next_crypto->used = 0; - return SSH_ERROR; + if (in_cipher->set_decrypt_key) { + /* Initialize the encryption and decryption keys in next_crypto */ + rc = in_cipher->set_decrypt_key(in_cipher, + session->next_crypto->decryptkey, + session->next_crypto->decryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } } - rc = session->next_crypto->out_cipher->set_encrypt_key( - session->next_crypto->out_cipher, - session->next_crypto->encryptkey, - session->next_crypto->encryptIV); - if (rc < 0) { - /* On error, make sure it is not used */ - session->next_crypto->used = 0; - return SSH_ERROR; + if (out_cipher->set_encrypt_key) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->next_crypto->encryptkey, + session->next_crypto->encryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } } return SSH_OK; diff --git a/libssh/src/packet_crypt.c b/libssh/src/packet_crypt.c index 95d438c..fe3f489 100644 --- a/libssh/src/packet_crypt.c +++ b/libssh/src/packet_crypt.c @@ -112,7 +112,7 @@ int ssh_packet_decrypt(ssh_session session, ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be used on multiple of " - "blocksize (received %" PRIdS ")", + "blocksize (received %zu)", encrypted_size); return SSH_ERROR; } @@ -130,14 +130,15 @@ int ssh_packet_decrypt(ssh_session session, return 0; } -unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) { struct ssh_crypto_struct *crypto = NULL; struct ssh_cipher_struct *cipher = NULL; HMACCTX ctx = NULL; char *out = NULL; - int etm_packet_offset = 0; - unsigned int finallen, blocksize; + int etm_packet_offset = 0, rc; + unsigned int blocksize; + size_t finallen = DIGEST_MAX_LEN; uint32_t seq, lenfield_blocksize; enum ssh_hmac_e type; bool etm; @@ -161,7 +162,7 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) if ((len - lenfield_blocksize - etm_packet_offset) % blocksize != 0) { ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set" - " on at least one blocksize (received %d)", len); + " on at least one blocksize (received %zu)", len); return NULL; } out = calloc(1, len); @@ -177,34 +178,62 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) crypto->hmacbuf, session->send_seq); memcpy(data, out, len); } else { - ctx = hmac_init(crypto->encryptMAC, hmac_digest_len(type), type); - if (ctx == NULL) { - SAFE_FREE(out); - return NULL; - } - - if (!etm) { - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); + if (type != SSH_HMAC_NONE) { + ctx = hmac_init(crypto->encryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + + if (!etm) { + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } } cipher->encrypt(cipher, (uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); memcpy((uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); - if (etm) { - PUSH_BE_U32(data, 0, len - etm_packet_offset); - hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, crypto->hmacbuf, &finallen); - } + if (type != SSH_HMAC_NONE) { + if (etm) { + PUSH_BE_U32(data, 0, len - etm_packet_offset); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("mac: ", data, len); - if (finallen != hmac_digest_len(type)) { - printf("Final len is %d\n", finallen); - } - ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); + ssh_log_hexdump("mac: ", data, len); + if (finallen != hmac_digest_len(type)) { + printf("Final len is %zu\n", finallen); + } + ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); #endif + } } explicit_bzero(out, len); SAFE_FREE(out); @@ -212,17 +241,6 @@ unsigned char *ssh_packet_encrypt(ssh_session session, void *data, uint32_t len) return crypto->hmacbuf; } -static int secure_memcmp(const void *s1, const void *s2, size_t n) -{ - int rc = 0; - const unsigned char *p1 = s1; - const unsigned char *p2 = s2; - for (; n > 0; --n) { - rc |= *p1++ ^ *p2++; - } - return (rc != 0); -} - /** * @internal * @@ -242,42 +260,71 @@ int ssh_packet_hmac_verify(ssh_session session, uint8_t *mac, enum ssh_hmac_e type) { - struct ssh_crypto_struct *crypto = NULL; - unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; - HMACCTX ctx; - unsigned int hmaclen; - uint32_t seq; - - /* AEAD types have no mac checking */ - if (type == SSH_HMAC_AEAD_POLY1305 || - type == SSH_HMAC_AEAD_GCM) { - return SSH_OK; - } + struct ssh_crypto_struct *crypto = NULL; + unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; + HMACCTX ctx; + size_t hmaclen = DIGEST_MAX_LEN; + uint32_t seq; + int cmp; + int rc; - crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); - if (crypto == NULL) { - return SSH_ERROR; - } + /* AEAD types have no mac checking */ + if (type == SSH_HMAC_AEAD_POLY1305 || + type == SSH_HMAC_AEAD_GCM) { + return SSH_OK; + } - ctx = hmac_init(crypto->decryptMAC, hmac_digest_len(type), type); - if (ctx == NULL) { - return -1; - } + crypto = ssh_packet_get_current_crypto(session, + SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } - seq = htonl(session->recv_seq); + ctx = hmac_init(crypto->decryptMAC, + hmac_digest_len(type), + type); + if (ctx == NULL) { + return SSH_ERROR; + } - hmac_update(ctx, (unsigned char *) &seq, sizeof(uint32_t)); - hmac_update(ctx, data, len); - hmac_final(ctx, hmacbuf, &hmaclen); + seq = htonl(session->recv_seq); + + rc = hmac_update(ctx, + (unsigned char *) &seq, + sizeof(uint32_t)); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_update(ctx, + data, + len); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_final(ctx, + hmacbuf, + &hmaclen); + if (rc != 1) { + return SSH_ERROR; + } #ifdef DEBUG_CRYPTO - ssh_log_hexdump("received mac",mac,hmaclen); - ssh_log_hexdump("Computed mac",hmacbuf,hmaclen); - ssh_log_hexdump("seq",(unsigned char *)&seq,sizeof(uint32_t)); + ssh_log_hexdump("received mac", + mac, + hmaclen); + ssh_log_hexdump("Computed mac", + hmacbuf, + hmaclen); + ssh_log_hexdump("seq", + (unsigned char *)&seq, + sizeof(uint32_t)); #endif - if (secure_memcmp(mac, hmacbuf, hmaclen) == 0) { - return 0; - } + cmp = secure_memcmp(mac, + hmacbuf, + hmaclen); + if (cmp == 0) { + return SSH_OK; + } - return -1; + return SSH_ERROR; } diff --git a/libssh/src/pcap.c b/libssh/src/pcap.c index c089854..9492df5 100644 --- a/libssh/src/pcap.c +++ b/libssh/src/pcap.c @@ -103,7 +103,7 @@ struct ssh_pcap_context_struct { ssh_session session; ssh_pcap_file file; int connected; - /* All of these information are useful to generate + /* All of this information is useful to generate * the dummy IP and TCP packets */ uint32_t ipsource; @@ -198,7 +198,7 @@ int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t o } /** - * @brief opens a new pcap file and create header + * @brief opens a new pcap file and creates header */ int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename){ ssh_buffer header; @@ -315,6 +315,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) socket_t fd; socklen_t len; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (session == NULL) { return SSH_ERROR; @@ -337,7 +338,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting local IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -347,7 +348,7 @@ static int ssh_pcap_context_connect(ssh_pcap_context ctx) ssh_set_error(session, SSH_REQUEST_DENIED, "Getting remote IP address: %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } @@ -506,7 +507,7 @@ int ssh_pcap_context_write(ssh_pcap_context ctx, /** @brief sets the pcap file used to trace the session * @param current session - * @param pcap an handler to a pcap file. A pcap file may be used in several + * @param pcap a handler to a pcap file. A pcap file may be used in several * sessions. * @returns SSH_ERROR in case of error, SSH_OK otherwise. */ diff --git a/libssh/src/pki.c b/libssh/src/pki.c index 932abf2..8f62ce2 100644 --- a/libssh/src/pki.c +++ b/libssh/src/pki.c @@ -4,6 +4,7 @@ * * Copyright (c) 2010 by Aris Adamantiadis * Copyright (c) 2011-2013 Andreas Schneider + * Copyright (c) 2019 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 @@ -32,9 +33,11 @@ */ #include "config.h" +#include "libssh/wrapper.h" #include #include +#include #include #include #include @@ -52,7 +55,7 @@ # undef unlink # define unlink _unlink # endif /* HAVE_IO_H */ -#endif +#endif /* _WIN32 */ #include "libssh/libssh.h" #include "libssh/session.h" @@ -64,14 +67,22 @@ #include "libssh/misc.h" #include "libssh/agent.h" +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif /* NOT MAX_LINE_SIZE */ + +#define PKCS11_URI "pkcs11:" + enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) { char *start = NULL; +#ifdef HAVE_DSA start = strstr(privkey, DSA_HEADER_BEGIN); if (start != NULL) { return SSH_KEYTYPE_DSS; } +#endif /* HAVE_DSA */ start = strstr(privkey, RSA_HEADER_BEGIN); if (start != NULL) { @@ -108,20 +119,22 @@ const char *ssh_pki_key_ecdsa_name(const ssh_key key) return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); #else return NULL; -#endif +#endif /* HAVE_ECC */ } /** * @brief creates a new empty SSH key + * * @returns an empty ssh_key handle, or NULL on error. */ -ssh_key ssh_key_new (void) { - ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); - if (ptr == NULL) { - return NULL; - } - ZERO_STRUCTP(ptr); - return ptr; +ssh_key ssh_key_new (void) +{ + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; } ssh_key ssh_key_dup(const ssh_key key) @@ -137,30 +150,13 @@ ssh_key ssh_key_dup(const ssh_key key) * @brief clean up the key and deallocate all existing keys * @param[in] key ssh_key to clean */ -void ssh_key_clean (ssh_key key){ - if(key == NULL) +void ssh_key_clean (ssh_key key) +{ + if (key == NULL) return; -#ifdef HAVE_LIBGCRYPT - if(key->dsa) gcry_sexp_release(key->dsa); - if(key->rsa) gcry_sexp_release(key->rsa); - if(key->ecdsa) gcry_sexp_release(key->ecdsa); -#elif defined HAVE_LIBCRYPTO - if(key->dsa) DSA_free(key->dsa); - if(key->rsa) RSA_free(key->rsa); -#ifdef HAVE_OPENSSL_ECC - if(key->ecdsa) EC_KEY_free(key->ecdsa); -#endif /* HAVE_OPENSSL_ECC */ -#elif defined HAVE_LIBMBEDCRYPTO - if (key->rsa != NULL) { - mbedtls_pk_free(key->rsa); - SAFE_FREE(key->rsa); - } - if (key->ecdsa != NULL) { - mbedtls_ecdsa_free(key->ecdsa); - SAFE_FREE(key->ecdsa); - } -#endif + pki_key_clean(key); + if (key->ed25519_privkey != NULL){ #ifdef HAVE_OPENSSL_ED25519 /* In OpenSSL implementation the private key is only the private @@ -169,29 +165,34 @@ void ssh_key_clean (ssh_key key){ explicit_bzero(key->ed25519_privkey, ED25519_KEY_LEN); #else explicit_bzero(key->ed25519_privkey, sizeof(ed25519_privkey)); -#endif +#endif /* HAVE_OPENSSL_ED25519 */ SAFE_FREE(key->ed25519_privkey); } SAFE_FREE(key->ed25519_pubkey); 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) { + ssh_string_burn(key->sk_application); + ssh_string_free(key->sk_application); + } key->cert_type = SSH_KEYTYPE_UNKNOWN; - key->flags=SSH_KEY_FLAG_EMPTY; - key->type=SSH_KEYTYPE_UNKNOWN; + key->flags = SSH_KEY_FLAG_EMPTY; + key->type = SSH_KEYTYPE_UNKNOWN; key->ecdsa_nid = 0; - key->type_c=NULL; - key->dsa = NULL; - key->rsa = NULL; - key->ecdsa = NULL; + key->type_c = NULL; } /** * @brief deallocate a SSH key * @param[in] key ssh_key handle to free */ -void ssh_key_free (ssh_key key){ - if(key){ +void ssh_key_free (ssh_key key) +{ + if (key) { ssh_key_clean(key); SAFE_FREE(key); } @@ -208,7 +209,8 @@ void ssh_key_free (ssh_key key){ * SSH_KEYTYPE_ED25519_CERT01. * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown */ -enum ssh_keytypes_e ssh_key_type(const ssh_key key){ +enum ssh_keytypes_e ssh_key_type(const ssh_key key) +{ if (key == NULL) { return SSH_KEYTYPE_UNKNOWN; } @@ -296,6 +298,14 @@ const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { return "ecdsa-sha2-nistp521-cert-v01@openssh.com"; case SSH_KEYTYPE_ED25519_CERT01: return "ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return "sk-ssh-ed25519-cert-v01@openssh.com"; case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: return NULL; @@ -328,6 +338,10 @@ enum ssh_digest_e ssh_key_hash_from_name(const char *name) return SSH_DIGEST_SHA512; } else if (strcmp(name, "ssh-ed25519") == 0) { return SSH_DIGEST_AUTO; + } else if (strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_DIGEST_AUTO; } SSH_LOG(SSH_LOG_WARN, "Unknown signature name %s", name); @@ -366,7 +380,7 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) return 0; } } -#endif +#endif /* WITH_SERVER */ else { SSH_LOG(SSH_LOG_WARN, "Session invalid: not set as client nor server"); return 0; @@ -376,6 +390,42 @@ int ssh_key_algorithm_allowed(ssh_session session, const char *type) return ssh_match_group(allowed_list, type); } +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key) +{ + int key_size = ssh_key_size(key); + + if (min_size < 768) { + if (ssh_fips_mode()) { + min_size = 2048; + } else { + min_size = 1024; + } + } + return (key_size >= min_size); +} + +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + /** * @brief Convert a key type to a hash type. This is usually unambiguous * for all the key types, unless the SHA2 extension (RFC 8332) is @@ -509,7 +559,8 @@ enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { * * @return The enum ssh key type. */ -enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) +{ if (name == NULL) { return SSH_KEYTYPE_UNKNOWN; } @@ -544,6 +595,14 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { return SSH_KEYTYPE_ECDSA_P521_CERT01; } else if (strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) { return SSH_KEYTYPE_ED25519_CERT01; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA_CERT01; + } else if(strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519; + } else if(strcmp(name, "sk-ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519_CERT01; } return SSH_KEYTYPE_UNKNOWN; @@ -556,7 +615,8 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) { * * @return The matching public key type. */ -enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) +{ switch (type) { case SSH_KEYTYPE_DSS_CERT01: return SSH_KEYTYPE_DSS; @@ -570,6 +630,10 @@ enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { return SSH_KEYTYPE_ECDSA_P521; case SSH_KEYTYPE_ED25519_CERT01: return SSH_KEYTYPE_ED25519; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return SSH_KEYTYPE_SK_ECDSA; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return SSH_KEYTYPE_SK_ED25519; default: return type; } @@ -582,7 +646,8 @@ enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) { * * @return 1 if it is a public key, 0 if not. */ -int ssh_key_is_public(const ssh_key k) { +int ssh_key_is_public(const ssh_key k) +{ if (k == NULL) { return 0; } @@ -636,7 +701,17 @@ int ssh_key_cmp(const ssh_key k1, } } - if (k1->type == SSH_KEYTYPE_ED25519) { + 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) { + return 1; + } + } + + if (k1->type == SSH_KEYTYPE_ED25519 || + k1->type == SSH_KEYTYPE_SK_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); } @@ -666,30 +741,32 @@ void ssh_signature_free(ssh_signature sig) case SSH_KEYTYPE_DSS: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(sig->dsa_sig); -#endif +#endif /* HAVE_LIBGCRYPT */ break; case SSH_KEYTYPE_RSA: #ifdef HAVE_LIBGCRYPT gcry_sexp_release(sig->rsa_sig); #elif defined HAVE_LIBMBEDCRYPTO SAFE_FREE(sig->rsa_sig); -#endif +#endif /* HAVE_LIBGCRYPT */ break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: #ifdef HAVE_GCRYPT_ECC gcry_sexp_release(sig->ecdsa_sig); #elif defined HAVE_LIBMBEDCRYPTO bignum_safe_free(sig->ecdsa_sig.r); bignum_safe_free(sig->ecdsa_sig.s); -#endif +#endif /* HAVE_GCRYPT_ECC */ break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: #ifndef HAVE_OPENSSL_ED25519 /* When using OpenSSL, the signature is stored in sig->raw_sig */ SAFE_FREE(sig->ed25519_sig); -#endif +#endif /* HAVE_OPENSSL_ED25519 */ break; case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: @@ -697,6 +774,8 @@ void ssh_signature_free(ssh_signature sig) 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_ECDSA: case SSH_KEYTYPE_UNKNOWN: @@ -710,7 +789,7 @@ void ssh_signature_free(ssh_signature sig) } /** - * @brief import a base64 formated key from a memory c-string + * @brief import a base64 formatted key from a memory c-string * * @param[in] b64_key The c-string holding the base64 encoded key * @@ -721,7 +800,7 @@ void ssh_signature_free(ssh_signature sig) * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free() * * @return SSH_ERROR in case of error, SSH_OK otherwise. * @@ -784,9 +863,11 @@ int ssh_pki_import_privkey_base64(const char *b64_key, * @param[in] auth_data Private data passed to the auth function. * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_from_char(). * * @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, @@ -828,9 +909,10 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, } /** - * @brief Import a key from a file. + * @brief Import a private key from a file or a PKCS #11 device. * - * @param[in] filename The filename of the the private key. + * @param[in] filename The filename of the private key or the + * PKCS #11 URI corresponding to the private key. * * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL * if none is needed or it is unknown. @@ -840,7 +922,7 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, * @param[in] auth_data Private data passed to the auth function. * * @param[out] pkey A pointer to store the allocated ssh_key. You need to - * free the key. + * free the key using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -857,17 +939,25 @@ int ssh_pki_import_privkey_file(const char *filename, FILE *file; off_t size; int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; } +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PRIVATE); + return rc; + } +#endif /* WITH_PKCS11_URI */ + file = fopen(filename, "rb"); if (file == NULL) { SSH_LOG(SSH_LOG_WARN, "Error opening %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } @@ -877,7 +967,7 @@ int ssh_pki_import_privkey_file(const char *filename, SSH_LOG(SSH_LOG_WARN, "Error getting stat of %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -909,7 +999,7 @@ int ssh_pki_import_privkey_file(const char *filename, SSH_LOG(SSH_LOG_WARN, "Error reading %s: %s", filename, - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = 0; @@ -957,8 +1047,9 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, fp = fopen(filename, "wb"); if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } @@ -991,11 +1082,12 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, } /* temporary function to migrate seemlessly to ssh_key */ -ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) +{ ssh_public_key pub; ssh_key tmp; - if(key == NULL) { + if (key == NULL) { return NULL; } @@ -1004,38 +1096,47 @@ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { return NULL; } - pub = malloc(sizeof(struct ssh_public_key_struct)); + pub = calloc(1, sizeof(struct ssh_public_key_struct)); if (pub == NULL) { ssh_key_free(tmp); return NULL; } - ZERO_STRUCTP(pub); pub->type = tmp->type; pub->type_c = tmp->type_c; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L pub->dsa_pub = tmp->dsa; tmp->dsa = NULL; pub->rsa_pub = tmp->rsa; tmp->rsa = NULL; +#else + pub->key_pub = tmp->key; + tmp->key = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ ssh_key_free(tmp); return pub; } -ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) { +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) +{ ssh_private_key privkey; - privkey = malloc(sizeof(struct ssh_private_key_struct)); + privkey = calloc(1, sizeof(struct ssh_private_key_struct)); if (privkey == NULL) { ssh_key_free(key); return NULL; } privkey->type = key->type; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L privkey->dsa_priv = key->dsa; privkey->rsa_priv = key->rsa; +#else + privkey->key_priv = key->key; +#endif /* OPENSSL_VERSION_NUMBER */ return privkey; } @@ -1081,7 +1182,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_len(pubkey)); ssh_log_hexdump("privkey", ssh_string_data(privkey), ssh_string_len(privkey)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(p); SSH_STRING_FREE(p); ssh_string_burn(q); @@ -1122,7 +1223,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_string_len(iqmp)); ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(n); SSH_STRING_FREE(n); ssh_string_burn(e); @@ -1178,7 +1279,7 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: { ssh_string pubkey = NULL, privkey = NULL; @@ -1205,6 +1306,10 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, 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: @@ -1222,7 +1327,8 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, static int pki_import_pubkey_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_key key = NULL; int rc; @@ -1254,7 +1360,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); ssh_log_hexdump("g", ssh_string_data(g), ssh_string_len(g)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(p); SSH_STRING_FREE(p); ssh_string_burn(q); @@ -1284,7 +1390,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, #ifdef DEBUG_CRYPTO ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); -#endif +#endif /* DEBUG_CRYPTO */ ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); @@ -1300,6 +1406,7 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { ssh_string e = NULL; ssh_string i = NULL; @@ -1314,6 +1421,8 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, 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); goto fail; } @@ -1329,12 +1438,25 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, 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); + if (application == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + key->type_c = ssh_key_type_to_char(key->type); + } } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: { ssh_string pubkey = ssh_buffer_get_ssh_string(buffer); + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { SSH_LOG(SSH_LOG_WARN, "Invalid public key length"); ssh_string_burn(pubkey); @@ -1352,6 +1474,15 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); ssh_string_burn(pubkey); SSH_STRING_FREE(pubkey); + + if (type == SSH_KEYTYPE_SK_ED25519) { + ssh_string application = ssh_buffer_get_ssh_string(buffer); + if (application == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + } } break; case SSH_KEYTYPE_DSS_CERT01: @@ -1359,7 +1490,9 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, 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: default: @@ -1377,7 +1510,8 @@ static int pki_import_pubkey_buffer(ssh_buffer buffer, static int pki_import_cert_buffer(ssh_buffer buffer, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer cert; ssh_string tmp_s; const char *type_c; @@ -1437,6 +1571,12 @@ static int pki_import_cert_buffer(ssh_buffer buffer, case SSH_KEYTYPE_ED25519_CERT01: rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ED25519, &key); break; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ECDSA, &key); + break; + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ED25519, &key); + break; default: key = ssh_key_new(); } @@ -1458,14 +1598,14 @@ static int pki_import_cert_buffer(ssh_buffer buffer, } /** - * @brief Import a base64 formated public key from a memory c-string. + * @brief Import a base64 formatted public key from a memory c-string. * * @param[in] b64_key The base64 key to format. * * @param[in] type The type of the key to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1473,7 +1613,8 @@ static int pki_import_cert_buffer(ssh_buffer buffer, */ int ssh_pki_import_pubkey_base64(const char *b64_key, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; int rc; @@ -1513,14 +1654,15 @@ int ssh_pki_import_pubkey_base64(const char *b64_key, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ ssh_buffer buffer = NULL; ssh_string type_s = NULL; enum ssh_keytypes_e type; @@ -1573,12 +1715,57 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, } /** - * @brief Import a public key from the given filename. + *@brief Detect if the pathname in cmp is a PKCS #11 URI. * - * @param[in] filename The path to the public key. + * @param[in] cmp The path to the public/private key + * or a private/public PKCS #11 URI. + * + * @returns true if filename is a URI starting with "pkcs11:" + * false otherwise. + */ +bool ssh_pki_is_uri(const char *cmp) +{ + int rc; + + rc = strncmp(cmp, PKCS11_URI, strlen(PKCS11_URI)); + if (rc == 0) { + return true; + } + + return false; +} + +/** + *@brief export a Public PKCS #11 URI from a Private PKCS #11 URI + * by replacing "type=private" to "type=public". + * TODO: Improve the parser + * + * @param[in] priv_uri Private PKCS #11 URI. + * + * @returns pointer to the public PKCS #11 URI. You need to free + * the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char(). + */ +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) +{ + char *pub_uri_temp = NULL; + + pub_uri_temp = ssh_strreplace(priv_uri, + "type=private", + "type=public"); + + return pub_uri_temp; +} + +/** + * @brief Import a public key from a file or a PKCS #11 device. + * + * @param[in] filename The filename of the public key or the + * PKCS #11 URI corresponding to the public key. * * @param[out] pkey A pointer to store the allocated public key. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1595,15 +1782,23 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) FILE *file; off_t size; int rc, cmp; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; if (pkey == NULL || filename == NULL || *filename == '\0') { return SSH_ERROR; } +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PUBLIC); + return rc; + } +#endif /* WITH_PKCS11_URI */ + file = fopen(filename, "rb"); if (file == NULL) { SSH_LOG(SSH_LOG_WARN, "Error opening %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_EOF; } @@ -1611,7 +1806,7 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) if (rc < 0) { fclose(file); SSH_LOG(SSH_LOG_WARN, "Error gettint stat of %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); switch (errno) { case ENOENT: case EACCES: @@ -1638,7 +1833,7 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) if (size != sb.st_size) { SAFE_FREE(key_buf); SSH_LOG(SSH_LOG_WARN, "Error reading %s: %s", - filename, strerror(errno)); + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return SSH_ERROR; } key_buf[size] = '\0'; @@ -1687,14 +1882,14 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) } /** - * @brief Import a base64 formated certificate from a memory c-string. + * @brief Import a base64 formatted certificate from a memory c-string. * * @param[in] b64_cert The base64 cert to format. * * @param[in] type The type of the cert to format. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1702,7 +1897,8 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) */ int ssh_pki_import_cert_base64(const char *b64_cert, enum ssh_keytypes_e type, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); } @@ -1715,14 +1911,15 @@ int ssh_pki_import_cert_base64(const char *b64_cert, * 6.6 "Public Key Algorithms". * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_key_free() */ int ssh_pki_import_cert_blob(const ssh_string cert_blob, - ssh_key *pkey) { + ssh_key *pkey) +{ return ssh_pki_import_pubkey_blob(cert_blob, pkey); } @@ -1732,7 +1929,7 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, * @param[in] filename The path to the certificate. * * @param[out] pkey A pointer to store the allocated certificate. You need to - * free the memory. + * free the memory using ssh_key_free(). * * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission * denied, SSH_ERROR otherwise. @@ -1753,14 +1950,17 @@ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) * rsa : length of the key in bits (e.g. 1024, 2048, 4096) * dsa : length of the key in bits (e.g. 1024, 2048, 3072) * @param[out] pkey A pointer to store the allocated private key. You need - * to free the memory. + * to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * * @warning Generating a key pair may take some time. + * + * @see ssh_key_free() */ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, - ssh_key *pkey){ + ssh_key *pkey) +{ int rc; ssh_key key = ssh_key_new(); @@ -1811,7 +2011,7 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, goto error; } break; -#endif +#endif /* HAVE_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_key_generate_ed25519(key); if (rc == SSH_ERROR) { @@ -1824,6 +2024,10 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, 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: @@ -1843,7 +2047,7 @@ int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, * @param[in] privkey The private key to get the public key from. * * @param[out] pkey A pointer to store the newly allocated public key. You - * NEED to free the key. + * NEED to free the key using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * @@ -1881,11 +2085,11 @@ int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, * from. * * @param[out] pblob A pointer to store the newly allocated key blob. You - * NEED to free it. + * need to free it using ssh_string_free(). * * @return SSH_OK on success, SSH_ERROR otherwise. * - * @see SSH_STRING_FREE() + * @see ssh_string_free() */ int ssh_pki_export_pubkey_blob(const ssh_key key, ssh_string *pblob) @@ -1911,11 +2115,11 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, * @param[in] key The key to hash * * @param[out] b64_key A pointer to store the allocated base64 encoded key. You - * need to free the buffer. + * need to free the buffer using ssh_string_free_char() * * @return SSH_OK on success, SSH_ERROR on error. * - * @see SSH_STRING_FREE_CHAR() + * @see ssh_string_free_char() */ int ssh_pki_export_pubkey_base64(const ssh_key key, char **b64_key) @@ -1946,7 +2150,7 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, int ssh_pki_export_pubkey_file(const ssh_key key, const char *filename) { - char key_buf[4096]; + char key_buf[MAX_LINE_SIZE]; char host[256]; char *b64_key; char *user; @@ -2112,6 +2316,8 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob, ssh_string algorithm = NULL, blob = NULL; ssh_buffer buf; const char *alg = NULL; + uint8_t flags = 0; + uint32_t counter = 0; int rc; if (sig_blob == NULL || psig == NULL) { @@ -2143,17 +2349,32 @@ int ssh_pki_import_signature_blob(const ssh_string sig_blob, SSH_STRING_FREE(algorithm); blob = ssh_buffer_get_ssh_string(buf); - SSH_BUFFER_FREE(buf); if (blob == NULL) { + SSH_BUFFER_FREE(buf); return SSH_ERROR; } + if (type == SSH_KEYTYPE_SK_ECDSA || + type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_unpack(buf, "bd", &flags, &counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(blob); + return SSH_ERROR; + } + } + SSH_BUFFER_FREE(buf); + sig = pki_signature_from_blob(pubkey, blob, type, hash_type); SSH_STRING_FREE(blob); if (sig == NULL) { return SSH_ERROR; } + /* Set SK specific values */ + sig->sk_flags = flags; + sig->sk_counter = counter; + *psig = sig; return SSH_OK; } @@ -2209,6 +2430,8 @@ int pki_key_check_hash_compatible(ssh_key key, break; case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ECDSA: if (hash_type == SSH_DIGEST_SHA256) { return SSH_OK; } @@ -2227,6 +2450,8 @@ int pki_key_check_hash_compatible(ssh_key key, break; case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: if (hash_type == SSH_DIGEST_AUTO) { return SSH_OK; } @@ -2251,6 +2476,7 @@ int ssh_pki_signature_verify(ssh_session session, size_t input_len) { int rc; + bool allowed; enum ssh_keytypes_e key_type; if (session == NULL || sig == NULL || key == NULL || input == NULL) { @@ -2271,15 +2497,75 @@ int ssh_pki_signature_verify(ssh_session session, return SSH_ERROR; } + allowed = ssh_key_size_allowed(session, key); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The '%s' key of size %d is not " + "allowd by RSA_MIN_SIZE", key->type_c, ssh_key_size(key)); + return SSH_ERROR; + } + /* Check if public key and hash type are compatible */ rc = pki_key_check_hash_compatible(key, sig->hash_type); if (rc != SSH_OK) { return SSH_ERROR; } - rc = pki_verify_data_signature(sig, key, input, input_len); + 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) { - return rc; + 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_WARN, + "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_WARN, + "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); + 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), + 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; + } + + return pki_verify_data_signature(sig, key, input, input_len); } ssh_signature pki_do_sign(const ssh_key privkey, @@ -2383,7 +2669,6 @@ ssh_string ssh_pki_do_sign(ssh_session session, return sig_blob; } -#ifndef _WIN32 ssh_string ssh_pki_do_sign_agent(ssh_session session, struct ssh_buffer_struct *buf, const ssh_key pubkey) @@ -2437,7 +2722,6 @@ ssh_string ssh_pki_do_sign_agent(ssh_session session, return sig_blob; } -#endif /* _WIN32 */ #ifdef WITH_SERVER ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, @@ -2445,7 +2729,7 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, const enum ssh_digest_e digest) { struct ssh_crypto_struct *crypto = NULL; - + bool allowed; ssh_signature sig = NULL; ssh_string sig_blob = NULL; @@ -2457,11 +2741,17 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, return NULL; } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The hostkey size too small"); + return NULL; + } + crypto = session->next_crypto ? session->next_crypto : session->current_crypto; if (crypto->secret_hash == NULL){ - ssh_set_error(session,SSH_FATAL,"Missing secret_hash"); + ssh_set_error(session, SSH_FATAL, "Missing secret_hash"); return NULL; } @@ -2482,9 +2772,9 @@ ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, /* Generate the signature */ sig = pki_do_sign(privkey, - ssh_buffer_get(sign_input), - ssh_buffer_get_len(sign_input), - digest); + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); if (sig == NULL) { goto end; } diff --git a/libssh/src/pki_container_openssh.c b/libssh/src/pki_container_openssh.c index ecde4cd..cc97da7 100644 --- a/libssh/src/pki_container_openssh.c +++ b/libssh/src/pki_container_openssh.c @@ -49,7 +49,7 @@ * code. * * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory. + * need to free the memory using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on error. * diff --git a/libssh/src/pki_crypto.c b/libssh/src/pki_crypto.c index ec9cfa4..33544d6 100644 --- a/libssh/src/pki_crypto.c +++ b/libssh/src/pki_crypto.c @@ -5,6 +5,7 @@ * * Copyright (c) 2003-2009 by Aris Adamantiadis * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2019 by 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 @@ -28,12 +29,20 @@ #include "config.h" #include "libssh/priv.h" +#include "libcrypto-compat.h" #include -#include +#include +#include #include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include #include -#include "libcrypto-compat.h" +#else +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ #ifdef HAVE_OPENSSL_EC_H #include @@ -78,7 +87,36 @@ static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DSA_free(key->dsa); + key->dsa = NULL; + RSA_free(key->rsa); + key->rsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move whole HAVE_OPENSSL_ECC into #if < 0x3 above + */ +#if 1 + EC_KEY_free(key->ecdsa); + key->ecdsa = NULL; +#endif +#endif /* HAVE_OPENSSL_ECC */ + EVP_PKEY_free(key->key); + key->key = NULL; +} + #ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 static int pki_key_ecdsa_to_nid(EC_KEY *k) { const EC_GROUP *g = EC_KEY_get0_group(k); @@ -91,8 +129,42 @@ static int pki_key_ecdsa_to_nid(EC_KEY *k) return -1; } +#else +static int pki_key_ecdsa_to_nid(EVP_PKEY *k) +{ + char gname[25] = { 0 }; + int nid, rc; + + rc = EVP_PKEY_get_utf8_string_param(k, "group", gname, 25, NULL); + if (rc != 1) + return -1; + + if (strcmp(gname, NISTP256) == 0 + || strcmp(gname, "secp256r1") == 0 + || strcmp(gname, "prime256v1") == 0) { + nid = NID_X9_62_prime256v1; + } else if (strcmp(gname, NISTP384) == 0 + || strcmp(gname, "secp384r1") == 0) { + nid = NID_secp384r1; + } else if (strcmp(gname, NISTP521) == 0 + || strcmp(gname, "secp521r1") == 0) { + nid = NID_secp521r1; + } else + return -1; + + return nid; +} +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +#else +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k) +#endif /* OPENSSL_VERSION_NUMBER */ { int nid; @@ -155,6 +227,11 @@ int pki_key_ecdsa_nid_from_name(const char *name) return -1; } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 static ssh_string make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) { @@ -189,17 +266,39 @@ static ssh_string make_ecpoint_string(const EC_GROUP *g, return s; } +#endif /* OPENSSL_VERSION_NUMBER */ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_POINT *p = NULL; const EC_GROUP *g = NULL; int ok; BIGNUM *bexp = NULL; +#else + int rc; + const BIGNUM *expb; + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; + + if (group_name == NULL) { + return -1; + } + expb = ssh_make_string_bn(exp); +#endif /* OPENSSL_VERSION_NUMBER */ key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); if (key->ecdsa == NULL) { return -1; @@ -243,17 +342,57 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) } return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, expb); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_POINT *p = NULL; const EC_GROUP *g = NULL; int ok; +#else + int rc; + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ key->ecdsa_nid = nid; key->type_c = pki_key_ecdsa_nid_to_name(nid); +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ + #if 1 key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); if (key->ecdsa == NULL) { return -1; @@ -284,12 +423,34 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) } return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ ssh_key pki_key_dup(const ssh_key key, int demote) { - ssh_key new; + ssh_key new = NULL; int rc; new = ssh_key_new(); @@ -307,6 +468,7 @@ ssh_key pki_key_dup(const ssh_key key, int demote) switch (key->type) { case SSH_KEYTYPE_DSS: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL, *priv_key = NULL; BIGNUM *np, *nq, *ng, *npub_key, *npriv_key; @@ -366,13 +528,32 @@ ssh_key pki_key_dup(const ssh_key key, int demote) goto fail; } } - +#else + rc = evp_dup_dsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *n = NULL, *e = NULL, *d = NULL; BIGNUM *nn, *ne, *nd; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L new->rsa = RSA_new(); if (new->rsa == NULL) { goto fail; @@ -457,7 +638,7 @@ ssh_key pki_key_dup(const ssh_key key, int demote) /* Memory management of ndmp1, ndmq1 and niqmp is transferred * to RSA object */ - rc = RSA_set0_crt_params(new->rsa, ndmp1, ndmq1, niqmp); + rc = RSA_set0_crt_params(new->rsa, ndmp1, ndmq1, niqmp); if (rc == 0) { BN_free(ndmp1); BN_free(ndmq1); @@ -466,7 +647,12 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } } } - +#else + rc = evp_dup_rsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ECDSA_P256: @@ -474,7 +660,27 @@ ssh_key pki_key_dup(const ssh_key key, int demote) case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_OPENSSL_ECC new->ecdsa_nid = key->ecdsa_nid; - +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + rc = EC_KEY_up_ref(key->ecdsa); + if (rc != 1) { + goto fail; + } + new->ecdsa = key->ecdsa; + return new; + } +#endif /* WITH_PKCS11_URI */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 /* privkey -> pubkey */ if (demote && ssh_key_is_private(key)) { const EC_POINT *p; @@ -495,10 +701,20 @@ ssh_key pki_key_dup(const ssh_key key, int demote) goto fail; } } else { - new->ecdsa = EC_KEY_dup(key->ecdsa); + rc = EC_KEY_up_ref(key->ecdsa); + if (rc != 1) { + goto fail; + } + new->ecdsa = key->ecdsa; + } +#else + rc = evp_dup_ecdsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ break; -#endif +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_ED25519: rc = pki_ed25519_key_dup(new, key); if (rc != SSH_OK) { @@ -518,25 +734,57 @@ ssh_key pki_key_dup(const ssh_key key, int demote) } int pki_key_generate_rsa(ssh_key key, int parameter){ - BIGNUM *e; int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *e; +#else + OSSL_PARAM params[3]; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + unsigned e = 65537; +#endif /* OPENSSL_VERSION_NUMBER */ - e = BN_new(); - key->rsa = RSA_new(); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + e = BN_new(); + key->rsa = RSA_new(); + + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key->rsa, parameter, e, NULL); + + BN_free(e); + + if (rc <= 0 || key->rsa == NULL) + return SSH_ERROR; +#else + key->key = NULL; + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } - BN_set_word(e, 65537); - rc = RSA_generate_key_ex(key->rsa, parameter, e, NULL); + params[0] = OSSL_PARAM_construct_int("bits", ¶meter); + params[1] = OSSL_PARAM_construct_uint("e", &e); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(pctx, &(key->key)); - BN_free(e); + EVP_PKEY_CTX_free(pctx); - if (rc <= 0 || key->rsa == NULL) - return SSH_ERROR; + if (rc != 1 || key->key == NULL) + return SSH_ERROR; +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_OK; } int pki_key_generate_dss(ssh_key key, int parameter){ int rc; -#if OPENSSL_VERSION_NUMBER > 0x00908000L +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = DSA_new(); if (key->dsa == NULL) { return SSH_ERROR; @@ -553,50 +801,146 @@ int pki_key_generate_dss(ssh_key key, int parameter){ key->dsa = NULL; return SSH_ERROR; } -#else - key->dsa = DSA_generate_parameters(parameter, NULL, 0, NULL, NULL, - NULL, NULL); - if(key->dsa == NULL){ - return SSH_ERROR; - } -#endif rc = DSA_generate_key(key->dsa); - if (rc != 1){ + if (rc != 1) { DSA_free(key->dsa); key->dsa=NULL; return SSH_ERROR; } +#else + OSSL_PARAM params[3]; + EVP_PKEY *param_key = NULL; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY_CTX *gctx = NULL; + int qbits = parameter < 2048 ? 160 : 256; + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + return SSH_ERROR; + } + pctx = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL); + if (pctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_paramgen_init(pctx); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + params[0] = OSSL_PARAM_construct_int("pbits", ¶meter); + params[1] = OSSL_PARAM_construct_int("qbits", &qbits); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + /* generating the domain parameters */ + rc = EVP_PKEY_generate(pctx, ¶m_key); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + EVP_PKEY_free(param_key); + return SSH_ERROR; + } + EVP_PKEY_CTX_free(pctx); + + gctx = EVP_PKEY_CTX_new_from_pkey(NULL, param_key, NULL); + if (gctx == NULL) { + EVP_PKEY_free(param_key); + return SSH_ERROR; + } + + EVP_PKEY_free(param_key); + rc = EVP_PKEY_keygen_init(gctx); + if (rc != 1) { + EVP_PKEY_CTX_free(gctx); + return SSH_ERROR; + } + /* generating the key from the domain parameters */ + rc = EVP_PKEY_generate(gctx, &key->key); + if (rc != 1) { + EVP_PKEY_free(key->key); + key->key = NULL; + EVP_PKEY_CTX_free(gctx); + return SSH_ERROR; + } + EVP_PKEY_CTX_free(gctx); +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_OK; } #ifdef HAVE_OPENSSL_ECC int pki_key_generate_ecdsa(ssh_key key, int parameter) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 int ok; - +#else + const char *group_name = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ switch (parameter) { + case 256: + key->ecdsa_nid = NID_X9_62_prime256v1; + key->type = SSH_KEYTYPE_ECDSA_P256; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + group_name = NISTP256; +#endif /* OPENSSL_VERSION_NUMBER */ + break; case 384: key->ecdsa_nid = NID_secp384r1; key->type = SSH_KEYTYPE_ECDSA_P384; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + group_name = NISTP384; +#endif /* OPENSSL_VERSION_NUMBER */ break; case 521: key->ecdsa_nid = NID_secp521r1; key->type = SSH_KEYTYPE_ECDSA_P521; - break; - case 256: - key->ecdsa_nid = NID_X9_62_prime256v1; - key->type = SSH_KEYTYPE_ECDSA_P256; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + group_name = NISTP521; +#endif /* OPENSSL_VERSION_NUMBER */ break; default: SSH_LOG(SSH_LOG_WARN, "Invalid parameter %d for ECDSA key " "generation", parameter); return SSH_ERROR; } - +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); if (key->ecdsa == NULL) { return SSH_ERROR; } +#else + key->key = EVP_EC_gen(group_name); + if (key->key == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 ok = EC_KEY_generate_key(key->ecdsa); if (!ok) { EC_KEY_free(key->ecdsa); @@ -604,77 +948,108 @@ int pki_key_generate_ecdsa(ssh_key key, int parameter) { } EC_KEY_set_asn1_flag(key->ecdsa, OPENSSL_EC_NAMED_CURVE); - +#endif /* OPENSSL_VERSION_NUMBER */ return SSH_OK; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +/* With OpenSSL 3.0 and higher the parameter 'what' + * is ignored and the comparision is done by OpenSSL + */ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { - switch (k1->type) { - case SSH_KEYTYPE_DSS: { - const BIGNUM *p1, *p2, *q1, *q2, *g1, *g2, - *pub_key1, *pub_key2, *priv_key1, *priv_key2; - if (DSA_size(k1->dsa) != DSA_size(k2->dsa)) { - return 1; - } - DSA_get0_pqg(k1->dsa, &p1, &q1, &g1); - DSA_get0_pqg(k2->dsa, &p2, &q2, &g2); - if (bignum_cmp(p1, p2) != 0) { - return 1; - } - if (bignum_cmp(q1, q2) != 0) { - return 1; - } - if (bignum_cmp(g1, g2) != 0) { - return 1; - } - DSA_get0_key(k1->dsa, &pub_key1, &priv_key1); - DSA_get0_key(k2->dsa, &pub_key2, &priv_key2); - if (bignum_cmp(pub_key1, pub_key2) != 0) { - return 1; - } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc; + (void) what; +#endif /* OPENSSL_VERSION_NUMBER */ - if (what == SSH_KEY_CMP_PRIVATE) { - if (bignum_cmp(priv_key1, priv_key2) != 0) { + switch (k1->type) { + case SSH_KEYTYPE_DSS: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + const BIGNUM *p1, *p2, *q1, *q2, *g1, *g2, + *pub_key1, *pub_key2, *priv_key1, *priv_key2; + if (DSA_size(k1->dsa) != DSA_size(k2->dsa)) { return 1; } - } - break; - } - case SSH_KEYTYPE_RSA: - case SSH_KEYTYPE_RSA1: { - const BIGNUM *e1, *e2, *n1, *n2, *p1, *p2, *q1, *q2; - if (RSA_size(k1->rsa) != RSA_size(k2->rsa)) { - return 1; - } - RSA_get0_key(k1->rsa, &n1, &e1, NULL); - RSA_get0_key(k2->rsa, &n2, &e2, NULL); - if (bignum_cmp(e1, e2) != 0) { - return 1; - } - if (bignum_cmp(n1, n2) != 0) { - return 1; - } - - if (what == SSH_KEY_CMP_PRIVATE) { - RSA_get0_factors(k1->rsa, &p1, &q1); - RSA_get0_factors(k2->rsa, &p2, &q2); + DSA_get0_pqg(k1->dsa, &p1, &q1, &g1); + DSA_get0_pqg(k2->dsa, &p2, &q2, &g2); if (bignum_cmp(p1, p2) != 0) { return 1; } - if (bignum_cmp(q1, q2) != 0) { return 1; } + if (bignum_cmp(g1, g2) != 0) { + return 1; + } + DSA_get0_key(k1->dsa, &pub_key1, &priv_key1); + DSA_get0_key(k2->dsa, &pub_key2, &priv_key2); + if (bignum_cmp(pub_key1, pub_key2) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (bignum_cmp(priv_key1, priv_key2) != 0) { + return 1; + } + } + break; + } +#endif /* OPENSSL_VERSION_NUMBER */ + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + const BIGNUM *e1, *e2, *n1, *n2, *p1, *p2, *q1, *q2; + if (RSA_size(k1->rsa) != RSA_size(k2->rsa)) { + return 1; + } + RSA_get0_key(k1->rsa, &n1, &e1, NULL); + RSA_get0_key(k2->rsa, &n2, &e2, NULL); + if (bignum_cmp(e1, e2) != 0) { + return 1; + } + if (bignum_cmp(n1, n2) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + RSA_get0_factors(k1->rsa, &p1, &q1); + RSA_get0_factors(k2->rsa, &p2, &q2); + if (bignum_cmp(p1, p2) != 0) { + return 1; + } + + if (bignum_cmp(q1, q2) != 0) { + return 1; + } + } + break; + } +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * delete this part of #if because it gets done below EC + */ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; } break; - } +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 #ifdef HAVE_OPENSSL_ECC { const EC_POINT *p1 = EC_KEY_get0_public_key(k1->ecdsa); @@ -700,17 +1075,28 @@ int pki_key_compare(const ssh_key k1, return 1; } } - break; } -#endif +#endif /* HAVE_OPENSSL_ECC */ +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * else + */ +#if 0 + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; + } + break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globaly */ case SSH_KEYTYPE_UNKNOWN: default: return 1; } - return 0; } @@ -732,6 +1118,7 @@ ssh_string pki_private_key_to_pem(const ssh_key key, switch (key->type) { case SSH_KEYTYPE_DSS: +#if OPENSSL_VERSION_NUMBER < 0x30000000L pkey = EVP_PKEY_new(); if (pkey == NULL) { goto err; @@ -739,8 +1126,10 @@ ssh_string pki_private_key_to_pem(const ssh_key key, rc = EVP_PKEY_set1_DSA(pkey, key->dsa); break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: +#if OPENSSL_VERSION_NUMBER < 0x30000000L pkey = EVP_PKEY_new(); if (pkey == NULL) { goto err; @@ -748,10 +1137,32 @@ ssh_string pki_private_key_to_pem(const ssh_key key, rc = EVP_PKEY_set1_RSA(pkey, key->rsa); break; -#ifdef HAVE_ECC +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Delete this part, because it is done below HAVE_ECC + */ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + 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; + + break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 pkey = EVP_PKEY_new(); if (pkey == NULL) { goto err; @@ -759,7 +1170,24 @@ ssh_string pki_private_key_to_pem(const ssh_key key, rc = EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); break; -#endif +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_ECC */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + 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; + + break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_ED25519: #ifdef HAVE_OPENSSL_ED25519 /* In OpenSSL, the input is the private key seed only, which means @@ -781,7 +1209,7 @@ ssh_string pki_private_key_to_pem(const ssh_key key, #else SSH_LOG(SSH_LOG_WARN, "PEM output not supported for key type ssh-ed25519"); goto err; -#endif +#endif /* HAVE_OPENSSL_ED25519 */ case SSH_KEYTYPE_DSS_CERT01: case SSH_KEYTYPE_RSA_CERT01: case SSH_KEYTYPE_ECDSA_P256_CERT01: @@ -853,20 +1281,22 @@ ssh_key pki_private_key_from_base64(const char *b64_key, void *auth_data) { BIO *mem = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA *dsa = NULL; RSA *rsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#ifdef HAVE_OPENSSL_ECC + EC_KEY *ecdsa = NULL; +#else + void *ecdsa = NULL; +#endif /* HAVE_OPENSSL_ECC */ #ifdef HAVE_OPENSSL_ED25519 uint8_t *ed25519 = NULL; #else ed25519_privkey *ed25519 = NULL; -#endif +#endif /* HAVE_OPENSSL_ED25519 */ ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; -#ifdef HAVE_OPENSSL_ECC - EC_KEY *ecdsa = NULL; -#else - void *ecdsa = NULL; -#endif EVP_PKEY *pkey = NULL; mem = BIO_new_mem_buf((void*)b64_key, -1); @@ -894,6 +1324,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, } switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_DSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L dsa = EVP_PKEY_get1_DSA(pkey); if (dsa == NULL) { SSH_LOG(SSH_LOG_WARN, @@ -901,9 +1332,11 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(),NULL)); goto fail; } +#endif type = SSH_KEYTYPE_DSS; break; case EVP_PKEY_RSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L rsa = EVP_PKEY_get1_RSA(pkey); if (rsa == NULL) { SSH_LOG(SSH_LOG_WARN, @@ -911,10 +1344,16 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(),NULL)); goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ type = SSH_KEYTYPE_RSA; break; case EVP_PKEY_EC: #ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 ecdsa = EVP_PKEY_get1_EC_KEY(pkey); if (ecdsa == NULL) { SSH_LOG(SSH_LOG_WARN, @@ -922,17 +1361,30 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(), NULL)); goto fail; } +#endif /* OPENSSL_VERSION_NUMBER */ /* pki_privatekey_type_from_string always returns P256 for ECDSA * keys, so we need to figure out the correct type here */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ if (type == SSH_KEYTYPE_UNKNOWN) { SSH_LOG(SSH_LOG_WARN, "Invalid private key."); goto fail; } +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Remove these three lines + */ break; -#endif +#endif /* HAVE_OPENSSL_ECC */ #ifdef HAVE_OPENSSL_ED25519 case EVP_PKEY_ED25519: { @@ -967,16 +1419,16 @@ ssh_key pki_private_key_from_base64(const char *b64_key, goto fail; } type = SSH_KEYTYPE_ED25519; + } break; -#endif +#endif /* HAVE_OPENSSL_ED25519 */ default: - EVP_PKEY_free(pkey); SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", EVP_PKEY_base_id(pkey)); + EVP_PKEY_free(pkey); return NULL; } - EVP_PKEY_free(pkey); key = ssh_key_new(); if (key == NULL) { @@ -986,22 +1438,43 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->type = type; key->type_c = ssh_key_type_to_char(type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = dsa; key->rsa = rsa; +#endif /* OPENSSL_VERSION_NUMBER */ + key->key = pkey; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move key->ecdsa line into the #if above this + */ key->ecdsa = ecdsa; key->ed25519_privkey = ed25519; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ } -#endif +#endif /* HAVE_OPENSSL_ECC */ return key; fail: EVP_PKEY_free(pkey); ssh_key_free(key); +#if OPENSSL_VERSION_NUMBER < 0x30000000L DSA_free(dsa); RSA_free(rsa); +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move HAVE_OPENSSL_ECC #ifdef inside the #if above + */ #ifdef HAVE_OPENSSL_ECC EC_KEY_free(ecdsa); #endif @@ -1019,8 +1492,14 @@ int pki_privkey_build_dss(ssh_key key, ssh_string privkey) { int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L BIGNUM *bp, *bq, *bg, *bpub_key, *bpriv_key; +#else + const BIGNUM *pb, *qb, *gb, *pubb, *privb; + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = DSA_new(); if (key->dsa == NULL) { return SSH_ERROR; @@ -1052,6 +1531,41 @@ int pki_privkey_build_dss(ssh_key key, fail: DSA_free(key->dsa); return SSH_ERROR; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + pb = ssh_make_string_bn(p); + qb = ssh_make_string_bn(q); + gb = ssh_make_string_bn(g); + pubb = ssh_make_string_bn(pubkey); + privb = ssh_make_string_bn(privkey); + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, pb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, qb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, gb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pubb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, privb); + if (rc != 1) + goto err; + + rc = evp_build_pkey("DSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_dss(ssh_key key, @@ -1060,8 +1574,14 @@ int pki_pubkey_build_dss(ssh_key key, ssh_string g, ssh_string pubkey) { int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL; +#else + const BIGNUM *pb, *qb, *gb, *pubb; + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->dsa = DSA_new(); if (key->dsa == NULL) { return SSH_ERROR; @@ -1092,6 +1612,37 @@ int pki_pubkey_build_dss(ssh_key key, fail: DSA_free(key->dsa); return SSH_ERROR; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + pb = ssh_make_string_bn(p); + qb = ssh_make_string_bn(q); + gb = ssh_make_string_bn(g); + pubb = ssh_make_string_bn(pubkey); + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, pb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_Q, qb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, gb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pubb); + if (rc != 1) + goto err; + + rc = evp_build_pkey("DSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_privkey_build_rsa(ssh_key key, @@ -1103,8 +1654,14 @@ int pki_privkey_build_rsa(ssh_key key, ssh_string q) { int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L BIGNUM *be, *bn, *bd/*, *biqmp*/, *bp, *bq; +#else + const BIGNUM *nb, *eb, *db, *pb, *qb; + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->rsa = RSA_new(); if (key->rsa == NULL) { return SSH_ERROR; @@ -1144,14 +1701,57 @@ int pki_privkey_build_rsa(ssh_key key, fail: RSA_free(key->rsa); return SSH_ERROR; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + nb = ssh_make_string_bn(n); + eb = ssh_make_string_bn(e); + db = ssh_make_string_bn(d); + pb = ssh_make_string_bn(p); + qb = ssh_make_string_bn(q); + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, nb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, eb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, db); + if (rc != 1) + goto err; + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR1, pb); + if (rc != 1) + goto err; + + rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR2, qb); + if (rc != 1) + goto err; + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) { int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L BIGNUM *be = NULL, *bn = NULL; +#else + const BIGNUM *eb, *nb; + OSSL_PARAM_BLD *param_bld; +#endif /* OPENSSL_VERSION_NUMBER */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L key->rsa = RSA_new(); if (key->rsa == NULL) { return SSH_ERROR; @@ -1169,10 +1769,33 @@ int pki_pubkey_build_rsa(ssh_key key, goto fail; } - return SSH_OK; -fail: - RSA_free(key->rsa); - return SSH_ERROR; + return SSH_OK; +fail: + RSA_free(key->rsa); + return SSH_ERROR; +#else + nb = ssh_make_string_bn(n); + eb = ssh_make_string_bn(e); + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, nb); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, eb); + if (rc != 1) + goto err; + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ } ssh_string pki_publickey_to_blob(const ssh_key key) @@ -1186,6 +1809,11 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string g = NULL; ssh_string q = NULL; int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, + *bn = NULL, *be = NULL; + OSSL_PARAM *params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ buffer = ssh_buffer_new(); if (buffer == NULL) { @@ -1216,8 +1844,53 @@ ssh_string pki_publickey_to_blob(const ssh_key key) switch (key->type) { case SSH_KEYTYPE_DSS: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *bp, *bq, *bg, *bpub_key; DSA_get0_pqg(key->dsa, &bp, &bq, &bg); + DSA_get0_key(key->dsa, &bpub_key, 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_FFC_P); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_WARN, "DSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_FFC_Q); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_WARN, "DSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_FFC_G); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_WARN, "DSA: No param G has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bg); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_WARN, "DSA: No param PUB_KEY has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bpub_key); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ p = ssh_make_bignum_string((BIGNUM *)bp); if (p == NULL) { goto fail; @@ -1233,7 +1906,6 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - DSA_get0_key(key->dsa, &bpub_key, NULL); n = ssh_make_bignum_string((BIGNUM *)bpub_key); if (n == NULL) { goto fail; @@ -1264,13 +1936,46 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L const BIGNUM *be, *bn; 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_WARN, "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_WARN, "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; @@ -1294,50 +1999,151 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string_burn(n); SSH_STRING_FREE(n); n = NULL; - +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ break; } case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: rc = pki_ed25519_public_key_to_blob(buffer, key); if (rc == SSH_ERROR){ goto fail; } + if (key->type == SSH_KEYTYPE_SK_ED25519 && + ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + goto fail; + } 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 - 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; - } + { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + const void *pubkey; + size_t pubkey_len; + OSSL_PARAM *params, *locate_param; +#endif /* OPENSSL_VERSION_NUMBER */ + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - rc = ssh_buffer_add_ssh_string(buffer, type_s); - SSH_STRING_FREE(type_s); - if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - e = make_ecpoint_string(EC_KEY_get0_group(key->ecdsa), - EC_KEY_get0_public_key(key->ecdsa)); - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(key->ecdsa)) { + SSH_LOG(SSH_LOG_INFO, "It is mandatory to have separate public" + " ECDSA key objects in the PKCS #11 device. Unlike RSA," + " ECDSA public keys cannot be derived from their private keys."); + goto fail; } +#endif /* WITH_PKCS11_URI */ + e = make_ecpoint_string(EC_KEY_get0_group(key->ecdsa), + EC_KEY_get0_public_key(key->ecdsa)); +#else + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + OSSL_PARAM_free(params); + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !locate_param) { + SSH_LOG(SSH_LOG_INFO, "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + + rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len); + if (rc != 1) { + OSSL_PARAM_free(params); + OSSL_PARAM_free(locate_param); + goto fail; + } + + e = ssh_string_new(pubkey_len); +#endif /* OPENSSL_VERSION_NUMBER */ + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + if (memcpy(ssh_string_data(e), pubkey, pubkey_len) == NULL) { + OSSL_PARAM_free(params); + OSSL_PARAM_free(locate_param); + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + OSSL_PARAM_free(params); + OSSL_PARAM_free(locate_param); +#endif /* OPENSSL_VERSION_NUMBER */ + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + OSSL_PARAM_free(params); + OSSL_PARAM_free(locate_param); +#endif /* OPENSSL_VERSION_NUMBER */ - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { + if (key->type == SSH_KEYTYPE_SK_ECDSA && + ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { goto fail; } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; - - break; -#endif + break; + } +#endif /* HAVE_OPENSSL_ECC */ case SSH_KEYTYPE_UNKNOWN: default: goto fail; @@ -1370,6 +2176,15 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(q); ssh_string_burn(n); SSH_STRING_FREE(n); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ return NULL; } @@ -1381,10 +2196,10 @@ static ssh_string pki_dsa_signature_to_blob(const ssh_signature sig) const BIGNUM *pr = NULL, *ps = NULL; ssh_string r = NULL; - int r_len, r_offset_in, r_offset_out; + size_t r_len, r_offset_in, r_offset_out; ssh_string s = NULL; - int s_len, s_offset_in, s_offset_out; + size_t s_len, s_offset_in, s_offset_out; const unsigned char *raw_sig_data = NULL; size_t raw_sig_len; @@ -1568,7 +2383,7 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) #ifdef HAVE_OPENSSL_ECC sig_blob = pki_ecdsa_signature_to_blob(sig); break; -#endif +#endif /* HAVE_OPENSSL_ECC */ default: case SSH_KEYTYPE_UNKNOWN: SSH_LOG(SSH_LOG_WARN, "Unknown signature key type: %s", sig->type_c); @@ -1590,12 +2405,21 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, size_t rsalen = 0; size_t len = ssh_string_len(sig_blob); +#if OPENSSL_VERSION_NUMBER < 0x30000000L if (pubkey->rsa == NULL) { SSH_LOG(SSH_LOG_WARN, "Pubkey RSA field NULL"); goto errout; } rsalen = RSA_size(pubkey->rsa); +#else + if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) { + SSH_LOG(SSH_LOG_WARN, "Key has no RSA pubkey"); + goto errout; + } + + rsalen = EVP_PKEY_size(pubkey->key); +#endif /* OPENSSL_VERSION_NUMBER */ if (len > rsalen) { SSH_LOG(SSH_LOG_WARN, "Signature is too big: %lu > %lu", @@ -1605,9 +2429,9 @@ static int pki_signature_from_rsa_blob(const ssh_key pubkey, } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); -#endif +#endif /* DEBUG_CRYPTO */ if (len == rsalen) { sig->raw_sig = ssh_string_copy(sig_blob); @@ -1655,11 +2479,11 @@ static int pki_signature_from_dsa_blob(UNUSED_PARAM(const ssh_key pubkey), BIGNUM *pr = NULL, *ps = NULL; ssh_string r; - ssh_string s; + ssh_string s = NULL; size_t len; - int raw_sig_len = 0; + size_t raw_sig_len = 0; unsigned char *raw_sig_data = NULL; unsigned char *temp_raw_sig = NULL; @@ -1678,7 +2502,7 @@ static int pki_signature_from_dsa_blob(UNUSED_PARAM(const ssh_key pubkey), #ifdef DEBUG_CRYPTO ssh_log_hexdump("r", ssh_string_data(sig_blob), 20); ssh_log_hexdump("s", (unsigned char *)ssh_string_data(sig_blob) + 20, 20); -#endif +#endif /* DEBUG_CRYPTO */ r = ssh_string_new(20); if (r == NULL) { @@ -1952,6 +2776,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: rc = pki_signature_from_ed25519_blob(sig, sig_blob); if (rc != SSH_OK){ goto error; @@ -1963,6 +2788,8 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, 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_OPENSSL_ECC rc = pki_signature_from_ecdsa_blob(pubkey, sig_blob, sig); if (rc != SSH_OK) { @@ -2015,10 +2842,14 @@ static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) static EVP_PKEY *pki_key_to_pkey(ssh_key key) { EVP_PKEY *pkey = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc = 0; +#endif - switch(key->type) { + switch (key->type) { case SSH_KEYTYPE_DSS: case SSH_KEYTYPE_DSS_CERT01: +#if OPENSSL_VERSION_NUMBER < 0x30000000L if (key->dsa == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->dsa"); goto error; @@ -2031,9 +2862,11 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) EVP_PKEY_set1_DSA(pkey, key->dsa); break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_RSA_CERT01: +#if OPENSSL_VERSION_NUMBER < 0x30000000L if (key->rsa == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->rsa"); goto error; @@ -2046,13 +2879,37 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) EVP_PKEY_set1_RSA(pkey, key->rsa); break; +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Remove this #else part from here + */ +#else + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); + goto error; + } + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); + return NULL; + } + pkey = key->key; + break; +#endif /* OPENSSL_VERSION_NUMBER */ 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: # if defined(HAVE_OPENSSL_ECC) +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 if (key->ecdsa == NULL) { SSH_LOG(SSH_LOG_TRACE, "NULL key->ecdsa"); goto error; @@ -2065,9 +2922,29 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) EVP_PKEY_set1_EC_KEY(pkey, key->ecdsa); break; +#endif /* OPENSSL_VERSION_NUMBER */ # endif +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER >= 0x30000000L + */ +#if 0 + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); + goto error; + } + rc = EVP_PKEY_uo_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); + return NULL; + } + pkey = key->key; + break; +#endif /* OPENSSL_VERSION_NUMBER */ case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: # if defined(HAVE_OPENSSL_ED25519) if (ssh_key_is_private(key)) { if (key->ed25519_privkey == NULL) { @@ -2114,7 +2991,7 @@ static EVP_PKEY *pki_key_to_pkey(ssh_key key) /** * @internal * - * @brief Sign the given input data. The digest of to be signed is calculated + * @brief Sign the given input data. The digest to be signed is calculated * internally as necessary. * * @param[in] privkey The private key to be used for signing. @@ -2183,7 +3060,7 @@ ssh_signature pki_sign_data(const ssh_key privkey, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Out of memory"); goto out; @@ -2260,9 +3137,7 @@ ssh_signature pki_sign_data(const ssh_key privkey, explicit_bzero(raw_sig_data, raw_sig_len); } SAFE_FREE(raw_sig_data); - if (pkey != NULL) { - EVP_PKEY_free(pkey); - } + EVP_PKEY_free(pkey); return sig; } @@ -2314,7 +3189,9 @@ int pki_verify_data_signature(ssh_signature signature, #ifndef HAVE_OPENSSL_ED25519 if (pubkey->type == SSH_KEYTYPE_ED25519 || - pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) { return pki_ed25519_verify(pubkey, signature, input, input_len); } @@ -2342,7 +3219,7 @@ int pki_verify_data_signature(ssh_signature signature, } /* Create the context */ - ctx = EVP_MD_CTX_create(); + ctx = EVP_MD_CTX_new(); if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Failed to create EVP_MD_CTX: %s", @@ -2386,10 +3263,46 @@ int pki_verify_data_signature(ssh_signature signature, if (ctx != NULL) { EVP_MD_CTX_free(ctx); } - if (pkey != NULL) { + EVP_PKEY_free(pkey); + return rc; +} + +int ssh_key_size(ssh_key key) +{ + int bits = 0; + EVP_PKEY *pkey = NULL; + + switch (key->type) { + case SSH_KEYTYPE_DSS: + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + pkey = pki_key_to_pkey(key); + if (pkey == NULL) { + return SSH_ERROR; + } + bits = EVP_PKEY_bits(pkey); EVP_PKEY_free(pkey); + return bits; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; } - return rc; } #ifdef HAVE_OPENSSL_ED25519 @@ -2512,4 +3425,172 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, } #endif /* HAVE_OPENSSL_ED25519 */ +/** + * @internal + * + * @brief Populate the public/private ssh_key from the engine with + * PKCS#11 URIs as the look up. + * + * @param[in] uri_name The PKCS#11 URI + * @param[in] nkey The ssh-key context for + * the key loaded from the engine. + * @param[in] key_type The type of the key used. Public/Private. + * + * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. + */ +int pki_uri_import(const char *uri_name, + ssh_key *nkey, + enum ssh_key_e key_type) +{ + ENGINE *engine = NULL; + EVP_PKEY *pkey = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + RSA *rsa = NULL; +#endif +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move HAVE_OPENSSL_ECC #ifdef into #if above + */ +#ifdef HAVE_OPENSSL_ECC + EC_KEY *ecdsa = NULL; +#else + void *ecdsa = NULL; +#endif + ssh_key key = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + + /* Do the init only once */ + engine = pki_get_engine(); + if (engine == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to initialize engine"); + goto fail; + } + + switch (key_type) { + case SSH_KEY_PRIVATE: + pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Could not load key: %s", + ERR_error_string(ERR_get_error(),NULL)); + goto fail; + } + break; + case SSH_KEY_PUBLIC: + pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Could not load key: %s", + ERR_error_string(ERR_get_error(),NULL)); + goto fail; + } + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Invalid key type: %d", key_type); + goto fail; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Parsing pub key: %s", + ERR_error_string(ERR_get_error(),NULL)); + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + type = SSH_KEYTYPE_RSA; + break; + case EVP_PKEY_EC: +#ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + ecdsa = EVP_PKEY_get1_EC_KEY(pkey); + if (ecdsa == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Parsing pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_WARN, "Invalid pub key."); + goto fail; + } + + break; +#endif + default: + SSH_LOG(SSH_LOG_WARN, "Unknown or invalid public key type %d", + EVP_PKEY_base_id(pkey)); + goto fail; + } + + key->key = pkey; + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; + if (key_type == SSH_KEY_PRIVATE) { + key->flags |= SSH_KEY_FLAG_PRIVATE; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->rsa = rsa; +#endif +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move line key->ecdsa into #if above + */ + key->ecdsa = ecdsa; +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ + } +#endif + + *nkey = key; + + return SSH_OK; + +fail: + EVP_PKEY_free(pkey); + ssh_key_free(key); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + RSA_free(rsa); +#endif +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move HAVE_OPENSSL_ECC #ifdef into #if above + */ +#ifdef HAVE_OPENSSL_ECC + EC_KEY_free(ecdsa); +#endif + + return SSH_ERROR; +} + #endif /* _PKI_CRYPTO_H */ diff --git a/libssh/src/pki_ed25519.c b/libssh/src/pki_ed25519.c index fdf94b4..6a5a4a8 100644 --- a/libssh/src/pki_ed25519.c +++ b/libssh/src/pki_ed25519.c @@ -1,5 +1,5 @@ /* - * pki_ed25519 .c - PKI infrastructure using ed25519 + * pki_ed25519.c - PKI infrastructure using ed25519 * * This file is part of the SSH Library * diff --git a/libssh/src/pki_gcrypt.c b/libssh/src/pki_gcrypt.c index bf45351..b619b1a 100644 --- a/libssh/src/pki_gcrypt.c +++ b/libssh/src/pki_gcrypt.c @@ -283,6 +283,23 @@ static int passphrase_to_key(char *data, unsigned int datalen, return 0; } +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->dsa) + gcry_sexp_release(key->dsa); + if (key->rsa) + gcry_sexp_release(key->rsa); + if (key->ecdsa) + gcry_sexp_release(key->ecdsa); + + key->dsa = NULL; + key->rsa = NULL; + key->ecdsa = NULL; +} + static int privatekey_decrypt(int algo, int mode, unsigned int key_len, unsigned char *iv, unsigned int iv_len, ssh_buffer data, ssh_auth_callback cb, @@ -1506,11 +1523,13 @@ int pki_key_compare(const ssh_key k1, } break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globaly */ 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) { return 1; @@ -1533,7 +1552,9 @@ int pki_key_compare(const ssh_key k1, 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; @@ -1675,14 +1696,20 @@ ssh_string pki_publickey_to_blob(const ssh_key key) break; case SSH_KEYTYPE_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK){ - goto fail; - } - break; + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc != SSH_OK){ + goto fail; + } + if (key->type == SSH_KEYTYPE_SK_ED25519 && + ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + goto fail; + } + 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)); @@ -1713,6 +1740,12 @@ ssh_string pki_publickey_to_blob(const ssh_key key) 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; + } + break; #endif case SSH_KEYTYPE_RSA1: @@ -1978,8 +2011,9 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, if (len > rsalen) { SSH_LOG(SSH_LOG_WARN, - "Signature is to big size: %lu", - (unsigned long)len); + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); ssh_signature_free(sig); return NULL; } @@ -2007,6 +2041,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, } 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); @@ -2016,6 +2051,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, 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; @@ -2358,7 +2394,9 @@ int pki_verify_data_signature(ssh_signature signature, break; case SSH_DIGEST_AUTO: if (pubkey->type == SSH_KEYTYPE_ED25519 || - pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) { verify_input = input; hlen = input_len; @@ -2427,6 +2465,8 @@ int pki_verify_data_signature(ssh_signature signature, 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, @@ -2454,6 +2494,8 @@ int pki_verify_data_signature(ssh_signature signature, #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"); @@ -2470,4 +2512,44 @@ int pki_verify_data_signature(ssh_signature signature, return SSH_OK; } +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_DSS: + case SSH_KEYTYPE_DSS_CERT01: + return gcry_pk_get_nbits(key->dsa); + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return gcry_pk_get_nbits(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return gcry_pk_get_nbits(key->ecdsa); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void) uri_name; + (void) key; + (void) key_type; + SSH_LOG(SSH_LOG_WARN, + "gcrypt does not support PKCS #11"); + return SSH_ERROR; +} #endif /* HAVE_LIBGCRYPT */ diff --git a/libssh/src/pki_mbedcrypto.c b/libssh/src/pki_mbedcrypto.c index c33fb72..4439b3c 100644 --- a/libssh/src/pki_mbedcrypto.c +++ b/libssh/src/pki_mbedcrypto.c @@ -26,6 +26,7 @@ #ifdef HAVE_LIBMBEDCRYPTO #include #include +#include "mbedcrypto-compat.h" #include "libssh/priv.h" #include "libssh/pki.h" @@ -37,6 +38,22 @@ #define MAX_PASSPHRASE_SIZE 1024 #define MAX_KEY_SIZE 32 +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa != NULL) { + mbedtls_pk_free(key->rsa); + SAFE_FREE(key->rsa); + } + + if (key->ecdsa != NULL) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } +} + ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { @@ -50,7 +67,7 @@ static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) { mbedtls_ecp_group_id id; - id = ecdsa->grp.id; + id = ecdsa->MBEDTLS_PRIVATE(grp.id); if (id == MBEDTLS_ECP_DP_SECP256R1) { return NID_mbedtls_nistp256; } else if (id == MBEDTLS_ECP_DP_SECP384R1) { @@ -84,118 +101,110 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, ssh_auth_callback auth_fn, void *auth_data) { ssh_key key = NULL; - mbedtls_pk_context *rsa = NULL; - mbedtls_pk_context *ecdsa = NULL; - ed25519_privkey *ed25519 = NULL; - enum ssh_keytypes_e type; + mbedtls_pk_context *pk = NULL; + mbedtls_pk_type_t mbed_type; int valid; /* mbedtls pk_parse_key expects strlen to count the 0 byte */ size_t b64len = strlen(b64_key) + 1; unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0}; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); +#endif - type = pki_privatekey_type_from_string(b64_key); - if (type == SSH_KEYTYPE_UNKNOWN) { - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key."); - return NULL; + pk = malloc(sizeof(mbedtls_pk_context)); + if (pk == NULL) { + goto fail; } - - switch (type) { - case SSH_KEYTYPE_RSA: - rsa = malloc(sizeof(mbedtls_pk_context)); - if (rsa == NULL) { - return NULL; - } - - mbedtls_pk_init(rsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - /* TODO fix signedness and strlen */ - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(rsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); - goto fail; - } - break; - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - ecdsa = malloc(sizeof(mbedtls_pk_context)); - if (ecdsa == NULL) { - return NULL; - } - - mbedtls_pk_init(ecdsa); - - if (passphrase == NULL) { - if (auth_fn) { - valid = auth_fn("Passphrase for private key:", (char *) tmp, - MAX_PASSPHRASE_SIZE, 0, 0, auth_data); - if (valid < 0) { - goto fail; - } - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, tmp, - strnlen((const char *) tmp, MAX_PASSPHRASE_SIZE)); - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, - b64len, NULL, - 0); - } - } else { - valid = mbedtls_pk_parse_key(ecdsa, - (const unsigned char *) b64_key, b64len, - (const unsigned char *) passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); - } - - if (valid != 0) { - char error_buf[100]; - mbedtls_strerror(valid, error_buf, 100); - SSH_LOG(SSH_LOG_WARN,"Parsing private key %s", error_buf); + mbedtls_pk_init(pk); + + if (passphrase == NULL) { + if (auth_fn) { + valid = auth_fn("Passphrase for private key:", + (char *)tmp, + MAX_PASSPHRASE_SIZE, + 0, + 0, + auth_data); + if (valid < 0) { goto fail; } - break; - case SSH_KEYTYPE_ED25519: - /* Cannot open ed25519 keys with libmbedcrypto */ - default: - SSH_LOG(SSH_LOG_WARN, "Unknown or invalid private key type %d", - type); - return NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE)); +#endif + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0, + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0); +#endif + } + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE), + mbedtls_ctr_drbg_random, + ctr_drbg); +#else + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE)); +#endif } + if (valid != 0) { + char error_buf[100]; + mbedtls_strerror(valid, error_buf, 100); + SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf); + goto fail; + } + + mbed_type = mbedtls_pk_get_type(pk); key = ssh_key_new(); if (key == NULL) { goto fail; } - if (ecdsa != NULL) { - mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*ecdsa); + switch (mbed_type) { + case MBEDTLS_PK_RSA: + case MBEDTLS_PK_RSA_ALT: + key->rsa = pk; + pk = NULL; + key->type = SSH_KEYTYPE_RSA; + break; + case MBEDTLS_PK_ECKEY: + case MBEDTLS_PK_ECDSA: { + /* type will be set later */ + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk); + pk = NULL; key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); if (key->ecdsa == NULL) { @@ -204,40 +213,36 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, mbedtls_ecdsa_init(key->ecdsa); mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + mbedtls_pk_free(pk); + SAFE_FREE(pk); key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); /* pki_privatekey_type_from_string always returns P256 for ECDSA - * keys, so we need to figure out the correct type here */ - type = pki_key_ecdsa_to_key_type(key->ecdsa); - if (type == SSH_KEYTYPE_UNKNOWN) { + * keys, so we need to figure out the correct type here */ + key->type = pki_key_ecdsa_to_key_type(key->ecdsa); + if (key->type == SSH_KEYTYPE_UNKNOWN) { SSH_LOG(SSH_LOG_WARN, "Invalid private key."); goto fail; } - } else { - key->ecdsa = NULL; + break; + } + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown or invalid private key type %d", + mbed_type); + return NULL; } - key->type = type; - key->type_c = ssh_key_type_to_char(type); + key->type_c = ssh_key_type_to_char(key->type); key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; - key->rsa = rsa; - key->ed25519_privkey = ed25519; - rsa = NULL; - ecdsa = NULL; return key; fail: ssh_key_free(key); - if (rsa != NULL) { - mbedtls_pk_free(rsa); - SAFE_FREE(rsa); - } - if (ecdsa != NULL) { - mbedtls_pk_free(ecdsa); - SAFE_FREE(ecdsa); + if (pk != NULL) { + mbedtls_pk_free(pk); + SAFE_FREE(pk); } return NULL; } @@ -304,6 +309,10 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) { mbedtls_rsa_context *rsa = NULL; const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; +#endif int rc; key->rsa = malloc(sizeof(mbedtls_pk_context)); @@ -320,26 +329,59 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); +#endif + rsa = mbedtls_pk_rsa(*key->rsa); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), + ssh_string_len(n)); +#else rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), ssh_string_len(n)); +#endif if (rc != 0) { goto fail; } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e), + ssh_string_len(e)); +#else rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), ssh_string_len(e)); +#endif if (rc != 0) { goto fail; } - rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } - return SSH_OK; + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + goto fail; + } +#else + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#endif + rc = SSH_OK; + goto exit; fail: + rc = SSH_ERROR; mbedtls_pk_free(key->rsa); SAFE_FREE(key->rsa); - return SSH_ERROR; +exit: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif + return rc; } ssh_key pki_key_dup(const ssh_key key, int demote) @@ -347,7 +389,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) ssh_key new = NULL; int rc; const mbedtls_pk_info_t *pk_info = NULL; - +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; + mbedtls_mpi D; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif new = ssh_key_new(); if (new == NULL) { @@ -362,6 +410,13 @@ ssh_key pki_key_dup(const ssh_key key, int demote) new->flags = key->flags; } +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif switch(key->type) { case SSH_KEYTYPE_RSA: { @@ -376,11 +431,26 @@ ssh_key pki_key_dup(const ssh_key key, int demote) pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); mbedtls_pk_setup(new->rsa, pk_info); - if (mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) { - rsa = mbedtls_pk_rsa(*key->rsa); - new_rsa = mbedtls_pk_rsa(*new->rsa); + if (!mbedtls_pk_can_do(key->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->rsa, MBEDTLS_PK_RSA)) + { + goto fail; + } + rsa = mbedtls_pk_rsa(*key->rsa); + new_rsa = mbedtls_pk_rsa(*new->rsa); + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } +#else rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); if (rc != 0) { goto fail; @@ -390,42 +460,70 @@ ssh_key pki_key_dup(const ssh_key key, int demote) if (rc != 0) { goto fail; } + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; - if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { - rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); - if (rc != 0) { - goto fail; - } - - rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); - if (rc != 0) { - goto fail; - } - - rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); - if (rc != 0) { - goto fail; - } - - rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); - if (rc != 0) { - goto fail; - } - - rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); - if (rc != 0) { - goto fail; - } - - rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); - if (rc != 0) { - goto fail; - } + rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); + if (rc != 0) { + goto fail; } +#endif } else { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; +#endif + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_complete(new_rsa); + if (rc != 0) { goto fail; } +#endif break; } @@ -443,12 +541,14 @@ ssh_key pki_key_dup(const ssh_key key, int demote) mbedtls_ecdsa_init(new->ecdsa); if (demote && ssh_key_is_private(key)) { - rc = mbedtls_ecp_copy(&new->ecdsa->Q, &key->ecdsa->Q); + rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&new->ecdsa->grp, &key->ecdsa->grp); + rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(grp)); if (rc != 0) { goto fail; } @@ -467,10 +567,19 @@ ssh_key pki_key_dup(const ssh_key key, int demote) goto fail; } - return new; + goto cleanup; + fail: - ssh_key_free(new); - return NULL; + SSH_KEY_FREE(new); +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + return new; } int pki_key_generate_rsa(ssh_key key, int parameter) @@ -508,77 +617,210 @@ int pki_key_generate_rsa(ssh_key key, int parameter) int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) { + int rc = 0; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N1; + mbedtls_mpi N2; + mbedtls_mpi P1; + mbedtls_mpi P2; + mbedtls_mpi Q1; + mbedtls_mpi Q2; + mbedtls_mpi E1; + mbedtls_mpi E2; + + mbedtls_mpi_init(&N1); + mbedtls_mpi_init(&N2); + mbedtls_mpi_init(&P1); + mbedtls_mpi_init(&P2); + mbedtls_mpi_init(&Q1); + mbedtls_mpi_init(&Q2); + mbedtls_mpi_init(&E1); + mbedtls_mpi_init(&E2); +#endif + switch (k1->type) { case SSH_KEYTYPE_RSA: { mbedtls_rsa_context *rsa1, *rsa2; - if (mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) && - mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) { - if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || - mbedtls_pk_get_bitlen(k1->rsa) != - mbedtls_pk_get_bitlen(k2->rsa)) { - return 1; + if (!mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->rsa, MBEDTLS_PK_RSA)) + { + break; + } + + if (mbedtls_pk_get_type(k1->rsa) != mbedtls_pk_get_type(k2->rsa) || + mbedtls_pk_get_bitlen(k1->rsa) != + mbedtls_pk_get_bitlen(k2->rsa)) + { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PUBLIC) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; } + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } +#else rsa1 = mbedtls_pk_rsa(*k1->rsa); rsa2 = mbedtls_pk_rsa(*k2->rsa); if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { - return 1; + rc = 1; + goto cleanup; } if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { - return 1; + rc = 1; + goto cleanup; + } +#endif + } else if (what == SSH_KEY_CMP_PRIVATE) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->rsa); + rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; } - if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { - return 1; - } + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) { + rc = 1; + goto cleanup; + } - if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { - return 1; - } + if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) { + rc = 1; + goto cleanup; } +#else + rsa1 = mbedtls_pk_rsa(*k1->rsa); + rsa2 = mbedtls_pk_rsa(*k2->rsa); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + rc = 1; + goto cleanup; + } +#endif } break; } case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: { + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; - if (ecdsa1->grp.id != ecdsa2->grp.id) { - return 1; + if (ecdsa1->MBEDTLS_PRIVATE(grp).id != + ecdsa2->MBEDTLS_PRIVATE(grp).id) { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.X, &ecdsa2->Q.X)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Y, &ecdsa2->Q.Y)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y))) + { + rc = 1; + goto cleanup; } - if (mbedtls_mpi_cmp_mpi(&ecdsa1->Q.Z, &ecdsa2->Q.Z)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z))) + { + rc = 1; + goto cleanup; } if (what == SSH_KEY_CMP_PRIVATE) { - if (mbedtls_mpi_cmp_mpi(&ecdsa1->d, &ecdsa2->d)) { - return 1; + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), + &ecdsa2->MBEDTLS_PRIVATE(d))) + { + rc = 1; + goto cleanup; } } break; } case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globally */ - return 0; + rc = 0; + break; default: - return 1; - } - - return 0; + rc = 1; + break; + } + +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N1); + mbedtls_mpi_free(&N2); + mbedtls_mpi_free(&P1); + mbedtls_mpi_free(&P2); + mbedtls_mpi_free(&Q1); + mbedtls_mpi_free(&Q2); + mbedtls_mpi_free(&E1); + mbedtls_mpi_free(&E2); +#endif + return rc; } ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const @@ -643,8 +885,17 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string e = NULL; ssh_string n = 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_init(&E); + mbedtls_mpi_init(&N); +#endif + buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; @@ -683,6 +934,22 @@ ssh_string pki_publickey_to_blob(const ssh_key key) rsa = mbedtls_pk_rsa(*key->rsa); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + + e = ssh_make_bignum_string(&E); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string(&N); + if (n == NULL) { + goto fail; + } +#else e = ssh_make_bignum_string(&rsa->E); if (e == NULL) { goto fail; @@ -692,6 +959,7 @@ ssh_string pki_publickey_to_blob(const ssh_key key) if (n == NULL) { goto fail; } +#endif if (ssh_buffer_add_ssh_string(buffer, e) < 0) { goto fail; @@ -713,6 +981,7 @@ ssh_string pki_publickey_to_blob(const ssh_key key) 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) { @@ -727,7 +996,8 @@ ssh_string pki_publickey_to_blob(const ssh_key key) return NULL; } - e = make_ecpoint_string(&key->ecdsa->grp, &key->ecdsa->Q); + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); if (e == NULL) { SSH_BUFFER_FREE(buffer); @@ -743,12 +1013,22 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; + if (key->type == SSH_KEYTYPE_SK_ECDSA && + ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + goto fail; + } + break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: rc = pki_ed25519_public_key_to_blob(buffer, key); if (rc != SSH_OK) { goto fail; } + if (key->type == SSH_KEYTYPE_SK_ED25519 && + ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { + goto fail; + } break; default: goto fail; @@ -766,6 +1046,10 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } 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); @@ -775,6 +1059,10 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif return NULL; } @@ -878,7 +1166,7 @@ static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const goto errout; } #ifdef DEBUG_CRYPTO - SSH_LOG(SSH_LOG_WARN, "RSA signature len: %lu", (unsigned long)len); + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); #endif @@ -944,7 +1232,8 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, break; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: { + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { ssh_buffer b; ssh_string r; ssh_string s; @@ -1013,6 +1302,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, break; } case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: rc = pki_signature_from_ed25519_blob(sig, sig_blob); if (rc == SSH_ERROR) { ssh_signature_free(sig); @@ -1036,6 +1326,7 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, mbedtls_md_type_t md = 0; unsigned char *sig = NULL; size_t slen; + size_t sig_size; int ok; switch (hash_type) { @@ -1054,7 +1345,8 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, return NULL; } - sig = malloc(mbedtls_pk_get_bitlen(privkey) / 8); + sig_size = mbedtls_pk_get_bitlen(privkey) / 8; + sig = malloc(sig_size); if (sig == NULL) { return NULL; } @@ -1064,6 +1356,9 @@ static ssh_string rsa_do_sign_hash(const unsigned char *digest, digest, dlen, sig, +#if MBEDTLS_VERSION_MAJOR > 2 + sig_size, +#endif &slen, mbedtls_ctr_drbg_random, ssh_get_mbedtls_ctr_drbg_context()); @@ -1130,10 +1425,10 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, return NULL; } - rc = mbedtls_ecdsa_sign(&privkey->ecdsa->grp, + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp), sig->ecdsa_sig.r, sig->ecdsa_sig.s, - &privkey->ecdsa->d, + &privkey->ecdsa->MBEDTLS_PRIVATE(d), hash, hlen, mbedtls_ctr_drbg_random, @@ -1300,6 +1595,8 @@ int pki_verify_data_signature(ssh_signature signature, break; case SSH_DIGEST_AUTO: if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || pubkey->type == SSH_KEYTYPE_ED25519_CERT01) { verify_input = input; @@ -1332,8 +1629,11 @@ int pki_verify_data_signature(ssh_signature signature, case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384_CERT01: case SSH_KEYTYPE_ECDSA_P521_CERT01: - rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->grp, hash, hlen, - &pubkey->ecdsa->Q, signature->ecdsa_sig.r, + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash, + hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q), + signature->ecdsa_sig.r, signature->ecdsa_sig.s); if (rc != 0) { char error_buf[100]; @@ -1345,6 +1645,8 @@ int pki_verify_data_signature(ssh_signature signature, break; 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"); @@ -1434,18 +1736,19 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - rc = mbedtls_mpi_read_binary(&keypair.d, ssh_string_data(exp), - ssh_string_len(exp)); + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), + ssh_string_data(exp), + ssh_string_len(exp)); if (rc != 0) { goto fail; } @@ -1501,17 +1804,17 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) goto fail; } - rc = mbedtls_ecp_copy(&keypair.Q, &Q); + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); if (rc != 0) { goto fail; } - rc = mbedtls_ecp_group_copy(&keypair.grp, &group); + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); if (rc != 0) { goto fail; } - mbedtls_mpi_init(&keypair.d); + mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d)); rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); if (rc != 0) { @@ -1600,4 +1903,46 @@ int pki_key_generate_dss(ssh_key key, int parameter) (void) parameter; return SSH_ERROR; } + +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return mbedtls_pk_get_bitlen(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return 256; + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return 384; + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return 521; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void) uri_name; + (void) key; + (void) key_type; + SSH_LOG(SSH_LOG_WARN, + "mbedcrypto does not support PKCS #11"); + return SSH_ERROR; +} #endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/libssh/src/poll.c b/libssh/src/poll.c index 4620694..1246f45 100644 --- a/libssh/src/poll.c +++ b/libssh/src/poll.c @@ -51,7 +51,7 @@ * * It's based on poll objects, each of which store a socket, its events and a * callback, which gets called whenever an event is set. The poll objects are - * attached to a poll context, which should be allocated on per thread basis. + * attached to a poll context, which should be allocated on a per thread basis. * * Polling the poll context will poll all the attached poll objects and call * their callbacks (handlers) if any of the socket events are set. This should @@ -84,15 +84,18 @@ struct ssh_poll_ctx_struct { #ifdef HAVE_POLL #include -void ssh_poll_init(void) { +void ssh_poll_init(void) +{ return; } -void ssh_poll_cleanup(void) { +void ssh_poll_cleanup(void) +{ return; } -int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ return poll((struct pollfd *) fds, nfds, timeout); } @@ -210,8 +213,8 @@ static short bsd_socket_compute_revents(int fd, short events) * poll implementation. * * Keep in mind that select is terribly inefficient. The interface is simply not - * meant to be used with maximum descriptor value greater, say, 32 or so. With - * a value as high as 1024 on Linux you'll pay dearly in every single call. + * meant to be used with maximum descriptor value greater than, say, 32 or so. + * With a value as high as 1024 on Linux you'll pay dearly in every single call. * poll() will be orders of magnitude faster. */ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) @@ -246,19 +249,17 @@ static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) } #endif - if (fds[i].events & (POLLIN | POLLRDNORM)) { - FD_SET (fds[i].fd, &readfds); - } + // we use the readfds to get POLLHUP and POLLERR, which are provided even when not requested + FD_SET (fds[i].fd, &readfds); + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { FD_SET (fds[i].fd, &writefds); } if (fds[i].events & (POLLPRI | POLLRDBAND)) { FD_SET (fds[i].fd, &exceptfds); } - if (fds[i].fd > max_fd && - (fds[i].events & (POLLIN | POLLOUT | POLLPRI | - POLLRDNORM | POLLRDBAND | - POLLWRNORM | POLLWRBAND))) { + + if (fds[i].fd > max_fd) { max_fd = fds[i].fd; rc = 0; } @@ -335,21 +336,24 @@ int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) { /** * @brief Allocate a new poll object, which could be used within a poll context. * - * @param fd Socket that will be polled. - * @param events Poll events that will be monitored for the socket. i.e. - * POLLIN, POLLPRI, POLLOUT - * @param cb Function to be called if any of the events are set. - * The prototype of cb is: - * int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, - * int revents, void *userdata); - * @param userdata Userdata to be passed to the callback function. NULL if - * not needed. + * @param[in] fd Socket that will be polled. + * @param[in] events Poll events that will be monitored for the socket. + * i.e. POLLIN, POLLPRI, POLLOUT + * @param[in] cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, + * socket_t fd, + * int revents, + * void *userdata); + * @param[in] userdata Userdata to be passed to the callback function. + * NULL if not needed. * - * @return A new poll object, NULL on error + * @return A new poll object, NULL on error */ -ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, - void *userdata) { +ssh_poll_handle +ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) +{ ssh_poll_handle p; p = malloc(sizeof(struct ssh_poll_handle_struct)); @@ -373,12 +377,13 @@ ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, * @param p Pointer to an already allocated poll object. */ -void ssh_poll_free(ssh_poll_handle p) { - if(p->ctx != NULL){ - ssh_poll_ctx_remove(p->ctx,p); - p->ctx=NULL; - } - SAFE_FREE(p); +void ssh_poll_free(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + ssh_poll_ctx_remove(p->ctx, p); + p->ctx = NULL; + } + SAFE_FREE(p); } /** @@ -388,8 +393,9 @@ void ssh_poll_free(ssh_poll_handle p) { * * @return Poll context or NULL if the poll object isn't attached. */ -ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { - return p->ctx; +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) +{ + return p->ctx; } /** @@ -399,8 +405,9 @@ ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) { * * @return Poll events. */ -short ssh_poll_get_events(ssh_poll_handle p) { - return p->events; +short ssh_poll_get_events(ssh_poll_handle p) +{ + return p->events; } /** @@ -410,11 +417,12 @@ short ssh_poll_get_events(ssh_poll_handle p) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_set_events(ssh_poll_handle p, short events) { - p->events = events; - if (p->ctx != NULL && !p->lock) { - p->ctx->pollfds[p->x.idx].events = events; - } +void ssh_poll_set_events(ssh_poll_handle p, short events) +{ + p->events = events; + if (p->ctx != NULL && !p->lock) { + p->ctx->pollfds[p->x.idx].events = events; + } } /** @@ -424,12 +432,13 @@ void ssh_poll_set_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param fd New file descriptor. */ -void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { - if (p->ctx != NULL) { - p->ctx->pollfds[p->x.idx].fd = fd; - } else { - p->x.fd = fd; - } +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) +{ + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } } /** @@ -439,8 +448,9 @@ void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_add_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +void ssh_poll_add_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); } /** @@ -450,8 +460,9 @@ void ssh_poll_add_events(ssh_poll_handle p, short events) { * @param p Pointer to an already allocated poll object. * @param events Poll events. */ -void ssh_poll_remove_events(ssh_poll_handle p, short events) { - ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +void ssh_poll_remove_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); } /** @@ -462,12 +473,13 @@ void ssh_poll_remove_events(ssh_poll_handle p, short events) { * @return Raw socket. */ -socket_t ssh_poll_get_fd(ssh_poll_handle p) { - if (p->ctx != NULL) { - return p->ctx->pollfds[p->x.idx].fd; - } +socket_t ssh_poll_get_fd(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } - return p->x.fd; + return p->x.fd; } /** * @brief Set the callback of a poll object. @@ -477,11 +489,12 @@ socket_t ssh_poll_get_fd(ssh_poll_handle p) { * @param userdata Userdata to be passed to the callback function. NULL if * not needed. */ -void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) { - if (cb != NULL) { - p->cb = cb; - p->cb_data = userdata; - } +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata) +{ + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } } /** @@ -495,7 +508,8 @@ void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userda * for the next 5. Set it to 0 if you want to use the * library's default value. */ -ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) +{ ssh_poll_ctx ctx; ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); @@ -518,25 +532,27 @@ ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) { * * @param ctx Pointer to an already allocated poll context. */ -void ssh_poll_ctx_free(ssh_poll_ctx ctx) { - if (ctx->polls_allocated > 0) { - while (ctx->polls_used > 0){ - ssh_poll_handle p = ctx->pollptrs[0]; - /* - * The free function calls ssh_poll_ctx_remove() and decrements - * ctx->polls_used - */ - ssh_poll_free(p); - } +void ssh_poll_ctx_free(ssh_poll_ctx ctx) +{ + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0){ + ssh_poll_handle p = ctx->pollptrs[0]; + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * ctx->polls_used + */ + ssh_poll_free(p); + } - SAFE_FREE(ctx->pollptrs); - SAFE_FREE(ctx->pollfds); - } + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } - SAFE_FREE(ctx); + SAFE_FREE(ctx); } -static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) +{ ssh_poll_handle *pollptrs; ssh_pollfd_t *pollfds; @@ -570,7 +586,8 @@ static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) { * * @return 0 on success, < 0 on error */ -int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) { +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) +{ socket_t fd; if (p->ctx != NULL) { @@ -622,7 +639,8 @@ int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, ssh_socket s) * @param ctx Pointer to an already allocated poll context. * @param p Pointer to an already allocated poll object. */ -void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) +{ size_t i; i = p->x.idx; @@ -648,7 +666,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * @brief Poll all the sockets associated through a poll object with a * poll context. If any of the events are set after the poll, the * call back function of the socket will be called. - * This function should be called once within the programs main loop. + * This function should be called once within the program's main loop. * * @param ctx Pointer to an already allocated poll context. * @param timeout An upper limit on the time for which ssh_poll_ctx() will @@ -657,7 +675,7 @@ void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) { * the poll() function. * @returns SSH_OK No error. * SSH_ERROR Error happened during the poll. - * SSH_AGAIN Timeout occured + * SSH_AGAIN Timeout occurred */ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) @@ -727,12 +745,13 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) * @param session SSH session * @returns the default ssh_poll_ctx */ -ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session){ - if(session->default_poll_ctx != NULL) - return session->default_poll_ctx; - /* 2 is enough for the default one */ - session->default_poll_ctx = ssh_poll_ctx_new(2); - return session->default_poll_ctx; +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) +{ + if(session->default_poll_ctx != NULL) + return session->default_poll_ctx; + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; } /* public event API */ @@ -754,10 +773,11 @@ struct ssh_event_struct { * ssh_session objects and socket fd which are going to be polled at the * same time as the event context. You would need a single event context * per thread. - * + * * @return The ssh_event object on success, NULL on failure. */ -ssh_event ssh_event_new(void) { +ssh_event ssh_event_new(void) +{ ssh_event event; event = malloc(sizeof(struct ssh_event_struct)); @@ -784,12 +804,14 @@ ssh_event ssh_event_new(void) { return event; } -static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, - void *userdata) { +static int +ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int revents, + void *userdata) +{ struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; (void)p; - if(pw->cb != NULL) { + if (pw->cb != NULL) { return pw->cb(fd, revents, pw->userdata); } return 0; @@ -812,11 +834,13 @@ static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, socket_t fd, int rev * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_fd(ssh_event event, socket_t fd, short events, - ssh_event_callback cb, void *userdata) { +int +ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata) +{ ssh_poll_handle p; struct ssh_event_fd_wrapper *pw; - + if(event == NULL || event->ctx == NULL || cb == NULL || fd == SSH_INVALID_SOCKET) { return SSH_ERROR; @@ -872,7 +896,7 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) } /** - * @brief remove the poll handle from session and assign them to a event, + * @brief remove the poll handle from session and assign them to an event, * when used in blocking mode. * * @param event The ssh_event object @@ -881,7 +905,8 @@ void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_add_session(ssh_event event, ssh_session session) { +int ssh_event_add_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; #ifdef WITH_SERVER struct ssh_iterator *iterator; @@ -933,16 +958,19 @@ int ssh_event_add_session(ssh_event event, ssh_session session) { * * @return SSH_ERROR in case of error */ -int ssh_event_add_connector(ssh_event event, ssh_connector connector){ +int ssh_event_add_connector(ssh_event event, ssh_connector connector) +{ return ssh_connector_set_event(connector, event); } /** - * @brief Poll all the sockets and sessions associated through an event object.i + * @brief Poll all the sockets and sessions associated through an event object. * * If any of the events are set after the poll, the call back functions of the * sessions or sockets will be called. * This function should be called once within the programs main loop. + * In case of failure, the errno should be consulted to find more information + * about the failure set by underlying poll imlpementation. * * @param event The ssh_event object to poll. * @@ -951,13 +979,15 @@ int ssh_event_add_connector(ssh_event event, ssh_connector connector){ * means an infinite timeout. This parameter is passed to * the poll() function. * @returns SSH_OK on success. - * SSH_ERROR Error happened during the poll. + * SSH_ERROR Error happened during the poll. Check errno to get more + * details about why it failed. * SSH_AGAIN Timeout occured */ -int ssh_event_dopoll(ssh_event event, int timeout) { +int ssh_event_dopoll(ssh_event event, int timeout) +{ int rc; - if(event == NULL || event->ctx == NULL) { + if (event == NULL || event->ctx == NULL) { return SSH_ERROR; } rc = ssh_poll_ctx_dopoll(event->ctx, timeout); @@ -973,7 +1003,8 @@ int ssh_event_dopoll(ssh_event event, int timeout) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_fd(ssh_event event, socket_t fd) { +int ssh_event_remove_fd(ssh_event event, socket_t fd) +{ register size_t i, used; int rc = SSH_ERROR; @@ -1019,7 +1050,8 @@ int ssh_event_remove_fd(ssh_event event, socket_t fd) { * @returns SSH_OK on success * SSH_ERROR on failure */ -int ssh_event_remove_session(ssh_event event, ssh_session session) { +int ssh_event_remove_session(ssh_event event, ssh_session session) +{ ssh_poll_handle p; register size_t i, used; int rc = SSH_ERROR; @@ -1027,14 +1059,14 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { struct ssh_iterator *iterator; #endif - if(event == NULL || event->ctx == NULL || session == NULL) { + if (event == NULL || event->ctx == NULL || session == NULL) { return SSH_ERROR; } used = event->ctx->polls_used; - for(i = 0; i < used; i++) { - p = event->ctx->pollptrs[i]; - if(p->session == session){ + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session == session) { /* * ssh_poll_ctx_remove() decrements * event->ctx->polls_used @@ -1054,8 +1086,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { } #ifdef WITH_SERVER iterator = ssh_list_get_iterator(event->sessions); - while(iterator != NULL) { - if((ssh_session)iterator->data == session) { + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { ssh_list_remove(event->sessions, iterator); /* there should be only one instance of this session */ break; @@ -1073,7 +1105,8 @@ int ssh_event_remove_session(ssh_event event, ssh_session session) { * @return SSH_OK on success * @return SSH_ERROR on failure */ -int ssh_event_remove_connector(ssh_event event, ssh_connector connector){ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector) +{ (void)event; return ssh_connector_remove_event(connector); } @@ -1091,13 +1124,13 @@ void ssh_event_free(ssh_event event) size_t used, i; ssh_poll_handle p; - if(event == NULL) { + if (event == NULL) { return; } if (event->ctx != NULL) { used = event->ctx->polls_used; - for(i = 0; i < used; i++) { + for (i = 0; i < used; i++) { p = event->ctx->pollptrs[i]; if (p->session != NULL) { ssh_poll_ctx_remove(event->ctx, p); @@ -1110,7 +1143,7 @@ void ssh_event_free(ssh_event event) ssh_poll_ctx_free(event->ctx); } #ifdef WITH_SERVER - if(event->sessions != NULL) { + if (event->sessions != NULL) { ssh_list_free(event->sessions); } #endif diff --git a/libssh/src/scp.c b/libssh/src/scp.c index 85d670a..ef5aa13 100644 --- a/libssh/src/scp.c +++ b/libssh/src/scp.c @@ -37,10 +37,14 @@ * * SCP protocol over SSH functions * + * @deprecated Please use SFTP instead + * * @{ */ /** + * @deprecated Please use SFTP instead + * * @brief Create a new scp session. * * @param[in] session The SSH session to use. @@ -108,6 +112,8 @@ ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the scp channel. * * @param[in] scp The scp context to initialize. @@ -119,7 +125,7 @@ ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) int ssh_scp_init(ssh_scp scp) { int rc; - char execbuffer[1024] = {0}; + char execbuffer[PATH_MAX] = {0}; char *quoted_location = NULL; size_t quoted_location_len = 0; size_t scp_location_len; @@ -230,6 +236,8 @@ int ssh_scp_init(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Close the scp channel. * * @param[in] scp The scp context to close. @@ -277,6 +285,8 @@ int ssh_scp_close(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Free a scp context. * * @param[in] scp The context to free. @@ -304,6 +314,8 @@ void ssh_scp_free(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Create a directory in a scp in sink mode. * * @param[in] scp The scp handle. @@ -313,13 +325,13 @@ void ssh_scp_free(ssh_scp scp) * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. * * @returns SSH_OK if the directory has been created, SSH_ERROR if - * an error occured. + * an error occurred. * * @see ssh_scp_leave_directory() */ int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) { - char buffer[1024] = {0}; + char buffer[PATH_MAX] = {0}; int rc; char *dir = NULL; char *perms = NULL; @@ -399,10 +411,12 @@ int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) } /** + * @deprecated Please use SFTP instead + * * @brief Leave a directory. * * @returns SSH_OK if the directory has been left, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_directory() */ @@ -436,6 +450,8 @@ int ssh_scp_leave_directory(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit * size. * @@ -449,14 +465,14 @@ int ssh_scp_leave_directory(ssh_scp scp) * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. * * @see ssh_scp_push_file() */ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int mode) { - char buffer[1024] = {0}; + char buffer[PATH_MAX] = {0}; int rc; char *file = NULL; char *perms = NULL; @@ -540,6 +556,8 @@ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, } /** + * @deprecated Please use SFTP instead + * * @brief Initialize the sending of a file to a scp in sink mode. * * @param[in] scp The scp handle. @@ -552,7 +570,7 @@ int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, * @param[in] mode The UNIX permissions for the new file, e.g. 0644. * * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an - * error occured. + * error occurred. */ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) { @@ -562,6 +580,8 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) /** * @internal * + * @deprecated Please use SFTP instead + * * @brief Wait for a response of the scp server. * * @param[in] scp The scp handle. @@ -569,7 +589,7 @@ int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) * @param[out] response A pointer where the response message must be copied if * any. This pointer must then be free'd. * - * @returns The return code, SSH_ERROR a error occured. + * @returns The return code, SSH_ERROR a error occurred. */ int ssh_scp_response(ssh_scp scp, char **response) { @@ -628,6 +648,8 @@ int ssh_scp_response(ssh_scp scp, char **response) } /** + * @deprecated Please use SFTP instead + * * @brief Write into a remote scp file. * * @param[in] scp The scp handle. @@ -637,7 +659,7 @@ int ssh_scp_response(ssh_scp scp, char **response) * @param[in] len The number of bytes to write. * * @returns SSH_OK if the write was successful, SSH_ERROR an error - * occured while writing. + * occurred while writing. */ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) { @@ -671,7 +693,6 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) scp->processed += w; } else { scp->state = SSH_SCP_ERROR; - //return = channel_get_exit_status(scp->channel); return SSH_ERROR; } @@ -702,6 +723,8 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Read a string on a channel, terminated by '\n' * * @param[in] scp The scp handle. @@ -713,7 +736,7 @@ int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) * null-terminated. * * @returns SSH_OK if the string was read, SSH_ERROR if an error - * occured while reading. + * occurred while reading. */ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) { @@ -748,6 +771,8 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) } /** + * @deprecated Please use SFTP instead + * * @brief Wait for a scp request (file, directory). * * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending @@ -769,7 +794,7 @@ int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) */ int ssh_scp_pull_request(ssh_scp scp) { - char buffer[MAX_BUF_SIZE] = {0}; + char buffer[PATH_MAX] = {0}; char *mode = NULL; char *p, *tmp; uint64_t size; @@ -859,7 +884,7 @@ int ssh_scp_pull_request(ssh_scp scp) return SSH_ERROR; } - /* a parsing error occured */ + /* a parsing error occurred */ error: SAFE_FREE(name); SAFE_FREE(mode); @@ -869,6 +894,8 @@ int ssh_scp_pull_request(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Deny the transfer of a file or creation of a directory coming from the * remote party. * @@ -881,7 +908,8 @@ int ssh_scp_pull_request(ssh_scp scp) */ int ssh_scp_deny_request(ssh_scp scp, const char *reason) { - char buffer[MAX_BUF_SIZE] = {0}; + char *buffer = NULL; + size_t len; int rc; if (scp == NULL) { @@ -894,8 +922,15 @@ int ssh_scp_deny_request(ssh_scp scp, const char *reason) return SSH_ERROR; } - snprintf(buffer, sizeof(buffer), "%c%s\n", 2, reason); - rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + len = strlen(reason) + 3; + buffer = malloc(len); + if (buffer == NULL) { + return SSH_ERROR; + } + + snprintf(buffer, len, "%c%s\n", 2, reason); + rc = ssh_channel_write(scp->channel, buffer, len - 1); + free(buffer); if (rc == SSH_ERROR) { return SSH_ERROR; } @@ -907,6 +942,8 @@ int ssh_scp_deny_request(ssh_scp scp, const char *reason) } /** + * @deprecated Please use SFTP instead + * * @brief Accepts transfer of a file or creation of a directory coming from the * remote party. * @@ -943,14 +980,18 @@ int ssh_scp_accept_request(ssh_scp scp) return SSH_OK; } -/** @brief Read from a remote scp file +/** + * @deprecated Please use SFTP instead + * + * @brief Read from a remote scp file + * * @param[in] scp The scp handle. * * @param[in] buffer The destination buffer. * * @param[in] size The size of the buffer. * - * @returns The nNumber of bytes read, SSH_ERROR if an error occured + * @returns The nNumber of bytes read, SSH_ERROR if an error occurred * while reading. */ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) @@ -1014,6 +1055,8 @@ int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) } /** + * @deprecated Please use SFTP instead + * * @brief Get the name of the directory or file being pushed from the other * party. * @@ -1030,6 +1073,8 @@ const char *ssh_scp_request_get_filename(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Get the permissions of the directory or file being pushed from the * other party. * @@ -1044,7 +1089,10 @@ int ssh_scp_request_get_permissions(ssh_scp scp) return scp->request_mode; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. * @warning The real size may not fit in a 32 bits field and may @@ -1059,7 +1107,10 @@ size_t ssh_scp_request_get_size(ssh_scp scp) return (size_t)scp->filelen; } -/** @brief Get the size of the file being pushed from the other party. +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. * * @returns The numeric size of the file being read. */ @@ -1072,6 +1123,8 @@ uint64_t ssh_scp_request_get_size64(ssh_scp scp) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a scp text mode to an integer. * * @param[in] mode The mode to convert, e.g. "0644". @@ -1085,6 +1138,8 @@ int ssh_scp_integer_mode(const char *mode) } /** + * @deprecated Please use SFTP instead + * * @brief Convert a unix mode into a scp string. * * @param[in] mode The mode to convert, e.g. 420 or 0644. @@ -1100,6 +1155,8 @@ char *ssh_scp_string_mode(int mode) } /** + * @deprecated Please use SFTP instead + * * @brief Get the warning string from a scp handle. * * @param[in] scp The scp handle. diff --git a/libssh/src/server.c b/libssh/src/server.c index 841a1c4..04949a9 100644 --- a/libssh/src/server.c +++ b/libssh/src/server.c @@ -361,7 +361,6 @@ static void ssh_server_connection_callback(ssh_session session){ } /* from now, the packet layer is handling incoming packets */ - session->socket_callbacks.data=ssh_packet_socket_callback; ssh_packet_register_socket_callback(session, session->socket); ssh_packet_set_default_callbacks(session); @@ -459,12 +458,12 @@ static void ssh_server_connection_callback(ssh_session session){ * @param user is a pointer to session * @returns Number of bytes processed, or zero if the banner is not complete. */ -static int callback_receive_banner(const void *data, size_t len, void *user) { +static size_t callback_receive_banner(const void *data, size_t len, void *user) { char *buffer = (char *) data; ssh_session session = (ssh_session) user; char *str = NULL; size_t i; - int ret=0; + size_t processed = 0; for (i = 0; i < len; i++) { #ifdef WITH_PCAP @@ -485,13 +484,13 @@ static int callback_receive_banner(const void *data, size_t len, void *user) { str = strdup(buffer); /* number of bytes read */ - ret = i + 1; + processed = i + 1; session->clientbanner = str; session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); session->ssh_connection_callback(session); - return ret; + return processed; } if(i > 127) { @@ -503,7 +502,7 @@ static int callback_receive_banner(const void *data, size_t len, void *user) { } } - return ret; + return processed; } /* returns 0 until the key exchange is not finished */ @@ -524,6 +523,30 @@ void ssh_set_auth_methods(ssh_session session, int auth_methods) session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; } +int ssh_send_issue_banner(ssh_session session, const ssh_string banner) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sending a server issue banner"); + + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_USERAUTH_BANNER, + banner); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + return rc; +} + /* Do the banner and key exchange */ int ssh_handle_key_exchange(ssh_session session) { int rc; @@ -707,7 +730,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por goto error; } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { rc = ssh_buffer_pack(msg->session->out_buffer, "d", bound_port); if (rc != SSH_OK) { @@ -719,7 +742,7 @@ int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_por return ssh_packet_send(msg->session); } - if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD && msg->global_request.bind_port == 0) { SSH_LOG(SSH_LOG_PACKET, "The client doesn't want to know the remote port!"); @@ -1025,7 +1048,7 @@ int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) { ssh_string pubkey_blob = NULL; int ret; - algo = ssh_string_from_char(msg->auth_request.pubkey->type_c); + algo = ssh_string_from_char(msg->auth_request.sigtype); if (algo == NULL) { return SSH_ERROR; } diff --git a/libssh/src/session.c b/libssh/src/session.c index 3199096..6025c13 100644 --- a/libssh/src/session.c +++ b/libssh/src/session.c @@ -97,16 +97,14 @@ ssh_session ssh_new(void) ssh_set_blocking(session, 1); session->maxchannel = FIRST_CHANNEL; -#ifndef _WIN32 session->agent = ssh_agent_new(session); if (session->agent == NULL) { goto err; } -#endif /* _WIN32 */ /* OPTIONS */ session->opts.StrictHostKeyChecking = 1; - session->opts.port = 0; + session->opts.port = 22; session->opts.fd = -1; session->opts.compressionlevel = 7; session->opts.nodelay = 0; @@ -301,9 +299,12 @@ void ssh_free(ssh_session session) SAFE_FREE(session->serverbanner); SAFE_FREE(session->clientbanner); SAFE_FREE(session->banner); + SAFE_FREE(session->disconnect_message); + SAFE_FREE(session->opts.agent_socket); SAFE_FREE(session->opts.bindaddr); SAFE_FREE(session->opts.custombanner); + SAFE_FREE(session->opts.moduli_file); SAFE_FREE(session->opts.username); SAFE_FREE(session->opts.host); SAFE_FREE(session->opts.sshdir); @@ -679,7 +680,7 @@ int ssh_handle_packets(ssh_session session, int timeout) { * @param[in] timeout Set an upper limit on the time for which this function * will block, in milliseconds. Specifying * SSH_TIMEOUT_INFINITE (-1) means an infinite timeout. - * Specifying SSH_TIMEOUT_USER means to use the timeout + * Specifying SSH_TIMEOUT_USER means using the timeout * specified in options. 0 means poll will return * immediately. * SSH_TIMEOUT_DEFAULT uses the session timeout if set or @@ -693,13 +694,13 @@ int ssh_handle_packets(ssh_session session, int timeout) { * SSH_ERROR otherwise. */ int ssh_handle_packets_termination(ssh_session session, - long timeout, + int timeout, ssh_termination_function fct, void *user) { struct ssh_timestamp ts; - long timeout_ms = SSH_TIMEOUT_INFINITE; - long tm; + int timeout_ms = SSH_TIMEOUT_INFINITE; + int tm; int ret = SSH_OK; /* If a timeout has been provided, use it */ @@ -856,7 +857,9 @@ void ssh_socket_exception_callback(int code, int errno_code, void *user){ if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); } else { - ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, SSH_FATAL, "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); } session->ssh_connection_callback(session); @@ -933,7 +936,7 @@ int ssh_send_debug (ssh_session session, const char *message, int always_display /** * @brief Set the session data counters. * - * This functions sets the counter structures to be used to calculate data + * This function sets the counter structures to be used to calculate data * which comes in and goes out through the session at different levels. * * @code @@ -1039,14 +1042,15 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) /** * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. * - * This is required under Microsoft platform as this library might use a + * This is required under Microsoft platform as this library might use a * different C library than your software, hence a different heap. * * @param[in] hash The buffer to deallocate. * * @see ssh_get_pubkey_hash() */ -void ssh_clean_pubkey_hash(unsigned char **hash) { +void ssh_clean_pubkey_hash(unsigned char **hash) +{ SAFE_FREE(*hash); } @@ -1056,7 +1060,7 @@ void ssh_clean_pubkey_hash(unsigned char **hash) { * @param[in] session The session to get the key from. * * @param[out] key A pointer to store the allocated key. You need to free - * the key. + * the key using ssh_key_free(). * * @return SSH_OK on success, SSH_ERROR on errror. * @@ -1100,15 +1104,15 @@ int ssh_get_publickey(ssh_session session, ssh_key *key) * * @param[in] type The type of the hash you want. * - * @param[in] hash A pointer to store the allocated buffer. It can be + * @param[out] hash A pointer to store the allocated buffer. It can be * freed using ssh_clean_pubkey_hash(). * * @param[in] hlen The length of the hash. * - * @return 0 on success, -1 if an error occured. + * @return 0 on success, -1 if an error occurred. * * @warning It is very important that you verify at some moment that the hash - * matches a known server. If you don't do it, cryptography wont help + * matches a known server. If you don't do it, cryptography won't help * you at making things secure. * OpenSSH uses SHA256 to print public key digests. * @@ -1123,7 +1127,7 @@ int ssh_get_publickey_hash(const ssh_key key, size_t *hlen) { ssh_string blob; - unsigned char *h; + unsigned char *h = NULL; int rc; rc = ssh_pki_export_pubkey_blob(key, &blob); diff --git a/libssh/src/sftp.c b/libssh/src/sftp.c index a834604..e01012a 100644 --- a/libssh/src/sftp.c +++ b/libssh/src/sftp.c @@ -58,7 +58,6 @@ /* Buffer size maximum is 256M */ #define SFTP_PACKET_SIZE_MAX 0x10000000 -#define SFTP_BUFFER_SIZE_MAX 16384 struct sftp_ext_struct { uint32_t count; @@ -229,40 +228,42 @@ sftp_new_channel(ssh_session session, ssh_channel channel) } #ifdef WITH_SERVER -sftp_session sftp_server_new(ssh_session session, ssh_channel chan){ - sftp_session sftp = NULL; +sftp_session +sftp_server_new(ssh_session session, ssh_channel chan) +{ + sftp_session sftp = NULL; - sftp = calloc(1, sizeof(struct sftp_session_struct)); - if (sftp == NULL) { - ssh_set_error_oom(session); - return NULL; - } + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } - sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); - if (sftp->read_packet == NULL) { - goto error; - } + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + goto error; + } - sftp->read_packet->payload = ssh_buffer_new(); - if (sftp->read_packet->payload == NULL) { - goto error; - } + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + goto error; + } - sftp->session = session; - sftp->channel = chan; + sftp->session = session; + sftp->channel = chan; - return sftp; + return sftp; error: - ssh_set_error_oom(session); - if (sftp->read_packet != NULL) { - if (sftp->read_packet->payload != NULL) { - SSH_BUFFER_FREE(sftp->read_packet->payload); + ssh_set_error_oom(session); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); } - SAFE_FREE(sftp->read_packet); - } - SAFE_FREE(sftp); - return NULL; + SAFE_FREE(sftp); + return NULL; } int sftp_server_init(sftp_session sftp){ @@ -386,11 +387,11 @@ void sftp_free(sftp_session sftp) SAFE_FREE(sftp); } -ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) { uint8_t header[5] = {0}; uint32_t payload_size; - ssize_t size; + int size; int rc; /* Add size of type */ @@ -415,7 +416,7 @@ ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) if ((uint32_t)size != ssh_buffer_get_len(payload)) { SSH_LOG(SSH_LOG_PACKET, - "Had to write %d bytes, wrote only %zd", + "Had to write %d bytes, wrote only %d", ssh_buffer_get_len(payload), size); } @@ -425,7 +426,8 @@ ssize_t sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) sftp_packet sftp_packet_read(sftp_session sftp) { - uint8_t buffer[SFTP_BUFFER_SIZE_MAX]; + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; sftp_packet packet = sftp->read_packet; uint32_t size; int nread; @@ -459,7 +461,7 @@ sftp_packet sftp_packet_read(sftp_session sftp) int s; // read from channel until 4 bytes have been read or an error occurs - s = ssh_channel_read(sftp->channel, buffer + nread, 4 - nread, 0); + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); if (s < 0) { goto error; } else if (s == 0) { @@ -476,7 +478,7 @@ sftp_packet sftp_packet_read(sftp_session sftp) } } while (nread < 4); - size = PULL_BE_U32(buffer, 0); + 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); @@ -484,7 +486,7 @@ sftp_packet sftp_packet_read(sftp_session sftp) } do { - nread = ssh_channel_read(sftp->channel, buffer, 1, 0); + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); if (nread < 0) { goto error; } else if (nread == 0) { @@ -499,34 +501,28 @@ sftp_packet sftp_packet_read(sftp_session sftp) } } while (nread < 1); - packet->type = buffer[0]; + packet->type = tmpbuf[0]; /* Remove the packet type size */ size -= sizeof(uint8_t); - nread = ssh_buffer_allocate_size(packet->payload, size); - if (nread < 0) { + /* 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, - sizeof(buffer) > size ? size : sizeof(buffer), - 0); + 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) { - rc = ssh_buffer_add_data(packet->payload, buffer, nread); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; - } + buffer += nread; + size -= nread; } else { /* nread == 0 */ /* Retry the reading unless the remote was closed */ is_eof = ssh_channel_is_eof(sftp->channel); @@ -538,8 +534,6 @@ sftp_packet sftp_packet_read(sftp_session sftp) goto error; } } - - size -= nread; } return packet; @@ -872,7 +866,7 @@ static int sftp_enqueue(sftp_session sftp, sftp_message msg) { } /* - * Pulls of a message from the queue based on the ID. + * 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){ @@ -1108,7 +1102,7 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) /* * Parse the attributes from a payload from some messages. It is coded on * baselines from the protocol version 4. - * This code is more or less dead but maybe we need it in future. + * 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) { @@ -1296,7 +1290,7 @@ static char *sftp_parse_longname(const char *longname, size_t len, field = 0; p = longname; - /* Find the beginning of the field which is specified by sftp_longanme_field_e. */ + /* Find the beginning of the field which is specified by sftp_longname_field_e. */ while(field != longname_field) { if(isspace(*p)) { field++; @@ -2009,7 +2003,7 @@ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { if (datalen > count) { ssh_set_error(sftp->session, SSH_FATAL, "Received a too big DATA packet from sftp server: " - "%" PRIdS " and asked for %" PRIdS, + "%zu and asked for %zu", datalen, count); SSH_STRING_FREE(datastring); return -1; @@ -2131,7 +2125,7 @@ int sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id){ if (ssh_string_len(datastring) > size) { ssh_set_error(sftp->session, SSH_FATAL, "Received a too big DATA packet from sftp server: " - "%" PRIdS " and asked for %u", + "%zu and asked for %u", ssh_string_len(datastring), size); SSH_STRING_FREE(datastring); return SSH_ERROR; @@ -2184,8 +2178,8 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } - packetlen=ssh_buffer_get_len(buffer); len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen=ssh_buffer_get_len(buffer); SSH_BUFFER_FREE(buffer); if (len < 0) { return -1; diff --git a/libssh/src/socket.c b/libssh/src/socket.c index 2fef8e7..4e637ae 100644 --- a/libssh/src/socket.c +++ b/libssh/src/socket.c @@ -28,6 +28,15 @@ #ifdef _WIN32 #include #include +#ifndef UNIX_PATH_MAX + /* Inlining the key portions of afunix.h in Windows 10 SDK; + * that header isn't available in the mingw environment. */ +#define UNIX_PATH_MAX 108 +struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif #if _MSC_VER >= 1400 #include #undef open @@ -233,8 +242,8 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, void *v_s) { ssh_socket s = (ssh_socket)v_s; - char buffer[MAX_BUF_SIZE]; - ssize_t nread; + void *buffer = NULL; + ssize_t nread = 0; int rc; int err = 0; socklen_t errlen = sizeof(err); @@ -275,8 +284,12 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, } if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { s->read_wontblock = 1; - nread = ssh_socket_unbuffered_read(s, buffer, sizeof(buffer)); + buffer = ssh_buffer_allocate(s->in_buffer, MAX_BUF_SIZE); + if (buffer) { + nread = ssh_socket_unbuffered_read(s, buffer, MAX_BUF_SIZE); + } if (nread < 0) { + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE); if (p != NULL) { ssh_poll_remove_events(p, POLLIN); } @@ -288,6 +301,10 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, } return -2; } + + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE - nread); + if (nread == 0) { if (p != NULL) { ssh_poll_remove_events(p, POLLIN); @@ -304,11 +321,7 @@ int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, s->session->socket_counter->in_bytes += nread; } - /* Bufferize the data and then call the callback */ - rc = ssh_buffer_add_data(s->in_buffer, buffer, nread); - if (rc < 0) { - return -1; - } + /* Call the callback */ if (s->callbacks != NULL && s->callbacks->data != NULL) { do { nread = s->callbacks->data(ssh_buffer_get(s->in_buffer), @@ -406,10 +419,10 @@ void ssh_socket_free(ssh_socket s) SAFE_FREE(s); } -#ifndef _WIN32 int ssh_socket_unix(ssh_socket s, const char *path) { struct sockaddr_un sunaddr; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; socket_t fd; sunaddr.sun_family = AF_UNIX; snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); @@ -418,28 +431,30 @@ int ssh_socket_unix(ssh_socket s, const char *path) if (fd == SSH_INVALID_SOCKET) { ssh_set_error(s->session, SSH_FATAL, "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", - strerror(errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return -1; } +#ifndef _WIN32 if (fcntl(fd, F_SETFD, 1) == -1) { ssh_set_error(s->session, SSH_FATAL, "Error from fcntl(fd, F_SETFD, 1): %s", - strerror(errno)); - close(fd); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } +#endif if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { - ssh_set_error(s->session, SSH_FATAL, "Error from connect(): %s", - strerror(errno)); - close(fd); + ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", + path, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); return -1; } ssh_socket_set_fd(s,fd); return 0; } -#endif /** \internal * \brief closes a socket @@ -473,12 +488,14 @@ void ssh_socket_close(ssh_socket s) kill(pid, SIGTERM); while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { - SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", strerror(errno)); + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); return; } } if (!WIFEXITED(status)) { - SSH_LOG(SSH_LOG_WARN, "Proxy command exitted abnormally"); + SSH_LOG(SSH_LOG_WARN, "Proxy command exited abnormally"); return; } SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); @@ -491,7 +508,7 @@ void ssh_socket_close(ssh_socket s) * @brief sets the file descriptor of the socket. * @param[out] s ssh_socket to update * @param[in] fd file descriptor to set - * @warning this function updates boths the input and output + * @warning this function updates both the input and output * file descriptors */ void ssh_socket_set_fd(ssh_socket s, socket_t fd) @@ -634,7 +651,7 @@ void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) * \returns SSH_OK, or SSH_ERROR * \warning has no effect on socket before a flush */ -int ssh_socket_write(ssh_socket s, const void *buffer, int len) +int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) { if (len > 0) { if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { @@ -664,11 +681,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -697,11 +715,12 @@ int ssh_socket_nonblocking_flush(ssh_socket s) s->last_errno, s->callbacks->userdata); } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ssh_set_error(session, SSH_FATAL, "Writing packet: error on socket (or connection " "closed): %s", - strerror(s->last_errno)); + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); } return SSH_ERROR; @@ -825,7 +844,7 @@ int ssh_socket_set_blocking(socket_t fd) /** * @internal * @brief Launches a socket connection - * If a the socket connected callback has been defined and + * If the socket connected callback has been defined and * a poll object exists, this call will be non blocking. * @param s socket to connect. * @param host hostname or ip address to connect to. @@ -842,7 +861,7 @@ int ssh_socket_connect(ssh_socket s, const char *bind_addr) { socket_t fd; - + if (s->state != SSH_SOCKET_NONE) { ssh_set_error(s->session, SSH_FATAL, "ssh_socket_connect called on socket not unconnected"); @@ -854,7 +873,7 @@ int ssh_socket_connect(ssh_socket s, return SSH_ERROR; } ssh_socket_set_fd(s,fd); - + return SSH_OK; } @@ -869,14 +888,30 @@ int ssh_socket_connect(ssh_socket s, void ssh_execute_command(const char *command, socket_t in, socket_t out) { - const char *args[] = {"/bin/sh", "-c", command, NULL}; + const char *shell = NULL; + const char *args[] = {NULL/*shell*/, "-c", command, NULL}; + int devnull; + int rc; + /* Prepare /dev/null socket for the stderr redirection */ - int devnull = open("/dev/null", O_WRONLY); + devnull = open("/dev/null", O_WRONLY); if (devnull == -1) { SSH_LOG(SSH_LOG_WARNING, "Failed to open /dev/null"); exit(1); } + /* + * By default, use the current users shell. This could fail with some + * shells like zsh or dash ... + */ + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + /* Fall back to bash. There are issues with dash or + * whatever people tend to link to /bin/sh */ + shell = "/bin/bash"; + } + args[0] = shell; + /* redirect in and out to stdin, stdout */ dup2(in, 0); dup2(out, 1); @@ -884,7 +919,13 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) dup2(devnull, STDERR_FILENO); close(in); close(out); - execv(args[0], (char * const *)args); + rc = execv(args[0], (char * const *)args); + if (rc < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", + command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } exit(1); } diff --git a/libssh/src/string.c b/libssh/src/string.c index acd3cf4..4440348 100644 --- a/libssh/src/string.c +++ b/libssh/src/string.c @@ -103,7 +103,7 @@ int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) { * @return The newly allocated string, NULL on error with errno * set. * - * @note The nul byte is not copied nor counted in the ouput string. + * @note The null byte is not copied nor counted in the output string. */ struct ssh_string_struct *ssh_string_from_char(const char *what) { struct ssh_string_struct *ptr; @@ -129,7 +129,7 @@ struct ssh_string_struct *ssh_string_from_char(const char *what) { /** * @brief Return the size of a SSH string. * - * @param[in] s The the input SSH string. + * @param[in] s The input SSH string. * * @return The size of the content of the string, 0 on error. */ @@ -149,7 +149,7 @@ size_t ssh_string_len(struct ssh_string_struct *s) { } /** - * @brief Get the the string as a C nul-terminated string. + * @brief Get the string as a C null-terminated string. * * This is only available as long as the SSH string exists. * @@ -168,7 +168,7 @@ const char *ssh_string_get_char(struct ssh_string_struct *s) } /** - * @brief Convert a SSH string to a C nul-terminated string. + * @brief Convert a SSH string to a C null-terminated string. * * @param[in] s The SSH input string. * diff --git a/libssh/src/threads/libcrypto.c b/libssh/src/threads/libcrypto.c index 5786948..dac840d 100644 --- a/libssh/src/threads/libcrypto.c +++ b/libssh/src/threads/libcrypto.c @@ -57,14 +57,12 @@ void libcrypto_lock_callback(int mode, int i, const char *file, int line) } } -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK static void libcrypto_THREADID_callback(CRYPTO_THREADID *id) { unsigned long thread_id = (*user_callbacks->thread_id)(); CRYPTO_THREADID_set_numeric(id, thread_id); } -#endif /* HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK */ int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) { @@ -96,12 +94,7 @@ int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) user_callbacks->mutex_init(&libcrypto_mutexes[i]); } -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK CRYPTO_THREADID_set_callback(libcrypto_THREADID_callback); -#else - CRYPTO_set_id_callback(user_callbacks->thread_id); -#endif - CRYPTO_set_locking_callback(libcrypto_lock_callback); return SSH_OK; @@ -116,12 +109,7 @@ void crypto_thread_finalize(void) return; } -#ifdef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK CRYPTO_THREADID_set_callback(NULL); -#else - CRYPTO_set_id_callback(NULL); -#endif - CRYPTO_set_locking_callback(NULL); for (i = 0; i < n; ++i) { diff --git a/libssh/src/wrapper.c b/libssh/src/wrapper.c index 05c820d..bff7bab 100644 --- a/libssh/src/wrapper.c +++ b/libssh/src/wrapper.c @@ -25,7 +25,7 @@ * Why a wrapper? * * Let's say you want to port libssh from libcrypto of openssl to libfoo - * you are going to spend hours to remove every references to SHA1_Update() + * you are going to spend hours removing every reference to SHA1_Update() * to libfoo_sha1_update after the work is finished, you're going to have * only this file to modify it's not needed to say that your modifications * are welcome. @@ -66,6 +66,9 @@ static struct ssh_hmac_struct ssh_hmac_tab[] = { { "hmac-sha2-256-etm@openssh.com", SSH_HMAC_SHA256, true }, { "hmac-sha2-512-etm@openssh.com", SSH_HMAC_SHA512, true }, { "hmac-md5-etm@openssh.com", SSH_HMAC_MD5, true }, +#ifdef WITH_INSECURE_NONE + { "none", SSH_HMAC_NONE, false }, +#endif /* WITH_INSECURE_NONE */ { NULL, 0, false } }; @@ -105,7 +108,7 @@ const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) } /* it allocates a new cipher structure based on its offset into the global table */ -static struct ssh_cipher_struct *cipher_new(int offset) { +static struct ssh_cipher_struct *cipher_new(uint8_t offset) { struct ssh_cipher_struct *cipher = NULL; cipher = malloc(sizeof(struct ssh_cipher_struct)); @@ -175,7 +178,15 @@ void crypto_free(struct ssh_crypto_struct *crypto) SAFE_FREE(crypto->ecdh_server_pubkey); if(crypto->ecdh_privkey != NULL){ #ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 EC_KEY_free(crypto->ecdh_privkey); +#else + EVP_PKEY_free(crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_release(crypto->ecdh_privkey); #endif @@ -236,7 +247,7 @@ static int crypt_set_algorithms2(ssh_session session) const char *wanted = NULL; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); - size_t i = 0; + uint8_t i = 0; int cmp; /* @@ -385,7 +396,7 @@ int crypt_set_algorithms_client(ssh_session session) #ifdef WITH_SERVER int crypt_set_algorithms_server(ssh_session session){ const char *method = NULL; - size_t i = 0; + uint8_t i = 0; struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); int cmp; diff --git a/libssh/tests/CMakeLists.txt b/libssh/tests/CMakeLists.txt index 75ff9b8..22a36f3 100644 --- a/libssh/tests/CMakeLists.txt +++ b/libssh/tests/CMakeLists.txt @@ -12,6 +12,7 @@ include_directories(${OPENSSL_INCLUDE_DIR} ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ${libssh_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/tests) @@ -32,6 +33,46 @@ target_compile_options(${TORTURE_LIBRARY} PRIVATE -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" ) +# The shared version of the library is only useful when client testing is +# enabled +if (CLIENT_TESTING) + # create shared test library + set(TORTURE_SHARED_LIBRARY torture_shared) + + # Create a list of symbols that should be wrapped for override test + set(WRAP_SYMBOLS "") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=chacha_keysetup" + "-Wl,--wrap=chacha_ivsetup" + "-Wl,--wrap=chacha_encrypt_bytes") + list(APPEND WRAP_SYMBOLS "-Wl,--wrap=poly1305_auth") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_sign_ed25519_keypair" + "-Wl,--wrap=crypto_sign_ed25519" + "-Wl,--wrap=crypto_sign_ed25519_open") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_scalarmult_base" + "-Wl,--wrap=crypto_scalarmult") + + add_library(${TORTURE_SHARED_LIBRARY} + SHARED + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_cmocka.c + ) + target_link_libraries(${TORTURE_SHARED_LIBRARY} + ${CMOCKA_LIBRARY} + ssh::static + ${WRAP_SYMBOLS} + ) + target_compile_options(${TORTURE_SHARED_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" + -DTORTURE_SHARED + ) +endif () + if (ARGP_LIBRARY) target_link_libraries(${TORTURE_LIBRARY} ${ARGP_LIBRARY} @@ -128,14 +169,21 @@ if (CLIENT_TESTING OR SERVER_TESTING) if (NOT SSHD_EXECUTABLE) message(SEND_ERROR "Could not find sshd which is required for client testing") endif() - find_program(NC_EXECUTABLE + find_program(NCAT_EXECUTABLE NAME - nc + ncat PATHS /bin /usr/bin /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) + endif (WITH_PKCS11_URI) + find_program(SSH_EXECUTABLE NAMES ssh) if (SSH_EXECUTABLE) execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) @@ -153,6 +201,17 @@ if (CLIENT_TESTING OR SERVER_TESTING) execute_process(COMMAND ${ID_EXECUTABLE} -u OUTPUT_VARIABLE LOCAL_UID OUTPUT_STRIP_TRAILING_WHITESPACE) endif() + find_program(TIMEOUT_EXECUTABLE + NAME + timeout + PATHS + /bin + /usr/bin + /usr/local/bin) + if (TIMEOUT_EXECUTABLE) + set(WITH_TIMEOUT "1") + endif() + # chroot_wrapper add_library(chroot_wrapper SHARED chroot_wrapper.c) set(CHROOT_WRAPPER_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chroot_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) @@ -164,7 +223,7 @@ if (CLIENT_TESTING OR SERVER_TESTING) # ssh_ping add_executable(ssh_ping ssh_ping.c) target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) - target_link_libraries(ssh_ping ssh::ssh) + target_link_libraries(ssh_ping ssh::static) # homedir will be used in passwd set(HOMEDIR ${CMAKE_CURRENT_BINARY_DIR}/home) @@ -186,10 +245,15 @@ if (CLIENT_TESTING OR SERVER_TESTING) list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_SHADOW=${CMAKE_CURRENT_BINARY_DIR}/etc/shadow) list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_GROUP=${CMAKE_CURRENT_BINARY_DIR}/etc/group) list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d) + list(APPEND TORTURE_ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/suppressions/lsan.supp) + list(APPEND TORTURE_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) # Give bob some keys file(COPY keys/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Same as id_rsa, protected with passphrase "secret" + file(COPY keys/id_rsa_protected DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa_protected.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ecdsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ecdsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/id_ed25519 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) @@ -197,6 +261,7 @@ if (CLIENT_TESTING OR SERVER_TESTING) # Allow to auth with bob's public keys on alice account configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) + # append ECDSA public key file(READ keys/id_ecdsa.pub CONTENTS) file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") @@ -205,11 +270,27 @@ 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}") + # 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) + + # append ECDSA public key + file(READ keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + # Copy the signed key to an alternative directory in bob's homedir. file(COPY keys/certauth/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/certauth/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/certauth/id_rsa-cert.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + #Copy the 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) + message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") endif () @@ -221,6 +302,9 @@ endif () if (CLIENT_TESTING) add_subdirectory(client) + + # Only add override testing if testing the client + add_subdirectory(external_override) endif () if (WITH_SERVER AND SERVER_TESTING) diff --git a/libssh/tests/authentication.c b/libssh/tests/authentication.c deleted file mode 100644 index 248b646..0000000 --- a/libssh/tests/authentication.c +++ /dev/null @@ -1,74 +0,0 @@ -/* -This file is distributed in public domain. You can do whatever you want -with its content. -*/ - - -#include -#include -#include -#include - -#include - -#include "tests.h" -static int auth_kbdint(SSH_SESSION *session){ - int err=ssh_userauth_kbdint(session,NULL,NULL); - char *name,*instruction,*prompt,*ptr; - char buffer[128]; - int i,n; - char echo; - while (err==SSH_AUTH_INFO){ - name=ssh_userauth_kbdint_getname(session); - instruction=ssh_userauth_kbdint_getinstruction(session); - n=ssh_userauth_kbdint_getnprompts(session); - if(strlen(name)>0) - printf("%s\n",name); - if(strlen(instruction)>0) - printf("%s\n",instruction); - for(i=0;issh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_try_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + static void torture_auth_autopubkey(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; @@ -280,6 +375,96 @@ static void torture_auth_autopubkey(void **state) { assert_int_equal(rc, SSH_AUTH_SUCCESS); } +struct torture_auth_autopubkey_protected_data { + ssh_session session; + int n_calls; +}; + +static int +torture_auth_autopubkey_protected_auth_function (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) +{ + int rc; + char *id, *expected_id; + struct torture_auth_autopubkey_protected_data *data = userdata; + + assert_true(prompt != NULL); + assert_int_equal(echo, 0); + assert_int_equal(verify, 0); + + expected_id = ssh_path_expand_escape(data->session, "%d/id_rsa_protected"); + assert_true(expected_id != NULL); + + rc = ssh_userauth_publickey_auto_get_current_identity(data->session, &id); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(expected_id, id); + + ssh_string_free_char(id); + ssh_string_free_char(expected_id); + + data->n_calls += 1; + strncpy(buf, "secret", len); + return 0; +} + +static void torture_auth_autopubkey_protected(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *id; + int rc; + + struct torture_auth_autopubkey_protected_data data = { + .session = session, + .n_calls = 0 + }; + + struct ssh_callbacks_struct callbacks = { + .userdata = &data, + .auth_function = torture_auth_autopubkey_protected_auth_function + }; + + /* no session pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(NULL, &id); + assert_int_equal(rc, SSH_ERROR); + + /* no result pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* no auto auth going on */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, &id); + assert_int_equal(rc, SSH_ERROR); + + ssh_callbacks_init(&callbacks); + ssh_set_callbacks(session, &callbacks); + + /* 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); + + /* Try id_rsa_protected first. + */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/id_rsa_protected"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + assert_int_equal (data.n_calls, 1); +} + static void torture_auth_autopubkey_nonblocking(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; @@ -467,7 +652,7 @@ static void torture_auth_agent(void **state) { assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); rc = ssh_userauth_agent(session, NULL); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_ssh_return_code(session, rc); } static void torture_auth_agent_nonblocking(void **state) { @@ -498,7 +683,7 @@ static void torture_auth_agent_nonblocking(void **state) { do { rc = ssh_userauth_agent(session, NULL); } while (rc == SSH_AUTH_AGAIN); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_ssh_return_code(session, rc); } static void torture_auth_cert(void **state) { @@ -540,7 +725,7 @@ static void torture_auth_cert(void **state) { assert_int_equal(rc, SSH_OK); rc = ssh_userauth_try_publickey(session, NULL, cert); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_ssh_return_code(session, rc); rc = ssh_userauth_publickey(session, NULL, privkey); assert_int_equal(rc, SSH_AUTH_SUCCESS); @@ -551,6 +736,7 @@ static void torture_auth_cert(void **state) { static void torture_auth_agent_cert(void **state) { +#if OPENSSH_VERSION_MAJOR < 8 struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -570,6 +756,7 @@ static void torture_auth_agent_cert(void **state) "ssh-rsa-cert-v01@openssh.com"); assert_int_equal(rc, SSH_OK); } +#endif /* OPENSSH_VERSION_MAJOR < 8 */ /* Setup loads a different key, tests are exactly the same. */ torture_auth_agent(state); @@ -577,6 +764,7 @@ static void torture_auth_agent_cert(void **state) static void torture_auth_agent_cert_nonblocking(void **state) { +#if OPENSSH_VERSION_MAJOR < 8 struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -596,6 +784,7 @@ static void torture_auth_agent_cert_nonblocking(void **state) "ssh-rsa-cert-v01@openssh.com"); assert_int_equal(rc, SSH_OK); } +#endif /* OPENSSH_VERSION_MAJOR < 8 */ torture_auth_agent_nonblocking(state); } @@ -891,6 +1080,124 @@ static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) } while (rc == SSH_AUTH_AGAIN); assert_int_equal(rc, SSH_AUTH_SUCCESS); + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); } int torture_run_tests(void) { @@ -914,9 +1221,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_nonblocking, + pubkey_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_auth_autopubkey, pubkey_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_protected, + pubkey_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_auth_autopubkey_nonblocking, pubkey_setup, session_teardown), @@ -953,6 +1269,12 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, pubkey_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking, + pubkey_setup, + session_teardown), }; ssh_init(); diff --git a/libssh/tests/client/torture_auth_pkcs11.c b/libssh/tests/client/torture_auth_pkcs11.c new file mode 100644 index 0000000..ee97bff --- /dev/null +++ b/libssh/tests/client/torture_auth_pkcs11.c @@ -0,0 +1,257 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +/* agent_is_running */ +#include "agent.c" + +#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 SOFTHSM_CONF "softhsm.conf" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *temp_dir; + char *orig_dir; + char *keys_dir; +}; + +static int setup_tokens(void **state, const char *type, const char *obj_name) +{ + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + char priv_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + 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; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} +static int setup_session(void **state) +{ + struct torture_state *s = *state; + struct pki_st *test_state = NULL; + int rc; + char conf_path[1024] = {0}; + char keys_dir[1024] = {0}; + char *temp_dir; + + 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()); + 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); + + test_state->temp_dir = strdup(torture_get_current_working_dir()); + assert_non_null(test_state->temp_dir); + + snprintf(keys_dir, sizeof(keys_dir), "%s/tests/keys/pkcs11/", SOURCEDIR); + + test_state->keys_dir = strdup(keys_dir); + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", test_state->temp_dir); + setenv("SOFTHSM2_CONF", conf_path, 1); + + setup_tokens(state, LIBSSH_RSA_TESTKEY, "rsa"); + setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); + setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); + setup_tokens(state, LIBSSH_ECDSA_521_TESTKEY, "ecdsa521"); + + return 0; +} + +static int sshd_setup(void **state) +{ + + torture_setup_sshd_server(state, true); + setup_session(state); + + return 0; +} + +static int sshd_teardown(void **state) { + + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + int rc; + + unsetenv("SOFTHSM2_CONF"); + + 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->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) { + 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); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity->root->data, priv_uri); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_autopubkey_rsa(void **state) { + torture_auth_autopubkey(state, "rsa", "1234"); +} + +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) { + torture_auth_autopubkey(state, "ecdsa384", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_521(void **state) { + torture_auth_autopubkey(state, "ecdsa521", "1234"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_rsa, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_521, + session_setup, + session_teardown), + }; + + ssh_session session = ssh_new(); + int verbosity = SSH_LOG_FUNCTIONS; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/client/torture_forward.c b/libssh/tests/client/torture_forward.c index dcbdcbd..18b8c85 100644 --- a/libssh/tests/client/torture_forward.c +++ b/libssh/tests/client/torture_forward.c @@ -82,6 +82,8 @@ static void torture_ssh_forward(void **state) ssh_channel c; int dport; int bound_port; + char *originator_host = NULL; + int originator_port; int rc; int verbosity = SSH_LOG_TRACE; @@ -90,7 +92,7 @@ static void torture_ssh_forward(void **state) rc = ssh_channel_listen_forward(session, "127.0.0.21", 8080, &bound_port); assert_ssh_return_code(session, rc); - c = ssh_channel_accept_forward(session, 10, &dport); + c = ssh_channel_open_forward_port(session, 10, &dport, &originator_host, &originator_port); /* We do not get a listener and run into the timeout here */ assert_null(c); diff --git a/libssh/tests/client/torture_proxycommand.c b/libssh/tests/client/torture_proxycommand.c index c04ff2a..7812a95 100644 --- a/libssh/tests/client/torture_proxycommand.c +++ b/libssh/tests/client/torture_proxycommand.c @@ -59,7 +59,7 @@ static int session_teardown(void **state) return 0; } -#ifdef NC_EXECUTABLE +#ifdef NCAT_EXECUTABLE static void torture_options_set_proxycommand(void **state) { struct torture_state *s = *state; @@ -71,13 +71,18 @@ static void torture_options_set_proxycommand(void **state) int rc; socket_t fd; - rc = stat(NC_EXECUTABLE, &sb); + rc = stat(NCAT_EXECUTABLE, &sb); if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { - SSH_LOG(SSH_LOG_WARNING, "Could not find " NC_EXECUTABLE ": Skipping the test"); + SSH_LOG(SSH_LOG_WARNING, + "Could not find " NCAT_EXECUTABLE ": Skipping the test"); skip(); } - rc = snprintf(command, sizeof(command), NC_EXECUTABLE " %s %d", address, port); + rc = snprintf(command, + sizeof(command), + NCAT_EXECUTABLE " %s %d", + address, + port); assert_true((size_t)rc < sizeof(command)); rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); @@ -90,7 +95,7 @@ static void torture_options_set_proxycommand(void **state) assert_int_equal(rc & O_RDWR, O_RDWR); } -#else /* NC_EXECUTABLE */ +#else /* NCAT_EXECUTABLE */ static void torture_options_set_proxycommand(void **state) { @@ -98,7 +103,7 @@ static void torture_options_set_proxycommand(void **state) skip(); } -#endif /* NC_EXECUTABLE */ +#endif /* NCAT_EXECUTABLE */ static void torture_options_set_proxycommand_notexist(void **state) { struct torture_state *s = *state; diff --git a/libssh/tests/client/torture_rekey.c b/libssh/tests/client/torture_rekey.c index 46a015d..ce31325 100644 --- a/libssh/tests/client/torture_rekey.c +++ b/libssh/tests/client/torture_rekey.c @@ -38,6 +38,8 @@ #include #include +#define KEX_RETRY 32 + static uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ static int sshd_setup(void **state) @@ -268,6 +270,7 @@ static void torture_rekey_recv(void **state) int fd; sftp_file file; mode_t mask; + int rc; /* The blocks limit is set correctly */ c = s->ssh.session->current_crypto; @@ -301,6 +304,8 @@ static void torture_rekey_recv(void **state) assert_int_equal(byteswritten, bytesread); } + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); close(fd); /* The rekey limit was restored in the new crypto to the same value */ @@ -496,9 +501,15 @@ static void torture_rekey_different_kex(void **state) * to make sure the rekey it completes with all different ciphers (paddings */ memset(data, 0, sizeof(data)); memset(data, 'A', 128); - for (i = 0; i < 20; i++) { + for (i = 0; i < KEX_RETRY; i++) { ssh_send_ignore(s->ssh.session, data); - ssh_handle_packets(s->ssh.session, 50); + ssh_handle_packets(s->ssh.session, 100); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } } /* The rekey limit was restored in the new crypto to the same value */ @@ -568,9 +579,15 @@ static void torture_rekey_server_different_kex(void **state) * to make sure the rekey it completes with all different ciphers (paddings */ memset(data, 0, sizeof(data)); memset(data, 'A', 128); - for (i = 0; i < 25; i++) { + for (i = 0; i < KEX_RETRY; i++) { ssh_send_ignore(s->ssh.session, data); - ssh_handle_packets(s->ssh.session, 50); + ssh_handle_packets(s->ssh.session, 100); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } } /* Check that the secret hash is different than initially */ @@ -611,6 +628,7 @@ static void torture_rekey_server_recv(void **state) int fd; sftp_file file; mode_t mask; + int rc; /* Copy the initial secret hash = session_id so we know we changed keys later */ c = s->ssh.session->current_crypto; @@ -638,6 +656,8 @@ static void torture_rekey_server_recv(void **state) assert_int_equal(byteswritten, bytesread); } + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); close(fd); /* Check that the secret hash is different than initially */ diff --git a/libssh/tests/client/torture_scp.c b/libssh/tests/client/torture_scp.c index 59a00ba..fe3f239 100644 --- a/libssh/tests/client/torture_scp.c +++ b/libssh/tests/client/torture_scp.c @@ -39,6 +39,9 @@ #define TEMPLATE BINARYDIR "/tests/home/alice/temp_dir_XXXXXX" #define ALICE_HOME BINARYDIR "/tests/home/alice" +/* store the original umask */ +mode_t old; + struct scp_st { struct torture_state *s; char *tmp_dir; @@ -99,6 +102,9 @@ static int session_setup(void **state) s = ts->s; + /* store the original umask and set a new one */ + old = umask(0022); + /* Create temporary directory for alice */ tmp_dir = torture_make_temp_dir(TEMPLATE); assert_non_null(tmp_dir); @@ -135,6 +141,9 @@ static int session_teardown(void **state) assert_non_null(ts->s); s = ts->s; + /* restore the umask */ + umask(old); + ssh_disconnect(s->ssh.session); ssh_free(s->ssh.session); diff --git a/libssh/tests/client/torture_session.c b/libssh/tests/client/torture_session.c index b5ed7a6..27e8fc8 100644 --- a/libssh/tests/client/torture_session.c +++ b/libssh/tests/client/torture_session.c @@ -118,18 +118,316 @@ static void torture_channel_read_error(void **state) { ssh_channel_free(channel); } +static void torture_channel_poll_timeout_valid(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, strlen("ABCD")); +} + +static void torture_channel_poll_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_OK); + + /* send crap and for server to send us a disconnect */ + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_ERROR); + + ssh_channel_free(channel); +} + +/* + * Check that the client can properly handle the error returned from the server + * when the maximum number of sessions is exceeded. + * + * Related: T75, T239 + * + */ +static void torture_max_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char max_session_config[32] = {0}; +#define MAX_CHANNELS 10 + ssh_channel channels[MAX_CHANNELS + 1]; + size_t i; + int rc; + + snprintf(max_session_config, + sizeof(max_session_config), + "MaxSessions %u", + MAX_CHANNELS); + + /* Update server configuration to limit number of sessions */ + torture_update_sshd_config(state, max_session_config); + + /* Open the maximum number of channel sessions */ + for (i = 0; i < MAX_CHANNELS; i++) { + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_ssh_return_code(session, rc); + } + + /* Try to open an extra session and expect failure */ + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[i]); + + /* Close and free channels */ + for (i = 0; i < MAX_CHANNELS; i++) { + ssh_channel_close(channels[i]); + ssh_channel_free(channels[i]); + } +#undef MAX_CHANNELS +} + +static void torture_channel_delayed_close(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + + int rc; + int fd; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_EOF); + + ssh_channel_free(channel); + +} + +/* Ensure that calling 'ssh_channel_poll' on a freed channel does not lead to + * segmentation faults. */ +static void torture_freed_channel_poll(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_poll(channel, 0); + assert_int_equal(rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_poll_timeout' on a freed channel does not + * lead to segmentation faults. */ +static void torture_freed_channel_poll_timeout(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does + * not lead to segmentation faults. */ +static void torture_freed_channel_read_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_read_nonblocking(channel, buff, 256, 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_get_exit_status' on a freed channel does not + * lead to segmentation faults. */ +static void torture_freed_channel_get_exit_status(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_get_exit_status(channel); + 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_channel_read_error, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout_valid, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_max_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_delayed_close, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll_timeout, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_get_exit_status, + session_setup, + session_teardown), }; ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); return rc; diff --git a/libssh/tests/client/torture_sftp_read.c b/libssh/tests/client/torture_sftp_read.c index 687153e..c6ec4b9 100644 --- a/libssh/tests/client/torture_sftp_read.c +++ b/libssh/tests/client/torture_sftp_read.c @@ -101,7 +101,7 @@ int torture_run_tests(void) { struct CMUnitTest tests[] = { /* This test is intentionally running twice to trigger a bug in OpenSSH * or in pam_wrapper, causing the second invocation to fail. - * See: https://bugs.libssh.org/T122 + * See: https://gitlab.com/libssh/libssh-mirror/-/issues/23 */ cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, session_setup, diff --git a/libssh/tests/connection.c b/libssh/tests/connection.c deleted file mode 100644 index 889c511..0000000 --- a/libssh/tests/connection.c +++ /dev/null @@ -1,31 +0,0 @@ -/* -This file is distributed in public domain. You can do whatever you want -with its content. -*/ - -#include -#include -#include "tests.h" -SSH_OPTIONS *set_opts(int argc, char **argv){ - SSH_OPTIONS *options=ssh_options_new(); - char *host=NULL; - if(ssh_options_getopt(options,&argc, argv)){ - fprintf(stderr,"error parsing command line :%s\n",ssh_get_error(options)); - return NULL; - } - int i; - while((i=getopt(argc,argv,""))!=-1){ - switch(i){ - default: - fprintf(stderr,"unknown option %c\n",optopt); - } - } - if(optind < argc) - host=argv[optind++]; - if(host==NULL){ - fprintf(stderr,"must provide an host name\n"); - return NULL; - } - ssh_options_set_host(options,host); - return options; -} diff --git a/libssh/tests/etc/pam_matrix_passdb.in b/libssh/tests/etc/pam_matrix_passdb.in index 8891fcf..c0aa54e 100644 --- a/libssh/tests/etc/pam_matrix_passdb.in +++ b/libssh/tests/etc/pam_matrix_passdb.in @@ -1,2 +1,3 @@ bob:secret:sshd alice:secret:sshd +charlie:secret:sshd diff --git a/libssh/tests/etc/passwd.in b/libssh/tests/etc/passwd.in index a3ddc97..85e20c6 100644 --- a/libssh/tests/etc/passwd.in +++ b/libssh/tests/etc/passwd.in @@ -1,5 +1,6 @@ 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 sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false root:x:65534:65532:root gecos:@HOMEDIR@:/bin/false diff --git a/libssh/tests/etc/shadow.in b/libssh/tests/etc/shadow.in index 5c0e3d8..a0b2b9d 100644 --- a/libssh/tests/etc/shadow.in +++ b/libssh/tests/etc/shadow.in @@ -1,2 +1,3 @@ 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::::: diff --git a/libssh/tests/external_override/CMakeLists.txt b/libssh/tests/external_override/CMakeLists.txt new file mode 100644 index 0000000..81d10c5 --- /dev/null +++ b/libssh/tests/external_override/CMakeLists.txt @@ -0,0 +1,165 @@ +project(external-override C) + +include_directories(${CMAKE_SOURCE_DIR}/include) + +set(LIBSSH_OVERRIDE_TESTS + torture_override +) + +# chacha20_override +add_library(chacha20_override SHARED + chacha20_override.c + ${libssh_SOURCE_DIR}/src/external/chacha.c + ) +set(CHACHA20_OVERRIDE_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chacha20_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# poly1305_override +add_library(poly1305_override SHARED + poly1305_override.c + ${libssh_SOURCE_DIR}/src/external/poly1305.c + ) +set(POLY1305_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}poly1305_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +if (WITH_GCRYPT) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_gcrypt.c + ${libssh_SOURCE_DIR}/src/md_gcrypt.c + ) + set(override_libs + ${GCRYPT_LIBRARIES} + ) +elseif (WITH_MBEDTLS) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_mbedcrypto.c + ${libssh_SOURCE_DIR}/src/md_mbedcrypto.c + ) + set(override_libs + ${MBEDTLS_CRYPTO_LIBRARY} + ) +else () + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_crypto.c + ${libssh_SOURCE_DIR}/src/md_crypto.c + ) + set(override_libs + ${OPENSSL_CRYPTO_LIBRARIES} + ) +endif (WITH_GCRYPT) + +# ed25519_override +add_library(ed25519_override SHARED + ed25519_override.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} + ) +target_link_libraries(ed25519_override + PRIVATE ${override_libs}) +set(ED25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}ed25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# curve25519_override +add_library(curve25519_override SHARED + curve25519_override.c + ${libssh_SOURCE_DIR}/src/external/curve25519_ref.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} +) +target_link_libraries(curve25519_override + PRIVATE ${override_libs}) +set(CURVE25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}curve25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +set(OVERRIDE_LIBRARIES + ${CHACHA20_OVERRIDE_LIBRARY}:${POLY1305_OVERRIDE_LIBRARY}:${ED25519_OVERRIDE_LIBRARY}:${CURVE25519_OVERRIDE_LIBRARY} +) + +if (WITH_MBEDTLS) + if (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=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 () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") +else () + if (HAVE_OPENSSL_EVP_CHACHA20 AND HAVE_OPENSSL_EVP_POLY1305) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + + if (HAVE_OPENSSL_ED25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + endif () + + if (HAVE_OPENSSL_X25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif () +endif () + +if (NOT OSX) + # Remove any preload string from the environment variables list + foreach(env_string ${TORTURE_ENVIRONMENT}) + if (${env_string} MATCHES "^LD_PRELOAD=*") + list(REMOVE_ITEM TORTURE_ENVIRONMENT ${env_string}) + set(PRELOAD_STRING "${env_string}:") + endif () + endforeach () + + if ("${PRELOAD_STRING}" STREQUAL "") + set(PRELOAD_STRING "LD_PRELOAD=") + endif () + + list(APPEND TORTURE_ENVIRONMENT + "${PRELOAD_STRING}${OVERRIDE_LIBRARIES}") +endif() + +foreach(_OVERRIDE_TEST ${LIBSSH_OVERRIDE_TESTS}) + add_cmocka_test(${_OVERRIDE_TEST} + SOURCES ${_OVERRIDE_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + ${OVERRIDE_RESULTS} + LINK_LIBRARIES + ${TORTURE_SHARED_LIBRARY} + chacha20_override + poly1305_override + ed25519_override + curve25519_override + ) + + if (OSX) + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${OVERRIDE_LIBRARIES}) + + else () + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + + endif() +endforeach() diff --git a/libssh/tests/external_override/chacha20_override.c b/libssh/tests/external_override/chacha20_override.c new file mode 100644 index 0000000..7e166e7 --- /dev/null +++ b/libssh/tests/external_override/chacha20_override.c @@ -0,0 +1,80 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "chacha20_override.h" + +static bool internal_function_called = false; + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_keysetup(x, k, kbits); +} + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_ivsetup(x, iv, ctr); +} + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_encrypt_bytes(x, m, c, bytes); +} + +bool internal_chacha20_function_called(void) +{ + return internal_function_called; +} + +void reset_chacha20_function_called(void) +{ + internal_function_called = false; +} diff --git a/libssh/tests/external_override/chacha20_override.h b/libssh/tests/external_override/chacha20_override.h new file mode 100644 index 0000000..58f8f21 --- /dev/null +++ b/libssh/tests/external_override/chacha20_override.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/chacha.h" + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +; + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +; + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +; + +bool internal_chacha20_function_called(void); +void reset_chacha20_function_called(void); diff --git a/libssh/tests/external_override/curve25519_override.c b/libssh/tests/external_override/curve25519_override.c new file mode 100644 index 0000000..983d57c --- /dev/null +++ b/libssh/tests/external_override/curve25519_override.c @@ -0,0 +1,58 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "curve25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult_base(q, n); +} + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult(q, n, p); +} + +bool internal_curve25519_function_called(void) +{ + return internal_function_called; +} + +void reset_curve25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/libssh/tests/external_override/curve25519_override.h b/libssh/tests/external_override/curve25519_override.h new file mode 100644 index 0000000..634c1c4 --- /dev/null +++ b/libssh/tests/external_override/curve25519_override.h @@ -0,0 +1,31 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/curve25519.h" + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n); + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p); + +bool internal_curve25519_function_called(void); +void reset_curve25519_function_called(void); diff --git a/libssh/tests/external_override/ed25519_override.c b/libssh/tests/external_override/ed25519_override.c new file mode 100644 index 0000000..439a5da --- /dev/null +++ b/libssh/tests/external_override/ed25519_override.c @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ed25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_keypair(pk, sk); +} + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519(sm, smlen, m, mlen, sk); +} + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_open(m, mlen, sm, smlen, pk); +} + +bool internal_ed25519_function_called(void) +{ + return internal_function_called; +} + +void reset_ed25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/libssh/tests/external_override/ed25519_override.h b/libssh/tests/external_override/ed25519_override.h new file mode 100644 index 0000000..0abe2e2 --- /dev/null +++ b/libssh/tests/external_override/ed25519_override.h @@ -0,0 +1,39 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/ed25519.h" + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk); + +bool internal_ed25519_function_called(void); +void reset_ed25519_function_called(void); diff --git a/libssh/tests/external_override/poly1305_override.c b/libssh/tests/external_override/poly1305_override.c new file mode 100644 index 0000000..4d78272 --- /dev/null +++ b/libssh/tests/external_override/poly1305_override.c @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +static bool internal_function_called = false; + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + poly1305_auth(out, m, inlen, key); +} + +bool internal_poly1305_function_called(void) +{ + return internal_function_called; +} + +void reset_poly1305_function_called(void) +{ + internal_function_called = false; +} diff --git a/libssh/tests/external_override/poly1305_override.h b/libssh/tests/external_override/poly1305_override.h new file mode 100644 index 0000000..8771780 --- /dev/null +++ b/libssh/tests/external_override/poly1305_override.h @@ -0,0 +1,35 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/poly1305.h" + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +; + +bool internal_poly1305_function_called(void); +void reset_poly1305_function_called(void); diff --git a/libssh/tests/external_override/torture_override.c b/libssh/tests/external_override/torture_override.c new file mode 100644 index 0000000..f351d52 --- /dev/null +++ b/libssh/tests/external_override/torture_override.c @@ -0,0 +1,320 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "chacha20_override.h" +#include "poly1305_override.h" +#include "curve25519_override.h" +#include "ed25519_override.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_st { + char *temp_dir; + char *orig_dir; +}; + +static int sshd_setup(void **state) +{ + struct torture_state *s; + struct test_st *test_state = NULL; + char *temp_dir; + int rc; + + torture_setup_sshd_server(state, false); + + test_state = malloc(sizeof(struct test_st)); + assert_non_null(test_state); + + s = *((struct torture_state **)state); + s->private_data = test_state; + + test_state->orig_dir = strdup(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); + + test_state->temp_dir = temp_dir; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct torture_state *s = *state; + struct test_st *test_state = s->private_data; + int rc; + + 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); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + reset_chacha20_function_called(); + reset_poly1305_function_called(); + reset_curve25519_function_called(); + reset_ed25519_function_called(); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hostkey) +{ + char data[256]; + int rc; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hostkey != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 'A', sizeof(data)); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM +static void torture_override_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + bool internal_chacha20_called; + bool internal_poly1305_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + "chacha20-poly1305@openssh.com", + NULL /* hostkey */); + + internal_chacha20_called = internal_chacha20_function_called(); + internal_poly1305_called = internal_poly1305_function_called(); + +#if SHOULD_CALL_INTERNAL_CHACHAPOLY + assert_true(internal_chacha20_called || + internal_poly1305_called); +#else + assert_false(internal_chacha20_called || + internal_poly1305_called); +#endif + +} +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + +#ifdef OPENSSH_CURVE25519_SHA256 +static void torture_override_ecdh_curve25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256 */ + +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG +static void torture_override_ecdh_curve25519_sha256_libssh_org(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256@libssh.org", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ + +#ifdef OPENSSH_SSH_ED25519 +static void torture_override_ed25519(void **state) +{ + struct torture_state *s = *state; + bool internal_ed25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + NULL, /* cipher */ + "ssh-ed25519"); + + internal_ed25519_called = internal_ed25519_function_called(); + +#if SHOULD_CALL_INTERNAL_ED25519 + assert_true(internal_ed25519_called); +#else + assert_false(internal_ed25519_called); +#endif +} +#endif /* OPENSSH_SSH_ED25519 */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_chacha20_poly1305, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ +#ifdef OPENSSH_CURVE25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256 */ +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SSH_ED25519 + cmocka_unit_test_setup_teardown(torture_override_ed25519, + session_setup, + session_teardown), +#endif /* OPENSSH_SSH_ED25519 */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + if (rc != 0) { + return rc; + } + + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/fuzz/CMakeLists.txt b/libssh/tests/fuzz/CMakeLists.txt index 5c1e63b..8d9b2be 100644 --- a/libssh/tests/fuzz/CMakeLists.txt +++ b/libssh/tests/fuzz/CMakeLists.txt @@ -1,9 +1,34 @@ project(fuzzing CXX) -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - add_executable(ssh_server_fuzzer ssh_server_fuzzer.cpp) - set_target_properties(ssh_server_fuzzer - PROPERTIES - COMPILE_FLAGS "-fsanitize=fuzzer" - LINK_FLAGS "-fsanitize=fuzzer") -endif() +macro(fuzzer name) + add_executable(${name} ${name}.c) + target_link_libraries(${name} + PRIVATE + ssh::static) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "-fsanitize=fuzzer" + LINK_FLAGS "-fsanitize=fuzzer") + # Run the fuzzer to make sure it works + add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + else() + target_sources(${name} PRIVATE fuzzer.c) + # Run the fuzzer to make sure it works + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus") + file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus/*") + set(i 0) + foreach(file ${files}) + add_test(${name}_${i} + ${CMAKE_CURRENT_BINARY_DIR}/${name} ${file}) + math(EXPR i "${i} + 1") + endforeach() + endif() + endif() +endmacro() + +fuzzer(ssh_client_fuzzer) +fuzzer(ssh_server_fuzzer) +fuzzer(ssh_client_config_fuzzer) +fuzzer(ssh_bind_config_fuzzer) +fuzzer(ssh_known_hosts_fuzzer) diff --git a/libssh/tests/fuzz/README.md b/libssh/tests/fuzz/README.md new file mode 100644 index 0000000..c68ef3d --- /dev/null +++ b/libssh/tests/fuzz/README.md @@ -0,0 +1,133 @@ +# Simple fuzzers for libssh + +This directory contains fuzzer programs, that are usable either in +oss-fuzz infrastructure or suitable for running fuzzing locally or +even for reproducing crashes with given trace files. + +When building with clang, fuzzers are automatically built with address +sanitizer. With gcc, they are built as they are without instrumentation, +but they are suitable for debugging. + +## Background + +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 +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. + +## Corpus creation + +For effective fuzzing, we need to provide corpus of initial (valid) inputs that +can be used for deriving other inputs. libssh already supports creation of pcap +files (packet capture), which include all the information we need for fuzzing. +This file is also created from date before encryption and after decryption so +it is in plain text as we expect it, but we still need to adjust configuration +to use none cipher for the key exchange to be plausible. + +### Creating packet capture using example libssh client + + * Compile libssh with support for none cipher and pcap: + + cmake -DWITH_INSECURE_NONE=ON -DWITH_PCAP=ON ../ + + * Create a configuration file enabling none cipher and mac: + + printf 'Ciphers none\nMACs none' > /tmp/ssh_config + + * Generate test host key: + + ./examples/keygen2 -f /tmp/hostkey -t rsa + + * Run example libssh server: + + ./examples/samplesshd-cb -f /tmp/ssh_config -k /tmp/hostkey -p 22222 127.0.0.1 + + * In other terminal, run the example libssh client with pcap enabled (use mypassword for password): + + ./examples/ssh-client -F /tmp/ssh_config -l myuser -P /tmp/ssh.pcap -p 22222 127.0.0.1 + + * Kill the server (in the first terminal, press Ctrl+C) + + * Convert the pcap file to raw traces (separate client and server messages) usable by fuzzer: + + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport==22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_server + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport!=22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_client + + * Now we should be able to "replay" the sessions in respective fuzzers, getting some more coverage: + + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_client_fuzzer /tmp/ssh_client + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_server_fuzzer /tmp/ssh_server + + (note, that the client fuzzer fails now because of invalid hostkey signature; TODO) + + * Store the appropriately named traces in the fuzers directory: + + cp /tmp/ssh_client tests/fuzz/ssh_client_fuzzer_corpus/$(sha1sum /tmp/ssh_client | cut -d ' ' -f 1) + cp /tmp/ssh_server tests/fuzz/ssh_server_fuzzer_corpus/$(sha1sum /tmp/ssh_server | cut -d ' ' -f 1) + +## Debugging issues reported by oss-fuzz + +OSS Fuzz provides helper scripts to reproduce issues locally. Even though the +fuzzing scripts can ran anywhere, the best bet for reproducing is to use +their container infrastructure. There is a +[complete documentation](https://google.github.io/oss-fuzz/advanced-topics/reproducing/) +but I will try to focus here on the workflow I use and libssh specifics. + +### Environment + +The helper scripts are written in Python and use docker to run containers +so these needs to be installed. I am using podman instead of docker for +some time, but it has some quirks that needs to be addressed in advance +and that I describe in the rejected [PR](https://github.com/google/oss-fuzz/pull/4774). +You can either pick up my branch or workaround them locally: + + * Package `podman-docker` installs symlink from `/bin/docker` to `/bin/podman` + * The directories mounted to the containers need to have `container_file_t` + SELinux labels -- this is needed for the `build` directory that is created + under the oss-fuzz repository, for testcases and for source files + * `podman` does not like combination of `--privileged` and + `--cap-add SYS_PTRACE` flags. Podman can work with non-privileged containers + so you can just remove the `--privileged` from the `infra/helper.py` + +### Reproduce locally + +Clone the above repository from https://github.com/google/oss-fuzz/, apply +changes from previous secion if needed, setup local clone of libssh repository +and build the fuzzers locally (where `~/devel/libssh` is path to local libssh +checkout): + + python infra/helper.py build_fuzzers libssh ~/devel/libssh/ + +Now, download the testcase from oss-fuzz.com (the file under `~/Downloads`) +and we are ready to reproduce the issue locally (replace the `ssh_client_fuzzer` +with the fuzzer name if the issue happens in other fuzzer): + + python infra/helper.py reproduce libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +This should give you the same error/leak/crash as you see on the testcase +detail in oss-fuzz.com. + +I find it very useful to run libssh in debug mode, to see what happened and +what exit path was taken to get to the error. Fortunatelly, we can simply +pass environment variables to the container: + + python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +### Fix the issue and verify the fix + +Now, we can properly investigate the issue and once we have a fix, we can +make changes in our local checkout and repeat the steps above (from building +fuzzers) to verify the issue is no longer present. + +### Fuzzing locally + +We can use the oss-fuzz tools even further and run the fuzzing process +locally, to verify there are no similar issues happening very close to +existing code paths and which would cause more reports very soon after +we would fix the current issue. The following command will run fuzzer +until it finds an issue or until killed: + + python infra/helper.py run_fuzzer libssh ssh_client_fuzzer diff --git a/libssh/tests/fuzz/fuzzer.c b/libssh/tests/fuzz/fuzzer.c new file mode 100644 index 0000000..4db6a2b --- /dev/null +++ b/libssh/tests/fuzz/fuzzer.c @@ -0,0 +1,39 @@ +/* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ + +#include +#include +#include + +int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); + +int +main (int argc, char **argv) +{ + FILE *f = NULL; + size_t n_read, len; + unsigned char *buf = NULL; + + if (argc < 2) { + return 1; + } + + if (LLVMFuzzerInitialize) { + LLVMFuzzerInitialize(&argc, &argv); + } + + f = fopen (argv[1], "r"); + assert (f); + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + buf = (unsigned char*) malloc (len); + n_read = fread (buf, 1, len, f); + fclose (f); + assert (n_read == len); + LLVMFuzzerTestOneInput (buf, len); + + free (buf); + printf ("Done!\n"); + return 0; +} diff --git a/libssh/tests/fuzz/ssh_bind_config_fuzzer.c b/libssh/tests/fuzz/ssh_bind_config_fuzzer.c new file mode 100644 index 0000000..3d0d8be --- /dev/null +++ b/libssh/tests/fuzz/ssh_bind_config_fuzzer.c @@ -0,0 +1,52 @@ +/* + * Copyright 2021 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 "libssh/server.h" +#include "libssh/bind_config.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_bind bind = NULL; + char *input = NULL; + + input = (char *)malloc(size + 1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + ssh_init(); + + bind = ssh_bind_new(); + assert(bind != NULL); + + ssh_bind_config_parse_string(bind, input); + + ssh_bind_free(bind); + ssh_finalize(); + + free(input); + + return 0; +} diff --git a/libssh/tests/fuzz/ssh_client_config_fuzzer.c b/libssh/tests/fuzz/ssh_client_config_fuzzer.c new file mode 100644 index 0000000..62eae93 --- /dev/null +++ b/libssh/tests/fuzz/ssh_client_config_fuzzer.c @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Stanislav Zidek + * + * 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 "libssh/options.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + char *input = NULL; + + input = (char *)malloc(size+1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + ssh_init(); + + session = ssh_new(); + assert(session != NULL); + + /* Make sure we have default options set */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + + ssh_config_parse_string(session, input); + + ssh_free(session); + ssh_finalize(); + + free(input); + + return 0; +} diff --git a/libssh/tests/fuzz/ssh_client_fuzzer.c b/libssh/tests/fuzz/ssh_client_fuzzer.c new file mode 100644 index 0000000..2e3a0da --- /dev/null +++ b/libssh/tests/fuzz/ssh_client_fuzzer.c @@ -0,0 +1,170 @@ +/* + * Copyright 2019 Andreas Schneider + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; /* unused */ + (void)echo; /* unused */ + (void)verify; /* unused */ + (void)userdata; /* unused */ + + snprintf(buf, len, "secret"); + + return 0; +} + +struct ssh_callbacks_struct cb = { + .userdata = NULL, + .auth_function = auth_callback, +}; + +static void select_loop(ssh_session session, ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, 0); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, 1); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, 2); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + ssh_event_dopoll(event, 60000); + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + const char *env = NULL; + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + int rc; + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == size); + + rc = shutdown(socket_fds[1], SHUT_WR); + assert(rc == 0); + + ssh_init(); + + session = ssh_new(); + assert(session != NULL); + + 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); + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.0.0.1"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_USER, "alice"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "none"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, "none"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "none"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "none"); + assert(rc == 0); + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &no); + assert(rc == 0); + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session, &cb); + + rc = ssh_connect(session); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + goto out; + } + + channel = ssh_channel_new(session); + assert(channel != NULL); + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_channel_request_exec(channel, "ls"); + assert(rc == SSH_OK); + + select_loop(session, channel); + +out: + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + + ssh_finalize(); + + close(socket_fds[0]); + close(socket_fds[1]); + + return 0; +} diff --git a/libssh/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a b/libssh/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a new file mode 100644 index 0000000000000000000000000000000000000000..2667602969d625ccc92e0d300379dcf28021c8fb GIT binary patch literal 2055 zcmWFz_RuxbGtkY+Oe!wUh&Rx)(6cmP;9_84oWd@`Zr!$3Zjx(mrSFV&bAPQ^4iuG5 zE-flcH8M3dwA3xmNHj7v)4>pNfEcEiUzDzsnw*jWR;ZhoSzJ;8QjaENY+-^fWNKun zlaiU1mYJ%Xk(!f}o0zAYUX)*2U}ym{%GA&ZyQ~>LS>4o%V|oW zTihl@Z9BF2{1YQVmL&$K7B2lP+|cb48hGLSgS`c3Oy8XC{xgSHAy;Qw<1d>af2IR+ zFF!y1xunbO9*<&tft<(I1tnkCT;8zxP1$5qOP71!z4q^2C&nf2n88$dYz8okFer#I zrd;7UUfVOR-~h|LASVflrqsVPgf|+@R1Esa<*%Ru%3MHoJn+N;OesLI2A31zYvf0iuq}vA*mfcP9 zb(?doUeH-+;*s6#^=kiPS9|;D-prE{5<0TE<@D)B!P9e8_6B9YQ-9ijZ1G`}bw9Zt zX!}ZwM4UKSIzi7MOlybp;iD>gODrRb{1rc0Puk>J^m2~4_+_z`x2EaN%l5jHGjHdn z|3z1s6pI#Z)YlY${6}1VOGS+3R`vrXi$$7E#NHkBX+Jf?WEGRZV{Yl2OD|6H{Ju_O z)2_q2G&f5n=9ZLg`Y9Z?Yr>Ds#xwVZCa=n5t5P$VQg^bazC_62?Y=qt;|wZ`dz96B zFEa)0`Y5Z$Fnbxh;a)`(#VrP6!OY7o&2<0th}4Q5{khb?#YFYLWyr?V;!lg@F1fpE zhTS*o>09|AN3l&i<$v#W54(k1Laxjgzs&vByxR2Niv@QW|1V{Daj>uM*qi#2;~iUt zj?EBv`SP`9dhn#3Z(X}g7z{X@GVCVJ`FcG4dgQU|EnqDVXj#uPSoARm~^ z82F1*fn{KkZgFP1ZemV)F;LVIloeA`Amw6mYEg-9nSr50enDy;P(m*`KNnQQ!3qX& zNeC?Cbcs?3EFXze2rOc-D@0Cb1XSQO1Id5byn-PHE!9!H3@ZGQ-Gbl%WoFObmtDsT z3|$#6HXz9Zval2w(21ob8CxGR+&#bcx$wD%Kp7QQV<0J!UR<15kf~dqS(2ffo0+Ur zkXT$?o?n#0aPsN#22~=|NgK$kOd>*ED34tC1fV(|E)#?I2i~?B#-}Mb0QoXp(Lhog zf@^_P$Bw7>XE@cC|0;Yp1IXoJO9shpxv%idq94K6$#9g}mjdLAaK(b;8HBkRxpzA4 zo1yV7ET;)1&KU<52d1i=`X_v49oNp9E<+VB4tuN8?#Ow@?i#8%?}N;+?A@N4AMT-w cb4`u + * + * 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 "knownhosts.c" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *hostname = NULL; + const uint8_t *hostname_end = NULL; + size_t hostname_len = 0; + char filename[256]; + struct ssh_list *entries = NULL; + struct ssh_iterator *it = NULL; + FILE *fp = NULL; + + /* Interpret the first part of the string (until the first NULL byte) + * as a hostname we are searching for in the file */ + hostname_end = memchr(data, '\0', size); + if (hostname_end == NULL) { + return 1; + } + hostname_len = hostname_end - data + 1; + if (hostname_len > 253) { + /* This is the maximum valid length of a hostname */ + return 1; + } + hostname = malloc(hostname_len); + if (hostname == NULL) { + return 1; + } + memcpy(hostname, data, hostname_len); + + snprintf(filename, sizeof(filename), "/tmp/libfuzzer.%d", getpid()); + fp = fopen(filename, "wb"); + if (!fp) { + free(hostname); + return 1; + } + fwrite(data + hostname_len, size - hostname_len, 1, fp); + fclose(fp); + + ssh_init(); + + ssh_known_hosts_read_entries(hostname, filename, &entries); + for (it = ssh_list_get_iterator(entries); + it != NULL; + it = ssh_list_get_iterator(entries)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entries, it); + } + ssh_list_free(entries); + + ssh_finalize(); + + free(hostname); + unlink(filename); + + return 0; +} diff --git a/libssh/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 b/libssh/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 new file mode 100644 index 0000000000000000000000000000000000000000..18e779e09d32610498d48f90eb5c45600fde64df GIT binary patch literal 189 zcmY+(&k{mF0D$q%Jw-1-?H0qK!|otEgV{oZ+xjy$6k{noeRZqv_L~prPzI_d0~u&w z1%9`6_i~TZfUujXox({Q0r|9P<%*G}uFJa{ijw}M$MqYTQacm? literal 0 HcmV?d00001 diff --git a/libssh/tests/fuzz/ssh_server_fuzzer.c b/libssh/tests/fuzz/ssh_server_fuzzer.c new file mode 100644 index 0000000..8c7913a --- /dev/null +++ b/libssh/tests/fuzz/ssh_server_fuzzer.c @@ -0,0 +1,223 @@ +/* +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include +#include + +static const char kRSAPrivateKeyPEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + size_t auth_attempts; + bool authenticated; +}; + +static int auth_none(ssh_session session, const char *user, void *userdata) +{ + struct session_data_struct *sdata = + (struct session_data_struct *)userdata; + + (void)session; + (void)user; + + if (sdata->auth_attempts > 0) { + sdata->authenticated = true; + } + sdata->auth_attempts++; + + if (!sdata->authenticated) { + return SSH_AUTH_PARTIAL; + } + + return SSH_AUTH_SUCCESS; +} + +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 write_rsa_hostkey(const char *rsakey_path) +{ + FILE *fp = NULL; + size_t nwritten; + + fp = fopen(rsakey_path, "wb"); + if (fp == NULL) { + return -1; + } + + nwritten = fwrite(kRSAPrivateKeyPEM, 1, strlen(kRSAPrivateKeyPEM), fp); + fclose(fp); + + if (nwritten != strlen(kRSAPrivateKeyPEM)) { + return -1; + } + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + const char *env = NULL; + int rc; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = false, + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_none_function = auth_none, + .channel_open_request_session_function = channel_open, + }; + + /* Write SSH RSA host key to disk */ + rc = write_rsa_hostkey("/tmp/libssh_fuzzer_private_key"); + assert(rc == 0); + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == 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); + + ssh_session session = ssh_new(); + assert(session != NULL); + + + 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); + } + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_RSAKEY, + "/tmp/libssh_fuzzer_private_key"); + assert(rc == 0); + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, "none"); + assert(rc == 0); + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, "none"); + assert(rc == 0); + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, "none"); + assert(rc == 0); + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, "none"); + assert(rc == 0); + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no); + assert(rc == 0); + + ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); + + ssh_callbacks_init(&server_cb); + + rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); + assert(rc == SSH_OK); + + ssh_event event = ssh_event_new(); + assert(event != NULL); + + if (ssh_handle_key_exchange(session) == SSH_OK) { + ssh_event_add_session(event, session); + + size_t n = 0; + while(sdata.authenticated == false || sdata.channel == NULL) { + if (sdata.auth_attempts >= 3 || n >= 100) { + break; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + break; + } + + n++; + } + } + + ssh_event_free(event); + + close(socket_fds[0]); + close(socket_fds[1]); + + ssh_disconnect(session); + ssh_free(session); + ssh_bind_free(sshbind); + + return 0; +} diff --git a/libssh/tests/fuzz/ssh_server_fuzzer.cpp b/libssh/tests/fuzz/ssh_server_fuzzer.cpp deleted file mode 100644 index d6321dd..0000000 --- a/libssh/tests/fuzz/ssh_server_fuzzer.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -################################################################################ -*/ - -#include -#include -#include -#include -#include -#include - -#define LIBSSH_STATIC 1 -#include -#include - -static const char kRSAPrivateKeyPEM[] = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" - "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" - "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" - "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" - "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" - "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" - "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" - "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" - "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" - "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" - "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" - "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" - "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" - "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" - "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" - "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" - "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" - "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" - "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" - "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" - "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" - "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" - "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" - "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" - "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" - "-----END RSA PRIVATE KEY-----\n"; - - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - int socket_fds[2]; - int res = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); - assert(res >= 0); - ssize_t send_res = send(socket_fds[1], data, size, 0); - assert(send_res == size); - res = shutdown(socket_fds[1], SHUT_WR); - assert(res == 0); - - int fd = open("/tmp/libssh_fuzzer_private_key", O_WRONLY | O_CREAT, S_IRWXU); - assert(fd >= 0); - ssize_t write_res = write(fd, kRSAPrivateKeyPEM, strlen(kRSAPrivateKeyPEM)); - assert(write_res == strlen(kRSAPrivateKeyPEM)); - close(fd); - - ssh_bind sshbind = ssh_bind_new(); - ssh_session session = ssh_new(); - - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_RSAKEY, "/tmp/libssh_fuzzer_private_key"); - - res = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); - assert(res == SSH_OK); - - if (ssh_handle_key_exchange(session) == SSH_OK) { - while (true) { - ssh_message message = ssh_message_get(session); - if (!message) { - break; - } - ssh_message_free(message); - } - } - - close(socket_fds[0]); - close(socket_fds[1]); - - ssh_disconnect(session); - ssh_free(session); - ssh_bind_free(sshbind); - - return 0; -} diff --git a/libssh/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 b/libssh/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 new file mode 100644 index 0000000000000000000000000000000000000000..2667602969d625ccc92e0d300379dcf28021c8fb GIT binary patch literal 2055 zcmWFz_RuxbGtkY+Oe!wUh&Rx)(6cmP;9_84oWd@`Zr!$3Zjx(mrSFV&bAPQ^4iuG5 zE-flcH8M3dwA3xmNHj7v)4>pNfEcEiUzDzsnw*jWR;ZhoSzJ;8QjaENY+-^fWNKun zlaiU1mYJ%Xk(!f}o0zAYUX)*2U}ym{%GA&ZyQ~>LS>4o%V|oW zTihl@Z9BF2{1YQVmL&$K7B2lP+|cb48hGLSgS`c3Oy8XC{xgSHAy;Qw<1d>af2IR+ zFF!y1xunbO9*<&tft<(I1tnkCT;8zxP1$5qOP71!z4q^2C&nf2n88$dYz8okFer#I zrd;7UUfVOR-~h|LASVflrqsVPgf|+@R1Esa<*%Ru%3MHoJn+N;OesLI2A31zYvf0iuq}vA*mfcP9 zb(?doUeH-+;*s6#^=kiPS9|;D-prE{5<0TE<@D)B!P9e8_6B9YQ-9ijZ1G`}bw9Zt zX!}ZwM4UKSIzi7MOlybp;iD>gODrRb{1rc0Puk>J^m2~4_+_z`x2EaN%l5jHGjHdn z|3z1s6pI#Z)YlY${6}1VOGS+3R`vrXi$$7E#NHkBX+Jf?WEGRZV{Yl2OD|6H{Ju_O z)2_q2G&f5n=9ZLg`Y9Z?Yr>Ds#xwVZCa=n5t5P$VQg^bazC_62?Y=qt;|wZ`dz96B zFEa)0`Y5Z$Fnbxh;a)`(#VrP6!OY7o&2<0th}4Q5{khb?#YFYLWyr?V;!lg@F1fpE zhTS*o>09|AN3l&i<$v#W54(k1Laxjgzs&vByxR2Niv@QW|1V{Daj>uM*qi#2;~iUt zj?EBv`SP`9dhn#3Z(X}g7z{X@GVCVJ`FcG4dgQU|EnqDVXj#uPSoARm~^ z82F1*fn{KkZgFP1ZemV)F;LVIloeA`Amw6mYEg-9nSr50enDy;P(m*`KNnQQ!3qX& zNeC?Cbcs?3EFXze2rOc-D@0Cb1XSQO1Id5byn-PHE!9!H3@ZGQ-Gbl%WoFObmtDsT z3|$#6HXz9Zval2w(21ob8CxGR+&#bcx$wD%Kp7QQV<0J!UR<15kf~dqS(2ffo0+Ur zkXT$?o?n#0aPsN#22~=|NgK$kOd>*ED34tC1fV(|E)#?I2i~?B#-}Mb0QoXp(Lhog zf@^_P$Bw7>XE@cC|0;Yp1IXoJO9shpxv%idq94K6$#9g}mjdLAaK(b;8HBkRxpzA4 zo1yV7ET;)1&KU<52d1i=`X_v49oNp9E<+VB4tuN8?#Ow@?i#8%?}N;+?A@N4AMT-w cb4`u$TESTDIR/softhsm.conf <opts.libssh_log_level; enum pkd_hostkey_type_e type = args->type; const char *hostkeypath = args->hostkeypath; - const char *default_kex = NULL; - char *all_kex = NULL; - size_t kex_len = 0; + const char *all_kex = NULL; const char *all_ciphers = NULL; + const char *all_macs = NULL; const uint64_t rekey_data_limit = args->rekey_data_limit; bool process_config = false; @@ -300,18 +299,12 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) } if (!ssh_fips_mode()) { + const char *all_hostkeys = NULL; /* Add methods not enabled by default */ -#define GEX_SHA1 "diffie-hellman-group-exchange-sha1" - default_kex = ssh_kex_get_default_methods(SSH_KEX); - kex_len = strlen(default_kex) + strlen(GEX_SHA1) + 2; - all_kex = malloc(kex_len); - if (all_kex == NULL) { - pkderr("Failed to alloc more memory.\n"); - goto outclose; - } - snprintf(all_kex, kex_len, "%s," GEX_SHA1, default_kex); + + /* Enable all supported key exchange methods */ + all_kex = ssh_kex_get_supported_method(SSH_KEX); rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_KEY_EXCHANGE, all_kex); - free(all_kex); if (rc != 0) { pkderr("ssh_bind_options_set kex methods: %s\n", ssh_get_error(b)); goto outclose; @@ -331,6 +324,30 @@ static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) pkderr("ssh_bind_options_set Ciphers S-C: %s\n", ssh_get_error(b)); goto outclose; } + + /* Enable all hostkey algorithms */ + all_hostkeys = ssh_kex_get_supported_method(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)); + goto outclose; + } + + /* Enable all message authentication codes */ + all_macs = ssh_kex_get_supported_method(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); + 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)); + goto outclose; + } + } s = ssh_new(); diff --git a/libssh/tests/pkd/pkd_util.c b/libssh/tests/pkd/pkd_util.c index 0e3b19b..e8e6fbb 100644 --- a/libssh/tests/pkd/pkd_util.c +++ b/libssh/tests/pkd/pkd_util.c @@ -81,6 +81,7 @@ static int is_openssh_client_new_enough(void) { ((major < 1) || (major > 100))) { fprintf(stderr, "failed to parse OpenSSH client version, " "errno %d\n", errno); + errno = 0; goto errversion; } diff --git a/libssh/tests/server/CMakeLists.txt b/libssh/tests/server/CMakeLists.txt index ebe2d16..96457cd 100644 --- a/libssh/tests/server/CMakeLists.txt +++ b/libssh/tests/server/CMakeLists.txt @@ -10,6 +10,7 @@ set(LIBSSH_SERVER_TESTS torture_server torture_server_auth_kbdint torture_server_config + torture_server_algorithms ) include_directories(${libssh_SOURCE_DIR}/include diff --git a/libssh/tests/server/test_server/default_cb.c b/libssh/tests/server/test_server/default_cb.c index 03c3419..a0a874f 100644 --- a/libssh/tests/server/test_server/default_cb.c +++ b/libssh/tests/server/test_server/default_cb.c @@ -51,6 +51,40 @@ #include #endif +int auth_none_cb(UNUSED_PARAM(ssh_session session), + const char *user, + void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_string banner = NULL; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto denied; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + printf("None authentication of user %s\n", user); + + /* Send the banner */ + banner = ssh_string_from_char(SSHD_BANNER_MESSAGE); + if (banner == NULL) { + goto denied; + } + if (ssh_send_issue_banner(session, banner) == SSH_ERROR) { + fprintf(stderr, "Error: Failed to send the banner.\n"); + goto denied; + } +denied: + ssh_string_free(banner); + return SSH_AUTH_DENIED; +} + int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), const char *user, UNUSED_PARAM(struct ssh_key_struct *pubkey), @@ -743,6 +777,7 @@ struct ssh_server_callbacks_struct *get_default_server_cb(void) goto end; } + cb->auth_none_function = auth_none_cb; cb->auth_password_function = auth_password_cb; cb->auth_pubkey_function = auth_pubkey_cb; cb->channel_open_request_session_function = channel_new_session_cb; diff --git a/libssh/tests/server/test_server/default_cb.h b/libssh/tests/server/test_server/default_cb.h index 487794c..ecfd651 100644 --- a/libssh/tests/server/test_server/default_cb.h +++ b/libssh/tests/server/test_server/default_cb.h @@ -32,6 +32,8 @@ #define SSHD_DEFAULT_ADDRESS "127.0.0.1" #define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" +#define SSHD_BANNER_MESSAGE "Test Banner Message\nlibssh-send-banner\n" + #ifndef KEYS_FOLDER #ifdef _WIN32 #define KEYS_FOLDER diff --git a/libssh/tests/server/test_server/main.c b/libssh/tests/server/test_server/main.c index 87ff6c8..b4676b4 100644 --- a/libssh/tests/server/test_server/main.c +++ b/libssh/tests/server/test_server/main.c @@ -61,6 +61,7 @@ struct arguments_st { char *config_file; bool with_global_config; + char *pid_file; }; static void free_arguments(struct arguments_st *arguments) @@ -85,6 +86,7 @@ static void free_arguments(struct arguments_st *arguments) SAFE_FREE(arguments->username); SAFE_FREE(arguments->password); SAFE_FREE(arguments->config_file); + SAFE_FREE(arguments->pid_file); end: return; @@ -401,6 +403,14 @@ static struct argp_option options[] = { .doc = "Set the pcap output file.", .group = 0 }, + { + .name = "pid_file", + .key = 'i', + .arg = "FILE", + .flags = 0, + .doc = "The server will write its pid in this file, if provided.", + .group = 0 + }, { .name = "auth-methods", .key = 'a', @@ -500,6 +510,14 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) goto end; } break; + case 'i': + arguments->pid_file = strdup(arg); + if (arguments->pid_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; case 'k': arguments->host_key = strdup(arg); if (arguments->host_key == NULL) { @@ -598,6 +616,8 @@ static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) { int rc; + FILE *pid_file; + pid_t pid; struct arguments_st arguments = { .address = NULL, @@ -611,6 +631,17 @@ int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) argp_parse (&argp, argc, argv, 0, 0, &arguments); #endif + if (arguments.pid_file) { + pid_file = fopen(arguments.pid_file, "w"); + if (pid_file == NULL) { + rc = -1; + goto free_arguments; + } + pid = getpid(); + fprintf(pid_file, "%d\n", pid); + fclose(pid_file); + } + /* Initialize the state using default or given parameters */ rc = init_server_state(&state, &arguments); if (rc != 0) { diff --git a/libssh/tests/server/torture_server.c b/libssh/tests/server/torture_server.c index 518fc97..c21f361 100644 --- a/libssh/tests/server/torture_server.c +++ b/libssh/tests/server/torture_server.c @@ -45,44 +45,14 @@ const char template[] = "temp_dir_XXXXXX"; struct test_server_st { struct torture_state *state; - struct server_state_st *ss; char *cwd; char *temp_dir; }; -static int setup_default_server(void **state) +static int libssh_server_setup(void **state) { - struct torture_state *s; - struct server_state_st *ss; - struct test_server_st *tss; -#ifdef HAVE_DSA - char dsa_hostkey[1024]; -#endif /* HAVE_DSA */ - - char ed25519_hostkey[1024] = {0}; - char rsa_hostkey[1024]; - char ecdsa_hostkey[1024]; - //char trusted_ca_pubkey[1024]; - - char sshd_path[1024]; - struct stat sb; - - const char *sftp_server_locations[] = { - "/usr/lib/ssh/sftp-server", - "/usr/libexec/sftp-server", - "/usr/libexec/openssh/sftp-server", - "/usr/lib/openssh/sftp-server", /* Debian */ - }; - - size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); - const char *sftp_server; - - size_t i; - int rc; - - char pid_str[1024]; - - pid_t pid; + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; assert_non_null(state); @@ -90,144 +60,26 @@ static int setup_default_server(void **state) assert_non_null(tss); torture_setup_socket_dir((void **)&s); - assert_non_null(s->socket_dir); - - /* Set the default interface for the server */ - setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); - setenv("PAM_WRAPPER", "1", 1); - - snprintf(sshd_path, - sizeof(sshd_path), - "%s/sshd", - s->socket_dir); - - rc = mkdir(sshd_path, 0755); - assert_return_code(rc, errno); - - snprintf(ed25519_hostkey, - sizeof(ed25519_hostkey), - "%s/sshd/ssh_host_ed25519_key", - s->socket_dir); - torture_write_file(ed25519_hostkey, - torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); - -#ifdef HAVE_DSA - snprintf(dsa_hostkey, - sizeof(dsa_hostkey), - "%s/sshd/ssh_host_dsa_key", - s->socket_dir); - torture_write_file(dsa_hostkey, torture_get_testkey(SSH_KEYTYPE_DSS, 0)); -#endif /* HAVE_DSA */ - - snprintf(rsa_hostkey, - sizeof(rsa_hostkey), - "%s/sshd/ssh_host_rsa_key", - s->socket_dir); - torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); - - snprintf(ecdsa_hostkey, - sizeof(ecdsa_hostkey), - "%s/sshd/ssh_host_ecdsa_key", - s->socket_dir); - torture_write_file(ecdsa_hostkey, - torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); - - sftp_server = getenv("TORTURE_SFTP_SERVER"); - if (sftp_server == NULL) { - for (i = 0; i < sftp_sl_size; i++) { - sftp_server = sftp_server_locations[i]; - rc = lstat(sftp_server, &sb); - if (rc == 0) { - break; - } - } - } - assert_non_null(sftp_server); - - /* Create default server state */ - ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); - assert_non_null(ss); - - ss->address = strdup("127.0.0.10"); - assert_non_null(ss->address); - - ss->port = 22; - - ss->ecdsa_key = strdup(ecdsa_hostkey); - assert_non_null(ss->ecdsa_key); - -#ifdef HAVE_DSA - ss->dsa_key = strdup(dsa_hostkey); - assert_non_null(ss->dsa_key); -#endif /* HAVE_DSA */ - - ss->ed25519_key = strdup(ed25519_hostkey); - assert_non_null(ed25519_hostkey); - - ss->rsa_key = strdup(rsa_hostkey); - assert_non_null(ss->rsa_key); - - ss->host_key = NULL; - - /* Use default username and password (set in default_handle_session_cb) */ - ss->expected_username = NULL; - ss->expected_password = NULL; - - ss->verbosity = torture_libssh_verbosity(); - - ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; - -#ifdef WITH_PCAP - ss->with_pcap = 1; - ss->pcap_file = strdup(s->pcap_file); - assert_non_null(ss->pcap_file); -#endif + torture_setup_create_libssh_config((void **)&s); - /* TODO make configurable */ - ss->max_tries = 3; - ss->error = 0; - - /* Use the default session handling function */ - ss->handle_session = default_handle_session_cb; - assert_non_null(ss->handle_session); - - /* Do not use global configuration */ - ss->parse_global_config = false; - - /* Start the server using the default values */ - pid = fork_run_server(ss); - if (pid < 0) { - fail(); - } - - snprintf(pid_str, sizeof(pid_str), "%d", pid); - - torture_write_file(s->srv_pidfile, (const char *)pid_str); - - setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); - unsetenv("PAM_WRAPPER"); - - /* Wait until the sshd is ready to accept connections */ - //rc = torture_wait_for_daemon(5); - //assert_int_equal(rc, 0); - - /* TODO properly wait for the server (use ping approach) */ - /* Wait 200ms */ - usleep(200 * 1000); + /* The second argument is the relative path to the "server" directory binary + */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); tss->state = s; - tss->ss = ss; *state = tss; return 0; } -static int teardown_default_server(void **state) -{ - struct torture_state *s; - struct server_state_st *ss; - struct test_server_st *tss; +static int sshd_teardown(void **state) { + + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); tss = *state; assert_non_null(tss); @@ -235,14 +87,9 @@ static int teardown_default_server(void **state) s = tss->state; assert_non_null(s); - ss = tss->ss; - assert_non_null(ss); - - /* This function can be reused */ + /* This function can be reused to teardown the server */ torture_teardown_sshd_server((void **)&s); - free_server_state(tss->ss); - SAFE_FREE(tss->ss); SAFE_FREE(tss); return 0; @@ -327,6 +174,7 @@ static void torture_server_auth_none(void **state) struct test_server_st *tss = *state; struct torture_state *s = NULL; ssh_session session = NULL; + char *banner = NULL; int rc; assert_non_null(tss); @@ -346,6 +194,11 @@ static void torture_server_auth_none(void **state) rc = ssh_userauth_none(session, NULL); assert_int_equal(rc, SSH_AUTH_DENIED); + banner = ssh_get_issue_banner(session); + assert_string_equal(banner, SSHD_BANNER_MESSAGE); + free(banner); + banner = NULL; + /* This request should return a SSH_REQUEST_DENIED error */ if (rc == SSH_ERROR) { assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); @@ -517,6 +370,66 @@ static void torture_server_unknown_global_request(void **state) ssh_channel_close(channel); } +static void torture_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + const char *message = "Goodbye"; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,message); + assert_ssh_return_code(session, rc); + assert_string_equal(session->disconnect_message,message); +} + +static void torture_null_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(NULL,"Goodbye"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_set_null_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,NULL); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->disconnect_message,"Bye Bye"); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -535,15 +448,21 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_null_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_null_disconnect_message, + session_setup, + session_teardown), }; ssh_init(); torture_filter_tests(tests); - rc = cmocka_run_group_tests(tests, - setup_default_server, - teardown_default_server); - + rc = cmocka_run_group_tests(tests, libssh_server_setup, sshd_teardown); ssh_finalize(); return rc; diff --git a/libssh/tests/server/torture_server_algorithms.c b/libssh/tests/server/torture_server_algorithms.c new file mode 100644 index 0000000..3963e36 --- /dev/null +++ b/libssh/tests/server/torture_server_algorithms.c @@ -0,0 +1,364 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; + char rsa_hostkey[1024]; +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Start the server using the default values */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* + * Check that the handshake works with an AEAD cipher configured + * but with no overlap for HMACs. AEAD ciphers have an implied HMAC + * so no HMAC overlap in the handshake should not fail the connection. + */ +static void test_algorithm_no_hmac_overlap(void **state, const char *algorithm) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare key files */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\nMACs %s\n", + tss->rsa_hostkey, + algorithm, + "hmac-sha2-512-etm@openssh.com"); + + 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, + config_content); + fflush(stderr); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + +static void torture_algorithm_chacha20_with_no_hmac_overlap(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_algorithm_no_hmac_overlap(state, "chacha20-poly1305@openssh.com"); +} + +static void torture_algorithm_aes256gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes256-gcm@openssh.com"); +} + +static void torture_algorithm_aes128gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes128-gcm@openssh.com"); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithm_chacha20_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes256gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes128gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/server/torture_server_config.c b/libssh/tests/server/torture_server_config.c index d751dd7..5f66f01 100644 --- a/libssh/tests/server/torture_server_config.c +++ b/libssh/tests/server/torture_server_config.c @@ -44,7 +44,6 @@ const char template[] = "temp_dir_XXXXXX"; struct test_server_st { struct torture_state *state; - struct server_state_st *ss; char *cwd; char *temp_dir; char ed25519_hostkey[1024]; @@ -195,78 +194,19 @@ static int teardown_temp_dir(void **state) return 0; } -static struct server_state_st *setup_server_state(char *config_file, - bool parse_global) -{ - struct server_state_st *ss = NULL; - - assert_non_null(config_file); - - /* Create default server state */ - ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); - assert_non_null(ss); - - ss->address = strdup("127.0.0.10"); - assert_non_null(ss->address); - - ss->port = 22; - ss->host_key = NULL; - - /* Use default username and password (set in default_handle_session_cb) */ - ss->expected_username = NULL; - ss->expected_password = NULL; - - ss->verbosity = torture_libssh_verbosity(); - ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; - - /* TODO make configurable */ - ss->max_tries = 3; - ss->error = 0; - - /* Use the default session handling function */ - ss->handle_session = default_handle_session_cb; - assert_non_null(ss->handle_session); - - /* Set if should parse global configuration before */ - ss->parse_global_config = parse_global; - - /* Set the config file to be used */ - ss->config_file = strdup(config_file); - assert_non_null(ss->config_file); - - return ss; -} - static int start_server(void **state) { struct test_server_st *tss = *state; struct torture_state *s; - struct server_state_st *ss; - - char pid_str[1024]; - pid_t pid; assert_non_null(tss); s = tss->state; assert_non_null(s); - ss = tss->ss; - assert_non_null(ss); - /* Start the server using the default values */ - pid = fork_run_server(ss); - if (pid < 0) { - fail(); - } - - snprintf(pid_str, sizeof(pid_str), "%d", pid); - - torture_write_file(s->srv_pidfile, (const char *)pid_str); - - /* TODO properly wait for the server (use ping approach) */ - /* Wait 200ms */ - usleep(200 * 1000); + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); return 0; } @@ -285,9 +225,7 @@ static int stop_server(void **state) assert_non_null(s); rc = torture_terminate_process(s->srv_pidfile); - if (rc != 0) { - fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); - } + assert_return_code(rc, errno); unlink(s->srv_pidfile); @@ -299,6 +237,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); struct passwd *pwd; bool b = false; int rc; @@ -327,6 +266,8 @@ static int session_setup(void **state) /* 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); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOSTKEYS, compat_hostkeys); + assert_ssh_return_code(s->ssh.session, rc); return 0; } @@ -351,9 +292,7 @@ static int try_config_content(void **state, const char *config_content, bool parse_global) { struct test_server_st *tss = *state; - struct server_state_st *ss; struct torture_state *s; - char config_file[1024]; int rc; ssh_session session; @@ -363,23 +302,19 @@ static int try_config_content(void **state, const char *config_content, s = tss->state; assert_non_null(s); - /* Prepare the config file to test */ - snprintf(config_file, - sizeof(config_file), - "%s/config_file", - tss->temp_dir); + assert_non_null(s->srv_config); if (parse_global) { fprintf(stderr, "Using system-wide configuration\n"); + } else { + /* The string is duplicated to not break the cleanup on error */ + s->srv_additional_config = strdup("-g"); } - fprintf(stderr, "Trying content: \n\n%s\n", config_content); - - torture_write_file(config_file, config_content); - ss = setup_server_state(config_file, parse_global); - assert_non_null(ss); + torture_write_file(s->srv_config, config_content); - tss->ss = ss; + fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + config_content); rc = start_server(state); assert_int_equal(rc, 0); @@ -414,10 +349,7 @@ static int try_config_content(void **state, const char *config_content, rc = stop_server(state); assert_int_equal(rc, 0); - free_server_state(tss->ss); - SAFE_FREE(tss->ss); - - unlink(config_file); + SAFE_FREE(s->srv_additional_config); return 0; } @@ -771,6 +703,103 @@ static void torture_server_config_unknown(void **state) assert_int_equal(rc, 0); } +/* + * Check that the server returns the correct signature when the negotiated host + * key is RSA but the signature algorithm is not the server's preferred + * algorithm (e.g. when the client prefers ssh-rsa over rsa-sha2-256 or + * rsa-sha2-512). + * + * Related: T191, T240 + */ +static void torture_server_config_rsa_hostkey_order(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + size_t num_hostkey_files; + const char *allowed = NULL; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare key files */ + num_hostkey_files = setup_hostkey_files(tss); + assert_true(num_hostkey_files > 0); + + /* Create the server configuration file */ + if (ssh_fips_mode()) { + allowed = "rsa-sha2-512,rsa-sha2-256"; + } else { + allowed = "rsa-sha2-256,ssh-rsa"; + } + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, allowed); + + 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, + config_content); + fflush(stderr); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Set client order of preference different from the server */ + if (ssh_fips_mode()) { + /* Set the host keys with rsa-sha2-256 before rsa-sha2-512 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "rsa-sha2-256,rsa-sha2-512"); + assert_int_equal(rc, SSH_OK); + } else { + /* Set the host keys with ssh-rsa before rsa-sha2-256 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "ssh-rsa,rsa-sha2-256"); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -786,6 +815,8 @@ int torture_run_tests(void) { setup_temp_dir, teardown_temp_dir), cmocka_unit_test_setup_teardown(torture_server_config_unknown, setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_rsa_hostkey_order, + setup_temp_dir, teardown_temp_dir), }; ssh_init(); diff --git a/libssh/tests/sftp_stress/main.c b/libssh/tests/sftp_stress/main.c deleted file mode 100644 index ac3f8bb..0000000 --- a/libssh/tests/sftp_stress/main.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * main.c - * - * Created on: 22 juin 2009 - * Author: aris - */ -#include -#include -#include -#include -#include -#include -#include -#include -#define TEST_READ 1 -#define TEST_WRITE 2 -#define NTHREADS 3 -#define FILESIZE 100000 -unsigned char samplefile[FILESIZE]; -volatile int stop=0; - -const char* hosts[]={"localhost","barebone"}; -void signal_stop(){ - stop=1; - printf("Stopping...\n"); -} - -SSH_SESSION *connect_host(const char *hostname); -int sftp_test(SSH_SESSION *session, int test); - -int docycle(const char *host, int test){ - SSH_SESSION *session=connect_host(host); - int ret=SSH_ERROR; - if(!session){ - printf("connect failed\n"); - } else { - printf("Connected\n"); - ret=sftp_test(session,test); - if(ret != SSH_OK){ - printf("Error in sftp\n"); - } - ssh_disconnect(session); - } - return ret; -} - -int thread(){ - while(docycle(hosts[rand()%2],TEST_WRITE) == SSH_OK) - if(stop) - break; - return 0; -} - -int main(int argc, char **argv){ - int i; - pthread_t threads[NTHREADS]; - ssh_init(); - srand(time(NULL)); - for(i=0;i #include +#include #include +#include int main(int argc, char **argv) { const char *banner = NULL; ssh_session session = NULL; + const char *hostkeys = NULL; int rc = 1; + bool process_config = false; + if (argc < 1 || argv[1] == NULL) { fprintf(stderr, "Error: Need an argument (hostname)\n"); goto out; } + ssh_init(); + session = ssh_new(); if (session == NULL) { goto out; @@ -45,6 +54,19 @@ int main(int argc, char **argv) goto out; } + /* Ignore system-wide configurations when simply trying to reach host */ + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + goto out; + } + + /* Enable all supported algorithms (including DSA) */ + hostkeys = ssh_kex_get_supported_method(SSH_HOSTKEYS); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkeys); + if (rc < 0) { + goto out; + } + rc = ssh_connect(session); if (rc != SSH_OK) { fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); @@ -62,6 +84,7 @@ int main(int argc, char **argv) out: ssh_free(session); + ssh_finalize(); return rc; } diff --git a/libssh/tests/suppressions/lsan.supp b/libssh/tests/suppressions/lsan.supp new file mode 100644 index 0000000..42a17e3 --- /dev/null +++ b/libssh/tests/suppressions/lsan.supp @@ -0,0 +1 @@ +leak:libcrypto.so diff --git a/libssh/tests/test_exec.c b/libssh/tests/test_exec.c deleted file mode 100644 index 352cc83..0000000 --- a/libssh/tests/test_exec.c +++ /dev/null @@ -1,62 +0,0 @@ -/* -This file is distributed in public domain. You can do whatever you want -with its content. -*/ -#include -#include -#include -#include "tests.h" - -void do_connect(SSH_SESSION *session) { - char buf[4096] = {0}; - CHANNEL *channel; - - int error = ssh_connect(session); - if (error != SSH_OK) { - fprintf(stderr,"Error at connection: %s\n", ssh_get_error(session)); - return; - } - printf("Connected\n"); - - ssh_session_is_known_server(session); - - error = authenticate(session); - if(error != SSH_AUTH_SUCCESS) { - fprintf(stderr,"Error at authentication: %s\n", ssh_get_error(session)); - return; - } - printf("Authenticated\n"); - channel = ssh_channel_new(session); - ssh_channel_open_session(channel); - printf("Execute 'ls' on the channel\n"); - error = ssh_channel_request_exec(channel, "ls"); - if(error != SSH_OK){ - fprintf(stderr, "Error executing command: %s\n", ssh_get_error(session)); - return; - } - printf("--------------------output----------------------\n"); - while (ssh_channel_read(channel, buf, sizeof(buf), 0)) { - printf("%s", buf); - } - printf("\n"); - printf("---------------------end------------------------\n"); - ssh_channel_send_eof(channel); - fprintf(stderr, "Exit status: %d\n", ssh_channel_get_exit_status(channel)); - - printf("\nChannel test finished\n"); - ssh_channel_close(channel); - ssh_channel_free(channel); -} - -int main(int argc, char **argv){ - SSH_OPTIONS *options=set_opts(argc, argv); - SSH_SESSION *session=ssh_new(); - if(options==NULL){ - return 1; - } - ssh_set_options(session,options); - do_connect(session); - ssh_disconnect(session); - ssh_finalize(); - return 0; -} diff --git a/libssh/tests/test_ssh_bind_accept_fd.c b/libssh/tests/test_ssh_bind_accept_fd.c deleted file mode 100644 index 5aa8211..0000000 --- a/libssh/tests/test_ssh_bind_accept_fd.c +++ /dev/null @@ -1,147 +0,0 @@ -/* Test the ability to use ssh_bind_accept_fd. - * - * Expected behavior: Prints "SUCCESS!" - * - * Faulty behavior observed before change: Connection timeout - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct options { - const char *server_keyfile; -} options; - -const char HOST[] = "127.0.0.1"; -const int PORT = 3333; - -int get_connection() { - int rc, server_socket, client_conn = -1; - struct sockaddr_in server_socket_addr; - struct sockaddr_storage client_conn_addr; - socklen_t client_conn_addr_size = sizeof(client_conn_addr); - - server_socket = socket(PF_INET, SOCK_STREAM, 0); - if (server_socket < 0) { - goto out; - } - - server_socket_addr.sin_family = AF_INET; - server_socket_addr.sin_port = htons(PORT); - if (inet_pton(AF_INET, HOST, &server_socket_addr.sin_addr) != 1) { - goto out; - } - - rc = bind(server_socket, (struct sockaddr *)&server_socket_addr, - sizeof(server_socket_addr)); - if (rc < 0) { - goto out; - } - - if (listen(server_socket, 0) < 0) { - goto out; - } - - client_conn = accept(server_socket, - (struct sockaddr *)&client_conn_addr, - &client_conn_addr_size); - - out: - return client_conn; -} - -void ssh_server() { - ssh_bind bind; - ssh_session session; - - int client_conn = get_connection(); - if (client_conn < 0) { - err(1, "get_connection"); - } - - bind = ssh_bind_new(); - if (!bind) { - errx(1, "ssh_bind_new"); - } - -#ifdef HAVE_DSA - /*TODO mbedtls this is probably required */ - if (ssh_bind_options_set(bind, SSH_BIND_OPTIONS_DSAKEY, - options.server_keyfile) != SSH_OK) { - errx(1, "ssh_bind_options_set(SSH_BIND_OPTIONS_DSAKEY"); - } -#else - if (ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSAKEY, - options.server_keyfile) != SSH_OK) { - errx(1, "ssh_bind_options_set(SSH_BIND_OPTIONS_RSAKEY"); - } -#endif - - session = ssh_new(); - if (!session) { - errx(1, "ssh_new"); - } - - if (ssh_bind_accept_fd(bind, session, client_conn) != SSH_OK) { - errx(1, "ssh_bind_accept: %s", ssh_get_error(bind)); - } - - if (ssh_handle_key_exchange(session) != SSH_OK) { - errx(1, "ssh_handle_key_exchange: %s", ssh_get_error(session)); - } - - printf("SUCCESS!\n"); -} - -void ssh_client() { - ssh_session session; - - session = ssh_new(); - if (!session) { - errx(1, "ssh_new"); - } - - if (ssh_options_set(session, SSH_OPTIONS_HOST, HOST) < 0) { - errx(1, "ssh_options_set(SSH_OPTIONS_HOST)"); - } - if (ssh_options_set(session, SSH_OPTIONS_PORT, &PORT) < 0) { - errx(1, "ssh_options_set(SSH_OPTIONS_PORT)"); - } - - if (ssh_connect(session) != SSH_OK) { - errx(1, "ssh_connect: %s", ssh_get_error(session)); - } -} - -int main(int argc, const char *argv[]) { - if (argc != 2) { - printf("Usage: %s \n", argv[0]); - exit(1); - } - - options.server_keyfile = argv[1]; - - pid_t pid = fork(); - if (pid < 0) { - errx(1, "fork"); - } - if (pid == 0) { - /* Allow the server to get set up */ - sleep(3); - - ssh_client(); - } else { - ssh_server(); - } - - return 0; -} diff --git a/libssh/tests/test_tunnel.c b/libssh/tests/test_tunnel.c deleted file mode 100644 index 952c73e..0000000 --- a/libssh/tests/test_tunnel.c +++ /dev/null @@ -1,76 +0,0 @@ -/* -This file is distributed in public domain. You can do whatever you want -with its content. -*/ -#include -#include -#include -#include "tests.h" -#define ECHO_PORT 7 -void do_connect(SSH_SESSION *session){ - int error=ssh_connect(session); - if(error != SSH_OK){ - fprintf(stderr,"Error at connection :%s\n",ssh_get_error(session)); - return; - } - printf("Connected\n"); - ssh_session_is_known_server(session); - // we don't care what happens here - error=authenticate(session); - if(error != SSH_AUTH_SUCCESS){ - fprintf(stderr,"Error at authentication :%s\n",ssh_get_error(session)); - return; - } - printf("Authenticated\n"); - CHANNEL *channel=ssh_channel_new(session); - error=ssh_channel_open_forward(channel,"localhost",ECHO_PORT,"localhost",42); - if(error!=SSH_OK){ - fprintf(stderr,"Error when opening forward:%s\n",ssh_get_error(session)); - return; - } - printf("Forward opened\n"); - int i=0; - char string[20]; - char buffer[20]; - for(i=0;i<2000;++i){ - sprintf(string,"%d\n",i); - ssh_channel_write(channel,string,strlen(string)); - do { - error=ssh_channel_poll(channel,0); - //if(error < strlen(string)) - //usleep(10); - } while(error < strlen(string) && error >= 0); - if(error>0){ - error=ssh_channel_read_nonblocking(channel,buffer,strlen(string),0); - if(error>=0){ - if(memcmp(buffer,string,strlen(string))!=0){ - fprintf(stderr,"Problem with answer: wanted %s got %s\n",string,buffer); - } else { - printf("."); - fflush(stdout); - } - } - - } - if(error==-1){ - fprintf(stderr,"Channel reading error : %s\n",ssh_get_error(session)); - break; - } - } - printf("\nChannel test finished\n"); - ssh_channel_close(channel); - ssh_channel_free(channel); -} - -int main(int argc, char **argv){ - SSH_OPTIONS *options=set_opts(argc, argv); - SSH_SESSION *session=ssh_new(); - if(options==NULL){ - return 1; - } - ssh_set_options(session,options); - do_connect(session); - ssh_disconnect(session); - ssh_finalize(); - return 0; -} diff --git a/libssh/tests/tests.h b/libssh/tests/tests.h deleted file mode 100644 index dd001f1..0000000 --- a/libssh/tests/tests.h +++ /dev/null @@ -1,8 +0,0 @@ -/* -This file is distributed in public domain. You can do whatever you want -with its content. -*/ -#include -int authenticate (SSH_SESSION *session); -SSH_OPTIONS *set_opts(int argc, char **argv); - diff --git a/libssh/tests/tests_config.h.cmake b/libssh/tests/tests_config.h.cmake index 3873384..b162db7 100644 --- a/libssh/tests/tests_config.h.cmake +++ b/libssh/tests/tests_config.h.cmake @@ -64,6 +64,8 @@ /* Available programs */ -#cmakedefine NC_EXECUTABLE "${NC_EXECUTABLE}" +#cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" -#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" \ No newline at end of file +#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} +#cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" diff --git a/libssh/tests/torture.c b/libssh/tests/torture.c index b3ea36b..f5a6bcc 100644 --- a/libssh/tests/torture.c +++ b/libssh/tests/torture.c @@ -51,6 +51,7 @@ #include "torture.h" #include "torture_key.h" #include "libssh/misc.h" +#include "libssh/token.h" #define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" /* socket wrapper IPv6 prefix fd00::5357:5fxx */ @@ -62,10 +63,6 @@ #define TORTURE_SSHD_CONFIG "sshd/sshd_config" #define TORTURE_PCAP_FILE "socket_trace.pcap" -#ifndef PATH_MAX -# define PATH_MAX 4096 -#endif - static const char torture_rsa_certauth_pub[]= "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" @@ -250,8 +247,12 @@ int torture_terminate_process(const char *pidfile) rc = kill(pid, 0); if (rc != 0) { - is_running = 0; - break; + /* Process not found */ + if (errno == ESRCH) { + is_running = 0; + rc = 0; + break; + } } } @@ -260,7 +261,7 @@ int torture_terminate_process(const char *pidfile) "WARNING: The process with pid %u is still running!\n", pid); } - return 0; + return rc; } ssh_session torture_ssh_session(struct torture_state *s, @@ -502,6 +503,10 @@ void torture_sftp_close(struct torture_sftp *t) { sftp_free(t->sftp); } + if (t->testdir) { + torture_rmdirs(t->testdir); + } + free(t->testdir); free(t); } @@ -611,6 +616,113 @@ void torture_setup_socket_dir(void **state) *state = s; } +/** + * @brief Create a libssh server configuration file + * + * It is expected the socket directory to be already created before by calling + * torture_setup_socket_dir(). The created configuration file will be stored in + * the socket directory and the srv_config pointer in the state will be + * initialized. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_setup_create_libssh_config(void **state) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; +#ifdef HAVE_DSA + char dsa_hostkey[1024]; +#endif /* HAVE_DSA */ + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char sshd_config[2048]; + 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" +#ifdef HAVE_DSA + "%s %s\n" +#endif /* HAVE_DSA */ + "%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); + + rc = lstat(sshd_path, &sb); + if (rc == 0 ) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + +#ifdef HAVE_DSA + snprintf(dsa_hostkey, + sizeof(dsa_hostkey), + "%s/sshd/ssh_host_dsa_key", + s->socket_dir); +#endif /* HAVE_DSA */ + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#ifdef HAVE_DSA + torture_write_file(dsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); +#endif /* HAVE_DSA */ + } + + additional_config = (s->srv_additional_config != NULL ? + s->srv_additional_config : ""); + + snprintf(sshd_config, sizeof(sshd_config), + config_string, + "HostKey", ed25519_hostkey, + "HostKey", rsa_hostkey, + "HostKey", ecdsa_hostkey, +#ifdef HAVE_DSA + "HostKey", dsa_hostkey, +#endif /* HAVE_DSA */ + additional_config); + + torture_write_file(s->srv_config, sshd_config); +} + +#ifdef SSHD_EXECUTABLE static void torture_setup_create_sshd_config(void **state, bool pam) { struct torture_state *s = *state; @@ -856,21 +968,158 @@ static int torture_wait_for_daemon(unsigned int seconds) return 1; } -void torture_setup_sshd_server(void **state, bool pam) +/** + * @brief Run a libssh based server under timeout. + * + * It is expected that the socket directory and libssh configuration file were + * already created before by calling torture_setup_socket_dir() and + * torture_setup_create_libssh_config() (or alternatively setup the state with + * the correct values). + * + * @param[in] state The content of the address pointed by this variable must be + * a pointer to an initialized instance of torture_state + * structure; it can be obtained by calling + * torture_setup_socket_dir() and + * torture_setup_create_libssh_config(). + * @param[in] server_path The path to the server executable. + * + * @note This function will use the state->srv_additional_config field as + * additional command line option used when starting the server instead of extra + * configuration file options. + * */ +void torture_setup_libssh_server(void **state, const char *server_path) { struct torture_state *s; - char sshd_start_cmd[1024]; + char start_cmd[1024]; + char timeout_cmd[512]; + char env[1024]; + char extra_options[1024]; int rc; + char *ld_preload = NULL; + const char *force_fips = NULL; - torture_setup_socket_dir(state); - torture_setup_create_sshd_config(state, pam); + struct ssh_tokens_st *env_tokens; + struct ssh_tokens_st *arg_tokens; + + pid_t pid; + ssize_t printed; + + s = *state; + + /* Get all the wrapper libraries to be pre-loaded */ + ld_preload = getenv("LD_PRELOAD"); + + if (s->srv_additional_config != NULL) { + printed = snprintf(extra_options, sizeof(extra_options), " %s ", + s->srv_additional_config); + if (printed < 0) { + fail_msg("Failed to print additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } else { + printed = snprintf(extra_options, sizeof(extra_options), " "); + if (printed < 0) { + fail_msg("Failed to print empty additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } + + if (ssh_fips_mode()) { + force_fips = "OPENSSL_FORCE_FIPS_MODE=1 "; + } else { + force_fips = ""; + } + + /* Write the environment setting */ + /* OPENSSL variable is needed to enable SHA1 */ + printed = snprintf(env, sizeof(env), + "SOCKET_WRAPPER_DIR=%s " + "SOCKET_WRAPPER_DEFAULT_IFACE=10 " + "LD_PRELOAD=%s " + "%s " + "OPENSSL_ENABLE_SHA1_SIGNATURES=1", + s->socket_dir, ld_preload, force_fips); + if (printed < 0) { + fail_msg("Failed to print env!"); + /* Unreachable */ + __builtin_unreachable(); + } + +#ifdef WITH_TIMEOUT + 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), + "%s" + "%s -f%s -v4 -p22 -i%s -C%s%s%s", + timeout_cmd, + server_path, s->pcap_file, s->srv_pidfile, + s->srv_config, extra_options, TORTURE_SSH_SERVER); + if (printed < 0) { + fail_msg("Failed to print start command!"); + /* Unreachable */ + __builtin_unreachable(); + } + + pid = fork(); + switch(pid) { + case 0: + env_tokens = ssh_tokenize(env, ' '); + if (env_tokens == NULL || env_tokens->tokens == NULL) { + fail_msg("Failed to tokenize env!"); + /* Unreachable */ + __builtin_unreachable(); + } + + arg_tokens = ssh_tokenize(start_cmd, ' '); + if (arg_tokens == NULL || arg_tokens->tokens == NULL) { + ssh_tokens_free(env_tokens); + fail_msg("Failed to tokenize args!"); + /* Unreachable */ + __builtin_unreachable(); + } + + execve(arg_tokens->tokens[0], (char **)arg_tokens->tokens, + (char **)env_tokens->tokens); + + /* execve returns only in case of error */ + ssh_tokens_free(env_tokens); + ssh_tokens_free(arg_tokens); + fail_msg("Error in execve: %s", strerror(errno)); + /* Unreachable */ + __builtin_unreachable(); + case -1: + fail_msg("Failed to fork!"); + /* Unreachable */ + __builtin_unreachable(); + default: + /* The parent continues the execution of the tests */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the server is ready to accept connections */ + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + break; + } +} + +static int torture_start_sshd_server(void **state) +{ + struct torture_state *s = *state; + char sshd_start_cmd[1024]; + int rc; /* Set the default interface for the server */ setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); setenv("PAM_WRAPPER", "1", 1); - s = *state; - snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), SSHD_EXECUTABLE " -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", s->srv_config, s->socket_dir, s->socket_dir); @@ -882,7 +1131,20 @@ void torture_setup_sshd_server(void **state, bool pam) unsetenv("PAM_WRAPPER"); /* Wait until the sshd is ready to accept connections */ - rc = torture_wait_for_daemon(5); + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + + return SSH_OK; +} + +void torture_setup_sshd_server(void **state, bool pam) +{ + int rc; + + torture_setup_socket_dir(state); + torture_setup_create_sshd_config(state, pam); + + rc = torture_start_sshd_server(state); assert_int_equal(rc, 0); } @@ -922,29 +1184,12 @@ static int torture_reload_sshd_server(void **state) { struct torture_state *s = *state; - pid_t pid; int rc; - /* read the pidfile */ - pid = torture_read_pidfile(s->srv_pidfile); - assert_int_not_equal(pid, -1); - - kill(pid, SIGHUP); - - /* 10 ms */ - usleep(10 * 1000); - - rc = kill(pid, 0); - if (rc != 0) { - fprintf(stderr, - "ERROR: SSHD process %u died during reload!\n", pid); - return SSH_ERROR; - } + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); - /* Wait until the sshd is ready to accept connections */ - rc = torture_wait_for_daemon(5); - assert_int_equal(rc, 0); - return SSH_OK; + return torture_start_sshd_server(state); } /* @brief: Updates SSHD server configuration with more options and @@ -980,12 +1225,30 @@ void torture_teardown_sshd_server(void **state) int rc; rc = torture_terminate_process(s->srv_pidfile); - if (rc != 0) { - fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); - } + assert_return_code(rc, errno); torture_teardown_socket_dir(state); } +#endif /* SSHD_EXECUTABLE */ + +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public) +{ + char token_setup_start_cmd[1024] = {0}; + int rc; + + snprintf(token_setup_start_cmd, sizeof(token_setup_start_cmd), + "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s", + BINARYDIR, + temp_dir, + filename, + object_name, load_public); + + rc = system(token_setup_start_cmd); + assert_return_code(rc, errno); +} char *torture_make_temp_dir(const char *template) { @@ -1069,8 +1332,8 @@ char *torture_get_current_working_dir(void) char *torture_make_temp_dir(const char *template) { DWORD rc = 0; - char tmp_dir_path[MAX_PATH]; - char tmp_file_name[MAX_PATH]; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; char *prefix = NULL; char *path = NULL; char *prefix_end = NULL; @@ -1098,8 +1361,8 @@ char *torture_make_temp_dir(const char *template) *prefix_end = '\0'; } - rc = GetTempPathA(MAX_PATH, tmp_dir_path); - if ((rc > MAX_PATH) || (rc == 0)) { + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { goto free_prefix; } @@ -1140,7 +1403,7 @@ static int recursive_rm_dir_content(const char *path) DWORD last_error = 0; - char file_path[MAX_PATH]; + char file_path[PATH_MAX]; int rc = 0; BOOL removed; @@ -1248,8 +1511,8 @@ int torture_isdir(const char *path) char *torture_create_temp_file(const char *template) { DWORD rc = 0; - char tmp_dir_path[MAX_PATH]; - char tmp_file_name[MAX_PATH]; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; char *prefix = NULL; char *path = NULL; char *prefix_end = NULL; @@ -1275,8 +1538,8 @@ char *torture_create_temp_file(const char *template) *prefix_end = '\0'; } - rc = GetTempPathA(MAX_PATH, tmp_dir_path); - if ((rc > MAX_PATH) || (rc == 0)) { + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { goto free_prefix; } @@ -1366,6 +1629,15 @@ void torture_reset_config(ssh_session session) memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); } +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void) +{ + fail_msg("torture_run_tests from shared library called"); + + return -1; +} +#endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ + int main(int argc, char **argv) { struct argument_s arguments; char *env = getenv("LIBSSH_VERBOSITY"); diff --git a/libssh/tests/torture.h b/libssh/tests/torture.h index 63ddc3c..67c249a 100644 --- a/libssh/tests/torture.h +++ b/libssh/tests/torture.h @@ -24,10 +24,6 @@ #ifndef _TORTURE_H #define _TORTURE_H -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include #include #include @@ -54,6 +50,7 @@ #define TORTURE_SSH_USER_BOB_PASSWORD "secret" #define TORTURE_SSH_USER_ALICE "alice" +#define TORTURE_SSH_USER_CHARLIE "charlie" /* Used by main to communicate with parse_opt. */ struct argument_s { @@ -81,6 +78,7 @@ struct torture_state { #ifdef WITH_PCAP ssh_pcap_file plain_pcap; #endif + void *private_data; }; #ifndef ZERO_STRUCT @@ -122,6 +120,7 @@ void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); const char *torture_server_address(int domain); int torture_server_port(void); +#ifdef SSHD_EXECUTABLE void torture_setup_socket_dir(void **state); void torture_setup_sshd_server(void **state, bool pam); @@ -129,13 +128,27 @@ void torture_teardown_socket_dir(void **state); void torture_teardown_sshd_server(void **state); int torture_update_sshd_config(void **state, const char *config); +#endif /* SSHD_EXECUTABLE */ + +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public); void torture_reset_config(ssh_session session); +void torture_setup_create_libssh_config(void **state); + +void torture_setup_libssh_server(void **state, const char *server_path); + +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void); +#else /* * This function must be defined in every unit test file. */ int torture_run_tests(void); +#endif char *torture_make_temp_dir(const char *template); char *torture_create_temp_file(const char *template); diff --git a/libssh/tests/torture_key.c b/libssh/tests/torture_key.c index 5854026..9bf7199 100644 --- a/libssh/tests/torture_key.c +++ b/libssh/tests/torture_key.c @@ -28,782 +28,1004 @@ #include "torture.h" #include "torture_key.h" +enum torture_format_e { + FORMAT_PEM = 0, + FORMAT_OPENSSH, + FORMAT_PKCS8, +}; + /**************************************************************************** - * DSA KEYS + * RSA KEYS ****************************************************************************/ static const char torture_rsa_private_testkey[] = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" - "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" - "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" - "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" - "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" - "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" - "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" - "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" - "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" - "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" - "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" - "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" - "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" - "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" - "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" - "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" - "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" - "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" - "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" - "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" - "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" - "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" - "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" - "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" - "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" - "-----END RSA PRIVATE KEY-----\n"; + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsA5ERRaUFckAp\n" + "nmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3\n" + "SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8f\n" + "JmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRY\n" + "YplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+\n" + "IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg5\n" + "2DVm4esZAgMBAAECggEAUlNqrL+OWWry9nhMqIP3TTAG+uezke1KUclN7zSGpChP\n" + "5JrVxZLE9HR56QvfYePL2KBszNnze5bOHsia2A04PL29F3Od+A7wJ9MJoF8DNCyM\n" + "WmvxCKQxPoRQj8umJEvVRoaw06VIS+dJLRlvW4PMB2uMv6ewQcQMm534r2PBKQJG\n" + "J9UzLt8t9q6b5gql14TPsrYyCbmkn9WD72y8jZXWrscA4jh5RVOAk0dfLl52JEmV\n" + "qNAd/+U1TMcqqfuIf4lGXTWC3rZfyugbrZtNsXKVuF6oyb1rC5IXa5w5zH6C67CG\n" + "Q+KvwEQxvTyPeGIr5N/A91jQXpAfg6GIorsxOpNWAQKBgQDS+aa7kY4G3BGarqBU\n" + "0IeF0sNBZrEBmpBGZFvPFWEWlu/QaNtAQXu222rd+MOvMN/oIT1G1lbDYFRJdWr4\n" + "hcH9tdvWSBZTbFbLCb44UOT05dCzy6L4xjsWI/fK6rdgmhvaDkIKxVBaXYIe9YaZ\n" + "MuETU4OWXf23tjW7B1wOhsk34QKBgQDQuVGF8yPJ1abCqihT1MONc0GbC5/JiU0V\n" + "MffnVqZiAVQOk1/flIwI/iEOixTD/MJMOlz7XDTgEkl+QfVI29dI6KWl1phFNi6v\n" + "IO8aZYLrorYpfWcsSuGWGzXsLDNX33udiAMfc45Tgd+ogqZ0ZuO9aWSZj6YsfIbX\n" + "Rtuk5Nu6OQKBgBzrzEd6NJYMJP2nYqM90tYthka45RrrLUv5BfzlWWHXXxiaxzOP\n" + "xn3Splu3dlvVv1v68oN7bnlqAyPuzLM04tr8yx4c07Kr71n2A/WEYV6RAoMjLzwb\n" + "MI5gb9+JPYoolhjWS4dedR1uJCBKWCE4MFKN6EP8roM4nzT8VUpIf90BAoGBAMn5\n" + "qlPOlmEnZ/mTovaMOrdUQrYAJ51kSb0q31NzXAoWcvqvzeSS4HeLQv3JxExq+LG/\n" + "AF8UOO5jg86zkOHyKn+Y8UJrSGohJke9vOJ0PJ48k73OZdpMk8MnjaR+mSh9UCd+\n" + "iFcVATL5yC5OTzjwn4yYwgp/ITSHRAxpuv3zoFSxAoGARWCiLhFfCa0ai+0x7DTU\n" + "5E9X0w5fe/5WewKAPj/npREo4CeCJLvTEQ843A1tMJa66/jccbMmhUWIAwadA/UE\n" + "AnuCQeSNJ6A4YqSLDXYQ9RaEVwyD4aSA+Fyk6o239UwE+Mn/KwcmqYt9Ar0DfklX\n" + "oKZ2mF744iXbxtwqEA7PwPA=\n" + "-----END PRIVATE KEY-----\n"; static const char torture_rsa_private_testkey_passphrase[] = - "-----BEGIN RSA PRIVATE KEY-----\n" - "Proc-Type: 4,ENCRYPTED\n" - "DEK-Info: AES-128-CBC,5375534F40903DD66B3851A0DA03F6FA\n" - "\n" - "m5YYTNOMd1xCKfifwCX4R1iLJoAc4cn1aFiL7f2kBbfE2jF1LTQBJV1h1CqYZfAB\n" - "WtM/7FkQPnKXqsMndP+v+1Xc+PYigE3AezJj/0g7xn/zIBwGjkLAp435AdL5i6Fg\n" - "OhOL8LyolRrcGn17jE4S4iGbzw8PVyfzNzdj0Emwql5F6M7pgLbInRNKM/TF4z2h\n" - "b6Pi9Bw43dwaJ7wiiy/vo/v4MyXsJBoeKbc4VCmxiYFvAYCvVFlDkyIw/QnR3MKQ\n" - "g/Zsk7Pw3aOioxk6LJpZ5x0tO23nXDG1aOZHWykI0BpJV+LIpD2oSYOHJyVO83XT\n" - "RQUMSTXc2K2+ejs0XQoLt/GxDDHe+8W8fWQK3C7Lyvl9oKjmb5sTWi3mdSv0C+zR\n" - "n5KSVbUKNXrjix7qPKkv5rWqb84CKVnCMb7tWaPLR19nQqKVYBIs6v0OTTvS6Le7\n" - "lz4lxBkcUy6vi0tWH9MvLuT+ugdHLJZ4UXBthCgV58pM1o+L+WMIl+SZXckiCAO3\n" - "7ercA57695IA6iHskmr3eazJsYFEVFdR/cm+IDy2FPkKmJMjXeIWuh3yASBk7LBR\n" - "EQq3CC7AioO+Vj8m/fEIiNZJSQ6p0NmgnPoO3rTYT/IobmE99/Ht6oNLmFX4Pr7e\n" - "F4CGWKzwxWpCnw2vVolCFByASmZycbJvrIonZBKY1toU28lRm4tCM6eCNISVLMeE\n" - "VtQ+1PH9/2KZspZl+SX/kjV3egggy0TFKRU8EcYPJFC3Vpy+shEai35KBVo44Z18\n" - "apza7exm3igNEqOqe07hLs3Bjhvk1oS+WhMbAG9ARTOKuyBOJh/ZV9tFMNZ6v+q5\n" - "TofgNcIhNYNascymU1io18xTW9c3RRcmRKqIWnj4EH8o7Aojv/l+zvdV7/GVlR4W\n" - "pR9cuJEiyiEjS46axoc6dSOtdnvag+BpFQb+lGY97F9nNGyBdtLD5ASVh5OVG4fu\n" - "Pf0O7Bdj1kIuBhV8axE/slf6UHANiodeqkR9B24+0Cy+miPiHazzUkbdSJ4r03g5\n" - "J1Y5S8qbl9++sqhQMLMUkeK4pDWh1aocA9bDA2RcBNuXGiZeRFUiqxcBS+iO418n\n" - "DFyWz4UfI/m1IRSjoo/PEpgu5GmosUzs3Dl4nAcf/REBEX6M/kKKxHTLjE8DxDsz\n" - "fn/vfsXV3s0tbN7YyJdP8aU+ApZntw1OF2TS2qS8CPWHTcCGGTab5WEGC3xFXKp0\n" - "uyonCxV7vNLOiIiHdQX+1bLu7ps7GBH92xGkPg7FrNNcMc07soP7jjjB578n9Gpl\n" - "cIDBdgovTRFHiWu3yRspVt0zPfMJB/hqn+IAp98wfvjl8OZM1ZZkejnwXnQil5ZU\n" - "wjEBEtx+nX56vdxipzKoHh5yDXmPbNajBYkg3rXJrLFh3Tsf0CzHcLdHNz/qJ9LO\n" - "wH16grjR1Q0CzCW3FAv0Q0euqkXac+TfuIg3HiTPrBPnJQW1uivrx1F5tpO/uboG\n" - "h28LwqJLYh+1T0V//uiy3SMATpYKvzg2byGct9VUib8QVop8LvVF/n42RaxtTCfw\n" - "JSvUyxoaZUjQkT7iF94HsF+FVVJdI55UjgnMiZ0d5vKffWyTHYcYHkFYaSloAMWN\n" - "-----END RSA PRIVATE KEY-----\n"; + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5375534F40903DD66B3851A0DA03F6FA\n" + "\n" + "m5YYTNOMd1xCKfifwCX4R1iLJoAc4cn1aFiL7f2kBbfE2jF1LTQBJV1h1CqYZfAB\n" + "WtM/7FkQPnKXqsMndP+v+1Xc+PYigE3AezJj/0g7xn/zIBwGjkLAp435AdL5i6Fg\n" + "OhOL8LyolRrcGn17jE4S4iGbzw8PVyfzNzdj0Emwql5F6M7pgLbInRNKM/TF4z2h\n" + "b6Pi9Bw43dwaJ7wiiy/vo/v4MyXsJBoeKbc4VCmxiYFvAYCvVFlDkyIw/QnR3MKQ\n" + "g/Zsk7Pw3aOioxk6LJpZ5x0tO23nXDG1aOZHWykI0BpJV+LIpD2oSYOHJyVO83XT\n" + "RQUMSTXc2K2+ejs0XQoLt/GxDDHe+8W8fWQK3C7Lyvl9oKjmb5sTWi3mdSv0C+zR\n" + "n5KSVbUKNXrjix7qPKkv5rWqb84CKVnCMb7tWaPLR19nQqKVYBIs6v0OTTvS6Le7\n" + "lz4lxBkcUy6vi0tWH9MvLuT+ugdHLJZ4UXBthCgV58pM1o+L+WMIl+SZXckiCAO3\n" + "7ercA57695IA6iHskmr3eazJsYFEVFdR/cm+IDy2FPkKmJMjXeIWuh3yASBk7LBR\n" + "EQq3CC7AioO+Vj8m/fEIiNZJSQ6p0NmgnPoO3rTYT/IobmE99/Ht6oNLmFX4Pr7e\n" + "F4CGWKzwxWpCnw2vVolCFByASmZycbJvrIonZBKY1toU28lRm4tCM6eCNISVLMeE\n" + "VtQ+1PH9/2KZspZl+SX/kjV3egggy0TFKRU8EcYPJFC3Vpy+shEai35KBVo44Z18\n" + "apza7exm3igNEqOqe07hLs3Bjhvk1oS+WhMbAG9ARTOKuyBOJh/ZV9tFMNZ6v+q5\n" + "TofgNcIhNYNascymU1io18xTW9c3RRcmRKqIWnj4EH8o7Aojv/l+zvdV7/GVlR4W\n" + "pR9cuJEiyiEjS46axoc6dSOtdnvag+BpFQb+lGY97F9nNGyBdtLD5ASVh5OVG4fu\n" + "Pf0O7Bdj1kIuBhV8axE/slf6UHANiodeqkR9B24+0Cy+miPiHazzUkbdSJ4r03g5\n" + "J1Y5S8qbl9++sqhQMLMUkeK4pDWh1aocA9bDA2RcBNuXGiZeRFUiqxcBS+iO418n\n" + "DFyWz4UfI/m1IRSjoo/PEpgu5GmosUzs3Dl4nAcf/REBEX6M/kKKxHTLjE8DxDsz\n" + "fn/vfsXV3s0tbN7YyJdP8aU+ApZntw1OF2TS2qS8CPWHTcCGGTab5WEGC3xFXKp0\n" + "uyonCxV7vNLOiIiHdQX+1bLu7ps7GBH92xGkPg7FrNNcMc07soP7jjjB578n9Gpl\n" + "cIDBdgovTRFHiWu3yRspVt0zPfMJB/hqn+IAp98wfvjl8OZM1ZZkejnwXnQil5ZU\n" + "wjEBEtx+nX56vdxipzKoHh5yDXmPbNajBYkg3rXJrLFh3Tsf0CzHcLdHNz/qJ9LO\n" + "wH16grjR1Q0CzCW3FAv0Q0euqkXac+TfuIg3HiTPrBPnJQW1uivrx1F5tpO/uboG\n" + "h28LwqJLYh+1T0V//uiy3SMATpYKvzg2byGct9VUib8QVop8LvVF/n42RaxtTCfw\n" + "JSvUyxoaZUjQkT7iF94HsF+FVVJdI55UjgnMiZ0d5vKffWyTHYcYHkFYaSloAMWN\n" + "-----END RSA PRIVATE KEY-----\n"; static const char torture_rsa_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI0RSm1ZXOBD8CAggA\n" - "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBS+59quuIVuxN/H9Wltk8TBIIE\n" - "0J7OhRw35ANRyTU2qhlhS8NATcguoD1J4IMXpXpv38iCBWd2bjxvuWnEu4aBX7iU\n" - "desfz9n6AoTVqURaOMLsv6EFV0tycf+mZsmdUmrD2270Wyj6TtQD8LO/7ibifCeL\n" - "XCCKjxciueSggHp5lnfogZwn8wjSEDP7OqNVRTwm8QKNrE7J5m5giFrjXoyqKM7r\n" - "DBa35UIZAXXY8z9CkI+GsyRtaZik3VD+xHShwUriOYg4x4VGZQLj24tjoUnqU4ml\n" - "iRMhGyYpxN7CnfaIwHJr3T0dmbT/BIXOQ2B6sWakioZeUuA6OTBHbFTUN9TUHaF0\n" - "rDMVmjL6BQcEiWwjvtw/3NLdkcKFjMiLTWA2GL71KPGCecpMmAMjo+ijnxeVhqpQ\n" - "dnhowG92DhCSf/XZI0vaaYflrV54U9PgcSPDFWmTOVe5151Mi8eR9qrCanfyHmX1\n" - "MLXs8Mw6xWedNj8AWLV3JGiWEeAEATuTAQfTqmBZbzaFKfSKp5PZjWxa5bZIomzS\n" - "Q0AsONTeYmKK+Pv95RYlgR2kKqhwy3OmcOuepwnzSeAGh1BdBzd2raoipkq1fpY5\n" - "8e75dJnTGvWfqfh0VXz/Wud+hMz/98Mh6Bnp9l+Ddxpp4RioWB2aH0HM8ZGTlbhf\n" - "r5qFmDY7k+RfDDp7K7UYMA+2hHCxY1aFSHVYGRQKdYdKIugLtKx6YKLeGVCR7Gbm\n" - "l/88qiGshF/qhdFbPb4K0Tz2Ug5uklveOQSkKX6RSZ30IW+N3E4nH/wvyOwbCPk7\n" - "u+iHB2zzk2Hws4O52a0Gqj+RbeGzzhl1D9jH35GMHUsfhDSA3/mmrVC7hiN/Aplt\n" - "2OmKFAkobZh/1UJAHBY9feIhLmQUy9dwy0E8G/0LEyyZYEizDC76jsvbh2cPg3jM\n" - "JsI31qUaGggwh3wB034BvsYIf/ZqLCt8hAXF9U5U7T5y3r6FNNBla8zlj25ILog6\n" - "t/bhOwFKYXamAVYMhhvUiA3YIYuBxT7MrgL7gDtKh3N/DleS/pLjmOFfMI3dfCd0\n" - "KSQX46uw7aFbV0Has9uUuGle9Foq52QFvYnDHWJuIyOvJ5st1Hd3Mjjsl9t3JFVM\n" - "I1aDZ17Z4LoThdezNQKGaAe5z7gGFMKKsm55CMT/7FxvConALeQKGAV6jA5xZzl4\n" - "+QB14YlxlZTxYnXd/69KGV56wP8sb6uMVDC/f5Vd3oHsamJKpPgts8WCn11f9wFn\n" - "Mx8YY/vBVVLQMw1aB+82Vk+Ix8YDYIPj5bJk2BkyCCUnMYkKswUOVzsdUq0xssEp\n" - "PASw0YvQ9mY2aQ9exme99JuAj5t4qIXoYTSrX5iv6NXtzDHgTR1pl9gQQVQ0zAUO\n" - "ZHKZXYAv5rLZKRcyeCLw0LkuthY2QtN3PsBlaRtfwZTaqUbBGbvEkcx5fxdEsasS\n" - "yQkZKBBvIi42LUN9ZzywYNGbOanCZ04p/+QscmmnVGuDMZJyaDRaapW6f0nJQ+lQ\n" - "CaVPRzLKGnHV5hWQDjTaPIh2s9rJSZJ3HyE8qshETHW/vQoYIcVB9TX5TnOY02Ak\n" - "IINKfSZGgz/NBeJItjk30UuTcISk65ekoXZIHHgdxD9iHy9D0w6FXcPNLLsWQn7n\n" - "jS4Bvt0VZ9zVAiyyVO4yAaMgP+saitYpjMgI8g67geD3\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI0RSm1ZXOBD8CAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBS+59quuIVuxN/H9Wltk8TBIIE\n" + "0J7OhRw35ANRyTU2qhlhS8NATcguoD1J4IMXpXpv38iCBWd2bjxvuWnEu4aBX7iU\n" + "desfz9n6AoTVqURaOMLsv6EFV0tycf+mZsmdUmrD2270Wyj6TtQD8LO/7ibifCeL\n" + "XCCKjxciueSggHp5lnfogZwn8wjSEDP7OqNVRTwm8QKNrE7J5m5giFrjXoyqKM7r\n" + "DBa35UIZAXXY8z9CkI+GsyRtaZik3VD+xHShwUriOYg4x4VGZQLj24tjoUnqU4ml\n" + "iRMhGyYpxN7CnfaIwHJr3T0dmbT/BIXOQ2B6sWakioZeUuA6OTBHbFTUN9TUHaF0\n" + "rDMVmjL6BQcEiWwjvtw/3NLdkcKFjMiLTWA2GL71KPGCecpMmAMjo+ijnxeVhqpQ\n" + "dnhowG92DhCSf/XZI0vaaYflrV54U9PgcSPDFWmTOVe5151Mi8eR9qrCanfyHmX1\n" + "MLXs8Mw6xWedNj8AWLV3JGiWEeAEATuTAQfTqmBZbzaFKfSKp5PZjWxa5bZIomzS\n" + "Q0AsONTeYmKK+Pv95RYlgR2kKqhwy3OmcOuepwnzSeAGh1BdBzd2raoipkq1fpY5\n" + "8e75dJnTGvWfqfh0VXz/Wud+hMz/98Mh6Bnp9l+Ddxpp4RioWB2aH0HM8ZGTlbhf\n" + "r5qFmDY7k+RfDDp7K7UYMA+2hHCxY1aFSHVYGRQKdYdKIugLtKx6YKLeGVCR7Gbm\n" + "l/88qiGshF/qhdFbPb4K0Tz2Ug5uklveOQSkKX6RSZ30IW+N3E4nH/wvyOwbCPk7\n" + "u+iHB2zzk2Hws4O52a0Gqj+RbeGzzhl1D9jH35GMHUsfhDSA3/mmrVC7hiN/Aplt\n" + "2OmKFAkobZh/1UJAHBY9feIhLmQUy9dwy0E8G/0LEyyZYEizDC76jsvbh2cPg3jM\n" + "JsI31qUaGggwh3wB034BvsYIf/ZqLCt8hAXF9U5U7T5y3r6FNNBla8zlj25ILog6\n" + "t/bhOwFKYXamAVYMhhvUiA3YIYuBxT7MrgL7gDtKh3N/DleS/pLjmOFfMI3dfCd0\n" + "KSQX46uw7aFbV0Has9uUuGle9Foq52QFvYnDHWJuIyOvJ5st1Hd3Mjjsl9t3JFVM\n" + "I1aDZ17Z4LoThdezNQKGaAe5z7gGFMKKsm55CMT/7FxvConALeQKGAV6jA5xZzl4\n" + "+QB14YlxlZTxYnXd/69KGV56wP8sb6uMVDC/f5Vd3oHsamJKpPgts8WCn11f9wFn\n" + "Mx8YY/vBVVLQMw1aB+82Vk+Ix8YDYIPj5bJk2BkyCCUnMYkKswUOVzsdUq0xssEp\n" + "PASw0YvQ9mY2aQ9exme99JuAj5t4qIXoYTSrX5iv6NXtzDHgTR1pl9gQQVQ0zAUO\n" + "ZHKZXYAv5rLZKRcyeCLw0LkuthY2QtN3PsBlaRtfwZTaqUbBGbvEkcx5fxdEsasS\n" + "yQkZKBBvIi42LUN9ZzywYNGbOanCZ04p/+QscmmnVGuDMZJyaDRaapW6f0nJQ+lQ\n" + "CaVPRzLKGnHV5hWQDjTaPIh2s9rJSZJ3HyE8qshETHW/vQoYIcVB9TX5TnOY02Ak\n" + "IINKfSZGgz/NBeJItjk30UuTcISk65ekoXZIHHgdxD9iHy9D0w6FXcPNLLsWQn7n\n" + "jS4Bvt0VZ9zVAiyyVO4yAaMgP+saitYpjMgI8g67geD3\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_rsa_private_openssh_testkey_passphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDX\n" - "ClCBeHgYyOEqmWpAanz9AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAAB\n" - "AQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe\n" - "453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv\n" - "9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d\n" - "7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY2\n" - "0H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6thmZty\n" - "eb9IsiB0tg2g0JN2VTAGkxqpAAADwG8gm8jZpx+GIKdhV+igcvYvIhzA+fz6UdXf\n" - "d/8wnYzMXtg+Ys7XsKUsxtMD8HGPiuwYsTrd/YGiol7SpkJV0STqtW+UZrcKamJ5\n" - "reFaDoIU8hhWTXCe/ogplTxH/zNNK7Xx5OAGnNWE3zsR1vbZaCv+Vwwa27eUCbpv\n" - "V1+92nBwkah3FCKCbwYDvTVRn1TZHQwnuNxDCRrlwaMjf8eX2ssqLLX7jqrb3j1u\n" - "c28GR3fNJ8ENaWshZ77tqexUQCnCx14/qtT434CMvENXnCP5BP/cRmbOlCFQ6Id7\n" - "nLMW0uDIy/q3xBsAcdMyV0LJW7sJNXIjTnS4lyXd0XescXrqTAKxTkqd1E0VIBpc\n" - "37+7vqv9A9Xxq74jy//L9L4Yrbijc9Vt+oNWFgOuakZGBLIQvm36Oqb0z0oWJcUt\n" - "VdZcvkCNMeixBqCnrQ8egO3x0pnZwo6cwH586Me8FgFacOnzWjzuQT6vYJ4EK5ch\n" - "YNRQpjtz5+T3rZK7eIF1ZUobM4S6di7A6lW9tycQVhjo5XlhalMfCfajhazgcIrY\n" - "Qdaq8+AguP8H+3bvXPZmitL8/mv5uVjqxy1lYh2xLzViTmFnvfdbZ92BWI9C6JBI\n" - "+mRWzXeEY71MjfeEaPStwBm5OYBMFwYrXPL7E3JjAXRxbB+LKUksj/lRk3K7aQp4\n" - "IDKCzAACgkOixfP39BgKQkrLjAoi6mEDqu5Ajc3GoljXsJEkcbu0j+0tVth+41nV\n" - "8yCkP5SVUQTCSKzoduE+0pk6oYO6vrwKLM62cQRPXLl/XNoUqETIe8dklIKojYo6\n" - "3ho1RaHgYr9/NAS0029CFt/rGmONWF9ihKON6wMavJRcofZ25FeylKiP2rrqdDIb\n" - "EiWULZi3MUJfKBwSeZMwaYYmSpaOZF1U/MgvEfeRkE1UmDp3FmBLSNHBYhAxNazH\n" - "R393BTr1zk7h+8s7QK986ZtcKkyUNXEK1NkLLuKlqMwFnjiOdeAIGwz9NEn+Tj60\n" - "jE5IcCE06B6ze/MOZcsPp1SoZv4kKmgWY5Gdqv/9O9SyFQ0Yh4MvBSD8l4x0epId\n" - "8Xm54ISVWP1SZ1x3Oe8yvtwOGqDkZeOVjnP7EQ7R0+1PZzW5P/x47skACqadGChN\n" - "ahbngIl+EhPOqhx+wIfDbtzTmGABgNhcI/d02b8py5MXFnA+uzeSucDREYRdm2TO\n" - "TQQ2CtxB6lcatIYG4AhyouQbujLd/AwpZJ05S1i/Qt6NenTgK3YyTWdXLQnjZSMx\n" - "FBRkf+Jj9eVXieT4PJKtWuvxNNrJVA==\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDX\n" + "ClCBeHgYyOEqmWpAanz9AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAAB\n" + "AQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe\n" + "453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv\n" + "9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d\n" + "7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY2\n" + "0H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6thmZty\n" + "eb9IsiB0tg2g0JN2VTAGkxqpAAADwG8gm8jZpx+GIKdhV+igcvYvIhzA+fz6UdXf\n" + "d/8wnYzMXtg+Ys7XsKUsxtMD8HGPiuwYsTrd/YGiol7SpkJV0STqtW+UZrcKamJ5\n" + "reFaDoIU8hhWTXCe/ogplTxH/zNNK7Xx5OAGnNWE3zsR1vbZaCv+Vwwa27eUCbpv\n" + "V1+92nBwkah3FCKCbwYDvTVRn1TZHQwnuNxDCRrlwaMjf8eX2ssqLLX7jqrb3j1u\n" + "c28GR3fNJ8ENaWshZ77tqexUQCnCx14/qtT434CMvENXnCP5BP/cRmbOlCFQ6Id7\n" + "nLMW0uDIy/q3xBsAcdMyV0LJW7sJNXIjTnS4lyXd0XescXrqTAKxTkqd1E0VIBpc\n" + "37+7vqv9A9Xxq74jy//L9L4Yrbijc9Vt+oNWFgOuakZGBLIQvm36Oqb0z0oWJcUt\n" + "VdZcvkCNMeixBqCnrQ8egO3x0pnZwo6cwH586Me8FgFacOnzWjzuQT6vYJ4EK5ch\n" + "YNRQpjtz5+T3rZK7eIF1ZUobM4S6di7A6lW9tycQVhjo5XlhalMfCfajhazgcIrY\n" + "Qdaq8+AguP8H+3bvXPZmitL8/mv5uVjqxy1lYh2xLzViTmFnvfdbZ92BWI9C6JBI\n" + "+mRWzXeEY71MjfeEaPStwBm5OYBMFwYrXPL7E3JjAXRxbB+LKUksj/lRk3K7aQp4\n" + "IDKCzAACgkOixfP39BgKQkrLjAoi6mEDqu5Ajc3GoljXsJEkcbu0j+0tVth+41nV\n" + "8yCkP5SVUQTCSKzoduE+0pk6oYO6vrwKLM62cQRPXLl/XNoUqETIe8dklIKojYo6\n" + "3ho1RaHgYr9/NAS0029CFt/rGmONWF9ihKON6wMavJRcofZ25FeylKiP2rrqdDIb\n" + "EiWULZi3MUJfKBwSeZMwaYYmSpaOZF1U/MgvEfeRkE1UmDp3FmBLSNHBYhAxNazH\n" + "R393BTr1zk7h+8s7QK986ZtcKkyUNXEK1NkLLuKlqMwFnjiOdeAIGwz9NEn+Tj60\n" + "jE5IcCE06B6ze/MOZcsPp1SoZv4kKmgWY5Gdqv/9O9SyFQ0Yh4MvBSD8l4x0epId\n" + "8Xm54ISVWP1SZ1x3Oe8yvtwOGqDkZeOVjnP7EQ7R0+1PZzW5P/x47skACqadGChN\n" + "ahbngIl+EhPOqhx+wIfDbtzTmGABgNhcI/d02b8py5MXFnA+uzeSucDREYRdm2TO\n" + "TQQ2CtxB6lcatIYG4AhyouQbujLd/AwpZJ05S1i/Qt6NenTgK3YyTWdXLQnjZSMx\n" + "FBRkf+Jj9eVXieT4PJKtWuvxNNrJVA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_rsa_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz\n" - "c2gtcnNhAAAAAwEAAQAAAQEA1717msM2gKxMJCyF08/xFssCzgrakC9z/cipK4c+\n" - "gKZVz9j4ToeacsWtVXxinuOd4zXmASdETraNwbIV4GV26POqC105G3tIDucYt6+V\n" - "o18Yzhb0UUy6LeXzyzQzr/fk57D5pDZ9IiHsj5363DyMo1rec0llH5DQvKDR8bIk\n" - "rIY6Wyb010g4qbpKVMuNXe44OLlhrqJzExe8rux37h6qu5YW9FpcEqX26gG32AEQ\n" - "XXXHXAEcoYseETFmWalWNtB91bI6vG4ygvjSVxXHtT0YPqnDlkHZVxHouHZMwLx7\n" - "30EcdU485KwWegerYZmbcnm/SLIgdLYNoNCTdlUwBpMaqQAAA7iQHqVWkB6lVgAA\n" - "AAdzc2gtcnNhAAABAQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP\n" - "2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjO\n" - "FvRRTLot5fPLNDOv9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpb\n" - "JvTXSDipukpUy41d7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdc\n" - "ARyhix4RMWZZqVY20H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1\n" - "TjzkrBZ6B6thmZtyeb9IsiB0tg2g0JN2VTAGkxqpAAAAAwEAAQAAAQAdjR3uQAkq\n" - "LO+tENAwCE680YgL0x7HG0jnHWJWzQq5so8UjmLM1vRH/l3U1Nnpa8JHyi08QTWx\n" - "Fn5qZstqVluoYyAKuHVHF2bya6NOHeYAX9lU+X3z2O+zs8jmL7tYwjr/pZU8ch5H\n" - "25+8uGYRXtXg1mScJBSO81Y0UE8RrVYqr2Os583yB657kYiVYYYSZlRGd9wmfXnJ\n" - "w0t8LaYcTn+i/lOvrJGa0Q0iV6+4rYmjwYd/D/vyNzF31hUEFrn3vDSgTnJdShgH\n" - "VqW0OwNuEDe/4p8KkKR1EVVj6xv4zicwouY7aQI+zT3MwAzvNdvYwytsIj6bhT9x\n" - "oyeAAIW0vaKVAAAAgQD6pPfu6tb7DiTlaH3/IPdGh3PTIf0zXHZ/ygxORXBZdoLY\n" - "Fq2h/YnBd2Hs8vARAjGJYs78gTPP0FVXPV8ut38xct4DQ2hbPMrjWv5gdhDazq8Q\n" - "qaFEa0+DeYONej8ItKwpsV2Rskkv5Pfm7M6EffVty1uzOpIcT8RYDAYUlc5D/wAA\n" - "AIEA+44ykLho3BDWnUzshVEm6iNoqlZqcDVcNSpCuYDnCy5UrTDk0zj+OUG9M0Zx\n" - "4c7kAmu/poXSimgAgMh9GNCzy3+a70WvH+fBqvG5tXLaSOQCswSdQjltANAnlt5L\n" - "YDHzGGJBsS4pYxoz22MKhFbpYUCQJvotXnZJpTQU6hdFRX8AAACBANuNSlFq/vG8\n" - "Vf9c2YsPiITmOrYxpUDMiMLvUGQOdyIIc45EAggOFHNF3AdPZEhinpD92EK+LiJc\n" - "WYJ26muVcicZoddgmpcHRt2gByC+ckWOM4sLpih6EyQLFZfqTx2X+KOI0ZTt7zEi\n" - "zfm1MJUNDFOr3DM0VBIf34Bn1hU/isPXAAAAAAEC\n" - "-----END OPENSSH PRIVATE KEY-----\n"; - + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz\n" + "c2gtcnNhAAAAAwEAAQAAAQEA1717msM2gKxMJCyF08/xFssCzgrakC9z/cipK4c+\n" + "gKZVz9j4ToeacsWtVXxinuOd4zXmASdETraNwbIV4GV26POqC105G3tIDucYt6+V\n" + "o18Yzhb0UUy6LeXzyzQzr/fk57D5pDZ9IiHsj5363DyMo1rec0llH5DQvKDR8bIk\n" + "rIY6Wyb010g4qbpKVMuNXe44OLlhrqJzExe8rux37h6qu5YW9FpcEqX26gG32AEQ\n" + "XXXHXAEcoYseETFmWalWNtB91bI6vG4ygvjSVxXHtT0YPqnDlkHZVxHouHZMwLx7\n" + "30EcdU485KwWegerYZmbcnm/SLIgdLYNoNCTdlUwBpMaqQAAA7iQHqVWkB6lVgAA\n" + "AAdzc2gtcnNhAAABAQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP\n" + "2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjO\n" + "FvRRTLot5fPLNDOv9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpb\n" + "JvTXSDipukpUy41d7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdc\n" + "ARyhix4RMWZZqVY20H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1\n" + "TjzkrBZ6B6thmZtyeb9IsiB0tg2g0JN2VTAGkxqpAAAAAwEAAQAAAQAdjR3uQAkq\n" + "LO+tENAwCE680YgL0x7HG0jnHWJWzQq5so8UjmLM1vRH/l3U1Nnpa8JHyi08QTWx\n" + "Fn5qZstqVluoYyAKuHVHF2bya6NOHeYAX9lU+X3z2O+zs8jmL7tYwjr/pZU8ch5H\n" + "25+8uGYRXtXg1mScJBSO81Y0UE8RrVYqr2Os583yB657kYiVYYYSZlRGd9wmfXnJ\n" + "w0t8LaYcTn+i/lOvrJGa0Q0iV6+4rYmjwYd/D/vyNzF31hUEFrn3vDSgTnJdShgH\n" + "VqW0OwNuEDe/4p8KkKR1EVVj6xv4zicwouY7aQI+zT3MwAzvNdvYwytsIj6bhT9x\n" + "oyeAAIW0vaKVAAAAgQD6pPfu6tb7DiTlaH3/IPdGh3PTIf0zXHZ/ygxORXBZdoLY\n" + "Fq2h/YnBd2Hs8vARAjGJYs78gTPP0FVXPV8ut38xct4DQ2hbPMrjWv5gdhDazq8Q\n" + "qaFEa0+DeYONej8ItKwpsV2Rskkv5Pfm7M6EffVty1uzOpIcT8RYDAYUlc5D/wAA\n" + "AIEA+44ykLho3BDWnUzshVEm6iNoqlZqcDVcNSpCuYDnCy5UrTDk0zj+OUG9M0Zx\n" + "4c7kAmu/poXSimgAgMh9GNCzy3+a70WvH+fBqvG5tXLaSOQCswSdQjltANAnlt5L\n" + "YDHzGGJBsS4pYxoz22MKhFbpYUCQJvotXnZJpTQU6hdFRX8AAACBANuNSlFq/vG8\n" + "Vf9c2YsPiITmOrYxpUDMiMLvUGQOdyIIc45EAggOFHNF3AdPZEhinpD92EK+LiJc\n" + "WYJ26muVcicZoddgmpcHRt2gByC+ckWOM4sLpih6EyQLFZfqTx2X+KOI0ZTt7zEi\n" + "zfm1MJUNDFOr3DM0VBIf34Bn1hU/isPXAAAAAAEC\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_rsa_public_testkey[] = - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsA5ERRaUFckApnmEAFjLGdFrIN" - "k/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3SP7H9lbCMwq79B" - "hzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8fJmmnIONQOeJZptd" - "AmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0" - "si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6B" - "QMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg52DVm4esZ " - "aris@aris-air\n"; + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsA5ERRaUFckApnmEAFjLGdFrIN" + "k/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3SP7H9lbCMwq79B" + "hzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8fJmmnIONQOeJZptd" + "AmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0" + "si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6B" + "QMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg52DVm4esZ " + "aris@aris-air\n"; + +static const char torture_rsa_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAOREUWlBXJAKZ5hABYy\n" + "xnRayDZP1bJeLbPVK+npxemrhHyZgjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZW\n" + "wjMKu/QYcycYp5HL01goxOxuusZbi+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDj\n" + "UDniWabXQJge8ksGXGTiFeAJ/687uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqe\n" + "GDzyeBZ1NLIuaiOORyLGSW4duHLDN78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9U\n" + "w4XQFTsWugUDEY1AU4c5g11nhzHzBi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHr\n" + "GQIDAQAB\n" + "-----END PUBLIC KEY-----\n"; static const char torture_rsa_testkey_cert[] = - "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNz" - "aC5jb20AAAAgL77S/SgY969FbEtNBsbLvvtGFgnEHaPb+V7ajwuf+R0AAAADAQABA" - "AABAQCsA5ERRaUFckApnmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34" - "lHJaO+WXbDYYj7duW3SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQ" - "x1cztTzZTuUD+P/8fJmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYV" - "DHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAl" - "CZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkP" - "iPSADYs4ekeGg52DVm4esZAAAAAAAAAAAAAAABAAAADmxpYnNzaF90b3J0dXJlAAA" - "AAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRp" - "bmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" - "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdX" - "Nlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAoowcv2Gn8tO" - "eDyw/lgdMpoBsLtHTTdVVOOo5HwMFvj/lFkbZlb6J2n9GIE64HNPE45vSnIdJZwz4" - "UYfTvtnNKNHp1MgMrjK1Z6EjyZsGqDZ+BhmvcKA6IckkhBJnDV7U9dMrovAWha61Z" - "9GpDqB1naRfbwqJQwSRHF1p71Cnf0fZKxOhAVx0ophmYGz3x3qq4PeOZv3Yl0AHTV" - "dRmqmeELDUxeuXN2bgSyb881zEgdaKHH5oWySykP4uwjn6T7ETuL2MsDdG3HZHDhn" - "LzLmfzOZ/cNadMCrgauMluQKc5dYF2TSeDaUxwun/NPMQBVZdETHLAMBgkGmhRUku" - "flVDIQAAAQ8AAAAHc3NoLXJzYQAAAQADSp4b/Zta8zs6v47iwmxV2Gbucvt1kDrvT" - "vKAKSbGN0+zoMyXiNfMHM/OvZObDS/WWGs4GMRqbJavwO3ja/dQY17oJss23lZ+Rc" - "Lw4Rqsi3/ZEPCnX6ficiRS/yRN/LAkoXvx9vBx9QHfxlzF6JXq07wTt21zxW0tntd" - "8dL+JI9ZZ9YylnxF3gHqfRFe2ahJpiywmxm0yOZgDmimOhep59i6BH5zHiPALvpge" - "Mbk075oA5K9XKsHTflCcsQRQH+pXqaNQGL37z2CFz9oezxQYvIqqKF0w/eeRIARoA" - "neB6OdgTpKFsmgPZVtqrvhjw+b5T8a4W4iWSl+6wg6gowAm " - "rsa_privkey.pub\n"; + "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNz" + "aC5jb20AAAAgL77S/SgY969FbEtNBsbLvvtGFgnEHaPb+V7ajwuf+R0AAAADAQABA" + "AABAQCsA5ERRaUFckApnmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34" + "lHJaO+WXbDYYj7duW3SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQ" + "x1cztTzZTuUD+P/8fJmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYV" + "DHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAl" + "CZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkP" + "iPSADYs4ekeGg52DVm4esZAAAAAAAAAAAAAAABAAAADmxpYnNzaF90b3J0dXJlAAA" + "AAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRp" + "bmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdX" + "Nlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAoowcv2Gn8tO" + "eDyw/lgdMpoBsLtHTTdVVOOo5HwMFvj/lFkbZlb6J2n9GIE64HNPE45vSnIdJZwz4" + "UYfTvtnNKNHp1MgMrjK1Z6EjyZsGqDZ+BhmvcKA6IckkhBJnDV7U9dMrovAWha61Z" + "9GpDqB1naRfbwqJQwSRHF1p71Cnf0fZKxOhAVx0ophmYGz3x3qq4PeOZv3Yl0AHTV" + "dRmqmeELDUxeuXN2bgSyb881zEgdaKHH5oWySykP4uwjn6T7ETuL2MsDdG3HZHDhn" + "LzLmfzOZ/cNadMCrgauMluQKc5dYF2TSeDaUxwun/NPMQBVZdETHLAMBgkGmhRUku" + "flVDIQAAAQ8AAAAHc3NoLXJzYQAAAQADSp4b/Zta8zs6v47iwmxV2Gbucvt1kDrvT" + "vKAKSbGN0+zoMyXiNfMHM/OvZObDS/WWGs4GMRqbJavwO3ja/dQY17oJss23lZ+Rc" + "Lw4Rqsi3/ZEPCnX6ficiRS/yRN/LAkoXvx9vBx9QHfxlzF6JXq07wTt21zxW0tntd" + "8dL+JI9ZZ9YylnxF3gHqfRFe2ahJpiywmxm0yOZgDmimOhep59i6BH5zHiPALvpge" + "Mbk075oA5K9XKsHTflCcsQRQH+pXqaNQGL37z2CFz9oezxQYvIqqKF0w/eeRIARoA" + "neB6OdgTpKFsmgPZVtqrvhjw+b5T8a4W4iWSl+6wg6gowAm " + "rsa_privkey.pub\n"; /**************************************************************************** * DSA KEYS ****************************************************************************/ static const char torture_dsa_private_testkey[] = - "-----BEGIN DSA PRIVATE KEY-----\n" - "MIIBuwIBAAKBgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" - "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" - "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwIVAK84\n" - "j20GmU+zewjQwsIXuVb6C/PHAoGAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHOzMB1\n" - "xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzPiXDu\n" - "l0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GSBZPX\n" - "MpUdng8CgYB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7\n" - "n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2u\n" - "ADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgIVAI1Hd8/i\n" - "Pzsg7bTzoNvjQL+Noyiy\n" - "-----END DSA PRIVATE KEY-----\n"; + "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwIVAK84\n" + "j20GmU+zewjQwsIXuVb6C/PHAoGAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHOzMB1\n" + "xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzPiXDu\n" + "l0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GSBZPX\n" + "MpUdng8CgYB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7\n" + "n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2u\n" + "ADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgIVAI1Hd8/i\n" + "Pzsg7bTzoNvjQL+Noyiy\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIBSwIBADCCASsGByqGSM44BAEwggEeAoGBAJTK9U8SSfdSdkOPMLNLNIelOW3O\n" + "vQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5Dj\n" + "T0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrW\n" + "XK74JNRTKHblo0MXAhUArziPbQaZT7N7CNDCwhe5VvoL88cCgYBeG4hWwnFRAnmd\n" + "ZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJ\n" + "ev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcX\n" + "fXTh9tVravBqeIuXgZIFk9cylR2eDwQXAhUAjUd3z+I/OyDttPOg2+NAv42jKLI=\n" + "-----END PRIVATE KEY-----\n"; static const char torture_dsa_private_testkey_passphrase[] = - "-----BEGIN DSA PRIVATE KEY-----\n" - "Proc-Type: 4,ENCRYPTED\n" - "DEK-Info: AES-128-CBC,266023B64B1B814BCD0D0E477257F06D\n" - "\n" - "QJQErZrvYsfeMNMnU+6yVHH5Zze/zUFdPip7Bon4T1wCGlVasn4x/GQcMm1+mgmb\n" - "PCK/qJ5qw9nCepLYJq2xh8gohbwF/XKxeaNGcRA2+ancTooDUjeRTlk1WRtS1+bq\n" - "LBkwhxLXW26lIuQUHzfi93rRqQI2LC4McngY7L7WVJer7sH7hk5//4Gf6zHtPEl+\n" - "Tr2ub1zNrVbh6e1Bitw7DaGZNX6XEWpyTTsAd42sQWh6o23MC6GyfS1YFsPGHzGe\n" - "WYQbWn2AZ1mK32z2mLZfVg41qu9RKG20iCyaczZ2YmuYyOkoLHijOAHC8vZbHwYC\n" - "+lN9Yc8/BoMuMMwDTMDaJD0TsBX02hi9YI7Gu88PMCJO+SRe5400MonUMXTwCa91\n" - "Tt3RhYpBzx2XGOq5199+oLdTJAaXHJcuB6viKNdSLBuhx6RAEJXZnVexchaHs4Q6\n" - "HweIv6Et8MjVoqwkaQDmcIGA73qZ0lbUJFZAu2YDJ6TpHc1lHZes763HoMYfuvkX\n" - "HTSuHZ7edjoWqwnl/vkc3+nG//IEj8LqAacx0i4krDcQpGuQ6BnPfwPFco2NQQpw\n" - "wHBOL6HrOnD+gGs6DUFwzA==\n" - "-----END DSA PRIVATE KEY-----\n"; + "-----BEGIN DSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,266023B64B1B814BCD0D0E477257F06D\n" + "\n" + "QJQErZrvYsfeMNMnU+6yVHH5Zze/zUFdPip7Bon4T1wCGlVasn4x/GQcMm1+mgmb\n" + "PCK/qJ5qw9nCepLYJq2xh8gohbwF/XKxeaNGcRA2+ancTooDUjeRTlk1WRtS1+bq\n" + "LBkwhxLXW26lIuQUHzfi93rRqQI2LC4McngY7L7WVJer7sH7hk5//4Gf6zHtPEl+\n" + "Tr2ub1zNrVbh6e1Bitw7DaGZNX6XEWpyTTsAd42sQWh6o23MC6GyfS1YFsPGHzGe\n" + "WYQbWn2AZ1mK32z2mLZfVg41qu9RKG20iCyaczZ2YmuYyOkoLHijOAHC8vZbHwYC\n" + "+lN9Yc8/BoMuMMwDTMDaJD0TsBX02hi9YI7Gu88PMCJO+SRe5400MonUMXTwCa91\n" + "Tt3RhYpBzx2XGOq5199+oLdTJAaXHJcuB6viKNdSLBuhx6RAEJXZnVexchaHs4Q6\n" + "HweIv6Et8MjVoqwkaQDmcIGA73qZ0lbUJFZAu2YDJ6TpHc1lHZes763HoMYfuvkX\n" + "HTSuHZ7edjoWqwnl/vkc3+nG//IEj8LqAacx0i4krDcQpGuQ6BnPfwPFco2NQQpw\n" + "wHBOL6HrOnD+gGs6DUFwzA==\n" + "-----END DSA PRIVATE KEY-----\n"; static const char torture_dsa_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIIBrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8001emUNAOECAggA\n" - "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBDgXXvQsVxY6zaAQVwzUwvDBIIB\n" - "UOBQqqJs4rYK6R0rXFitkdUodOK3CdFAKodyCkSC5cgoW2+ht2ndRCepxuKB2X14\n" - "Lvt1CIxPvu1k7bGnd25kePmNF85cJxG9wf0/+6vpptO3fTUdsUKyLcRKDqvxxOMB\n" - "OSqQK1MLgvUxB5uBSGCsKqFkVUPYs46uihfozjqHH2IghHSQr+VczhFDoWtzgcgp\n" - "nRNZiyXN5Thob5WOrL849TSlcaMyI3ssErEVP1G2t3ax5bLQ4AqDddumoRBed/XY\n" - "lad5QGAS2XlwMFj8tR/Spi1fEWfamIsvh23ba5ksb35TT3SUJd2gf2NC7QEz3dUK\n" - "YDSSeRSF24c4nXBsJ94TkVuUujo4X3QSaWQ2anYYBBwfQtrddVNVu95QS2sQGLov\n" - "UWIhq1xXbnL/SGC6E5T1VGnAx3qwfDEZX5tTNzkwqeTZfkrb6vRk+O+Lxt67iP+n\n" - "nw==\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8001emUNAOECAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBDgXXvQsVxY6zaAQVwzUwvDBIIB\n" + "UOBQqqJs4rYK6R0rXFitkdUodOK3CdFAKodyCkSC5cgoW2+ht2ndRCepxuKB2X14\n" + "Lvt1CIxPvu1k7bGnd25kePmNF85cJxG9wf0/+6vpptO3fTUdsUKyLcRKDqvxxOMB\n" + "OSqQK1MLgvUxB5uBSGCsKqFkVUPYs46uihfozjqHH2IghHSQr+VczhFDoWtzgcgp\n" + "nRNZiyXN5Thob5WOrL849TSlcaMyI3ssErEVP1G2t3ax5bLQ4AqDddumoRBed/XY\n" + "lad5QGAS2XlwMFj8tR/Spi1fEWfamIsvh23ba5ksb35TT3SUJd2gf2NC7QEz3dUK\n" + "YDSSeRSF24c4nXBsJ94TkVuUujo4X3QSaWQ2anYYBBwfQtrddVNVu95QS2sQGLov\n" + "UWIhq1xXbnL/SGC6E5T1VGnAx3qwfDEZX5tTNzkwqeTZfkrb6vRk+O+Lxt67iP+n\n" + "nw==\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_dsa_private_openssh_testkey_passphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBC\n" - "UZK61oXs3uKMs4l7G0cpAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAJTK9U8S\n" - "SfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+f\n" - "AC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hod\n" - "Qn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW\n" - "+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8c\n" - "NlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0\n" - "kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQ\n" - "Bfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv\n" - "1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/\n" - "iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgAAAeAtGFEW6JZTeSumizZJI4T2\n" - "Kha05Ze3juTeW+BMjqTcf77yAL2jvsljogCtu4+5CWWO4g+cr80vyVytji6IYTNM\n" - "MPn1qe6dHXnfmgtiegHXxrjr5v5/i1cvD32Bxffy+yjR9kbV9GJYF+K5pfYVpQBa\n" - "XVmq6AJUPd/yxKw6jRGZJi8GTcrKbCZAL+VYSPwc0veCrmGPjeeMCgYcEXPvhSui\n" - "P0JnG1Ap12FeK+61rIbZBAr7qbTGJi5Z5HlDlgon2tmMZOkIuL1Oytgut4MpmYjP\n" - "ph+qrzgwfSwOsjVIuHlb1L0phWRlgbT8lmysEE7McGKWiCOabxgl3NF9lClhDBb9\n" - "nzupkK1cg/4p17USYMOdeNhTmJ0DkQT+8UenfBOmzV7kamLlEYXJdDZBN//dZ8UR\n" - "KEzAzpaAVIyJQ+wvCUIh/VO8sJP+3q4XQUkv0QcIRlc0+r9qbW2Tqv3vajFcFtK6\n" - "nrTmIJVL0pG+z/93Ncpy5susD+JvhJ4yfl7Jet3jy4fWwm3qkLl0WsobJ7Om+GyH\n" - "DzHH9RgDk3XuUHS/fz+kTwmtyIH/Rq1jIt+s+T8iA9CzKSX6sBu2yfMo1w2/LbCx\n" - "Xy1rHS42TePw28m1cQuUfjqdOC3IBgQ1m3x2f1on7hk=\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBC\n" + "UZK61oXs3uKMs4l7G0cpAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAJTK9U8S\n" + "SfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+f\n" + "AC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hod\n" + "Qn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW\n" + "+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8c\n" + "NlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0\n" + "kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQ\n" + "Bfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv\n" + "1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/\n" + "iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgAAAeAtGFEW6JZTeSumizZJI4T2\n" + "Kha05Ze3juTeW+BMjqTcf77yAL2jvsljogCtu4+5CWWO4g+cr80vyVytji6IYTNM\n" + "MPn1qe6dHXnfmgtiegHXxrjr5v5/i1cvD32Bxffy+yjR9kbV9GJYF+K5pfYVpQBa\n" + "XVmq6AJUPd/yxKw6jRGZJi8GTcrKbCZAL+VYSPwc0veCrmGPjeeMCgYcEXPvhSui\n" + "P0JnG1Ap12FeK+61rIbZBAr7qbTGJi5Z5HlDlgon2tmMZOkIuL1Oytgut4MpmYjP\n" + "ph+qrzgwfSwOsjVIuHlb1L0phWRlgbT8lmysEE7McGKWiCOabxgl3NF9lClhDBb9\n" + "nzupkK1cg/4p17USYMOdeNhTmJ0DkQT+8UenfBOmzV7kamLlEYXJdDZBN//dZ8UR\n" + "KEzAzpaAVIyJQ+wvCUIh/VO8sJP+3q4XQUkv0QcIRlc0+r9qbW2Tqv3vajFcFtK6\n" + "nrTmIJVL0pG+z/93Ncpy5susD+JvhJ4yfl7Jet3jy4fWwm3qkLl0WsobJ7Om+GyH\n" + "DzHH9RgDk3XuUHS/fz+kTwmtyIH/Rq1jIt+s+T8iA9CzKSX6sBu2yfMo1w2/LbCx\n" + "Xy1rHS42TePw28m1cQuUfjqdOC3IBgQ1m3x2f1on7hk=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_dsa_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdz\n" - "c2gtZHNzAAAAgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" - "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" - "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwAAABUA\n" - "rziPbQaZT7N7CNDCwhe5VvoL88cAAACAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHO\n" - "zMB1xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzP\n" - "iXDul0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GS\n" - "BZPXMpUdng8AAACAeK6A0AX4H/AKEgCQG+8vDlb8beXE+4qmkTEF9l4LVtPTbvUW\n" - "1CFUu59psN4qmRtMZxt4r9aC/YMDVzC5X/oC5rKlN+/PI2hGfSIWfFXYBwKB5ulF\n" - "jO29rgA5oYqyOnUWVFTvv4jBlLw8WuujiOIQ1dOeaW8EIbcbqVGrU4cg8yIAAAHY\n" - "tbI937WyPd8AAAAHc3NoLWRzcwAAAIEAlMr1TxJJ91J2Q48ws0s0h6U5bc69BHPt\n" - "Zs/yQAoxzASfQv1vz8Lx98o5H6BYYURL6GOyD58ALuW3McPE5CEqu2ETkONPT6sM\n" - "D78tDdxlKf/pCktuvASCflSjedd10X+3i9XuGh1CfmpfcdScU5nOk9BgatZcrvgk\n" - "1FModuWjQxcAAAAVAK84j20GmU+zewjQwsIXuVb6C/PHAAAAgF4biFbCcVECeZ1k\n" - "ES3+6N1xBkPhHFZhzszAdcQrB42J6RIYXKPq/xw2Ug99ZMw3gGVMnM99Vqhi/Ml6\n" - "/jmUS6hLBgu3cBAMz4lw7pdMx3JdMoij60ay4DSR3inE+iX5QZ4Cu40ESFVINxd9\n" - "dOH21Wtq8Gp4i5eBkgWT1zKVHZ4PAAAAgHiugNAF+B/wChIAkBvvLw5W/G3lxPuK\n" - "ppExBfZeC1bT0271FtQhVLufabDeKpkbTGcbeK/Wgv2DA1cwuV/6AuaypTfvzyNo\n" - "Rn0iFnxV2AcCgebpRYztva4AOaGKsjp1FlRU77+IwZS8PFrro4jiENXTnmlvBCG3\n" - "G6lRq1OHIPMiAAAAFQCNR3fP4j87IO2086Db40C/jaMosgAAAAABAg==\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdz\n" + "c2gtZHNzAAAAgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwAAABUA\n" + "rziPbQaZT7N7CNDCwhe5VvoL88cAAACAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHO\n" + "zMB1xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzP\n" + "iXDul0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GS\n" + "BZPXMpUdng8AAACAeK6A0AX4H/AKEgCQG+8vDlb8beXE+4qmkTEF9l4LVtPTbvUW\n" + "1CFUu59psN4qmRtMZxt4r9aC/YMDVzC5X/oC5rKlN+/PI2hGfSIWfFXYBwKB5ulF\n" + "jO29rgA5oYqyOnUWVFTvv4jBlLw8WuujiOIQ1dOeaW8EIbcbqVGrU4cg8yIAAAHY\n" + "tbI937WyPd8AAAAHc3NoLWRzcwAAAIEAlMr1TxJJ91J2Q48ws0s0h6U5bc69BHPt\n" + "Zs/yQAoxzASfQv1vz8Lx98o5H6BYYURL6GOyD58ALuW3McPE5CEqu2ETkONPT6sM\n" + "D78tDdxlKf/pCktuvASCflSjedd10X+3i9XuGh1CfmpfcdScU5nOk9BgatZcrvgk\n" + "1FModuWjQxcAAAAVAK84j20GmU+zewjQwsIXuVb6C/PHAAAAgF4biFbCcVECeZ1k\n" + "ES3+6N1xBkPhHFZhzszAdcQrB42J6RIYXKPq/xw2Ug99ZMw3gGVMnM99Vqhi/Ml6\n" + "/jmUS6hLBgu3cBAMz4lw7pdMx3JdMoij60ay4DSR3inE+iX5QZ4Cu40ESFVINxd9\n" + "dOH21Wtq8Gp4i5eBkgWT1zKVHZ4PAAAAgHiugNAF+B/wChIAkBvvLw5W/G3lxPuK\n" + "ppExBfZeC1bT0271FtQhVLufabDeKpkbTGcbeK/Wgv2DA1cwuV/6AuaypTfvzyNo\n" + "Rn0iFnxV2AcCgebpRYztva4AOaGKsjp1FlRU77+IwZS8PFrro4jiENXTnmlvBCG3\n" + "G6lRq1OHIPMiAAAAFQCNR3fP4j87IO2086Db40C/jaMosgAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_dsa_public_testkey[] = - "ssh-dss AAAAB3NzaC1kc3MAAACBAJTK9U8SSfdSdkOPMLNLNIelOW3OvQRz7WbP8k" - "AKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3c" - "ZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0" - "MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD" - "4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3" - "AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuX" - "gZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9R" - "bUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM" - "7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIg==\n"; + "ssh-dss AAAAB3NzaC1kc3MAAACBAJTK9U8SSfdSdkOPMLNLNIelOW3OvQRz7WbP8k" + "AKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3c" + "ZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0" + "MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD" + "4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3" + "AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuX" + "gZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9R" + "bUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM" + "7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIg==\n"; static const char torture_dsa_testkey_cert[] = - "ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNza" - "C5jb20AAAAgKAd9MpIBrzctQyJvCYYJ2WUD5fyWlXMSv1G/3VihbCAAAACBAJTK9U8" - "SSfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fA" - "C7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5" - "qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzx" - "wAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWT" - "MN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+" - "UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJA" - "b7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+" - "gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hD" - "V055pbwQhtxupUatThyDzIgAAAAAAAAAAAAAAAQAAAA5saWJzc2hfdG9ydHVyZQAAA" - "AAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5" - "nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvc" - "nQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI" - "tcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKKMHL9hp/LTng8sP" - "5YHTKaAbC7R003VVTjqOR8DBb4/5RZG2ZW+idp/RiBOuBzTxOOb0pyHSWcM+FGH077" - "ZzSjR6dTIDK4ytWehI8mbBqg2fgYZr3CgOiHJJIQSZw1e1PXTK6LwFoWutWfRqQ6gd" - "Z2kX28KiUMEkRxdae9Qp39H2SsToQFcdKKYZmBs98d6quD3jmb92JdAB01XUZqpnhC" - "w1MXrlzdm4Esm/PNcxIHWihx+aFskspD+LsI5+k+xE7i9jLA3Rtx2Rw4Zy8y5n8zmf" - "3DWnTAq4GrjJbkCnOXWBdk0ng2lMcLp/zTzEAVWXRExywDAYJBpoUVJLn5VQyEAAAE" - "PAAAAB3NzaC1yc2EAAAEAAt4V9aGqeahOfUvhG7M8/Mn26aLB/HXbICYFJF7dY6urm" - "SIoS2KBqISCFGXTituiwGlZeAJ+pVgCMYo07Nxtd6oqIjsgKfJqDNx7e4pGw/YJnkm" - "BqMO/k/ygu2mLmQF0lnpmG2KyjKEljMibHaKlFkcVNbwfOb4p8N3OHm66g5mbCUTRZ" - "DHqMSJb3YtnObLexD13RydwxkG5AfCnOWxy5O4agXGEYwr/48AQBHYg9obGtpD1qyF" - "4mMXgzaLViFtcwah6wHGlW0UPQMvrq/RqigAkyUszSccfibkIXJ+wGAgsRYhVAMwME" - "JqPZ6GHOEIjLBKUegsclHb7Pk0YO8Auaw== " - "aris@aris-air\n"; + "ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNza" + "C5jb20AAAAgKAd9MpIBrzctQyJvCYYJ2WUD5fyWlXMSv1G/3VihbCAAAACBAJTK9U8" + "SSfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fA" + "C7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5" + "qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzx" + "wAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWT" + "MN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+" + "UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJA" + "b7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+" + "gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hD" + "V055pbwQhtxupUatThyDzIgAAAAAAAAAAAAAAAQAAAA5saWJzc2hfdG9ydHVyZQAAA" + "AAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5" + "nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvc" + "nQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI" + "tcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKKMHL9hp/LTng8sP" + "5YHTKaAbC7R003VVTjqOR8DBb4/5RZG2ZW+idp/RiBOuBzTxOOb0pyHSWcM+FGH077" + "ZzSjR6dTIDK4ytWehI8mbBqg2fgYZr3CgOiHJJIQSZw1e1PXTK6LwFoWutWfRqQ6gd" + "Z2kX28KiUMEkRxdae9Qp39H2SsToQFcdKKYZmBs98d6quD3jmb92JdAB01XUZqpnhC" + "w1MXrlzdm4Esm/PNcxIHWihx+aFskspD+LsI5+k+xE7i9jLA3Rtx2Rw4Zy8y5n8zmf" + "3DWnTAq4GrjJbkCnOXWBdk0ng2lMcLp/zTzEAVWXRExywDAYJBpoUVJLn5VQyEAAAE" + "PAAAAB3NzaC1yc2EAAAEAAt4V9aGqeahOfUvhG7M8/Mn26aLB/HXbICYFJF7dY6urm" + "SIoS2KBqISCFGXTituiwGlZeAJ+pVgCMYo07Nxtd6oqIjsgKfJqDNx7e4pGw/YJnkm" + "BqMO/k/ygu2mLmQF0lnpmG2KyjKEljMibHaKlFkcVNbwfOb4p8N3OHm66g5mbCUTRZ" + "DHqMSJb3YtnObLexD13RydwxkG5AfCnOWxy5O4agXGEYwr/48AQBHYg9obGtpD1qyF" + "4mMXgzaLViFtcwah6wHGlW0UPQMvrq/RqigAkyUszSccfibkIXJ+wGAgsRYhVAMwME" + "JqPZ6GHOEIjLBKUegsclHb7Pk0YO8Auaw== " + "aris@aris-air\n"; /**************************************************************************** * ECDSA KEYS ****************************************************************************/ static const char torture_ecdsa256_private_testkey[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "MHcCAQEEIBCDeeYYAtX3EnsP0ratwVpNTaA/4K1N6VvHMiUZlVdhoAoGCCqGSM49\n" - "AwEHoUQDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7VtFHA0EbNHqBUQxoh+Ik\n" - "89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIBCDeeYYAtX3EnsP0ratwVpNTaA/4K1N6VvHMiUZlVdhoAoGCCqGSM49\n" + "AwEHoUQDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7VtFHA0EbNHqBUQxoh+Ik\n" + "89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEIN55hgC1fcSew/S\n" + "tq3BWk1NoD/grU3pW8cyJRmVV2GhRANCAATH7253zxDkZa0t37Iy1hpqkLzmD7Zk\n" + "untW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1BwvsTqJ\n" + "-----END PRIVATE KEY-----\n"; static const char torture_ecdsa256_private_testkey_passphrase[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "Proc-Type: 4,ENCRYPTED\n" - "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" - "\n" - "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" - "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" - "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; static const char torture_ecdsa256_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhvndbkbElTnAICCAAw\n" - "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOu4ierPcQpcA9RJNHUbTCoEgZBe\n" - "iusOkUYp4JZJEIpi98VlqnROzDXHpTTpEGiUDC/k+cuKvoPop5+Jx0qXp+A1NJxu\n" - "kx3j+U0ISGY7J6b2Pqt1msC/FzqpeFM7ybuHDRz+c5ZBONTp8wrs52d5NdjrYguz\n" - "UO6n9+yydSsO0FqbwPaqNZ6goBN0TfhYnToG4ZPJxlHa7gf7Su4KSMYKZdOtfx4=\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhvndbkbElTnAICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOu4ierPcQpcA9RJNHUbTCoEgZBe\n" + "iusOkUYp4JZJEIpi98VlqnROzDXHpTTpEGiUDC/k+cuKvoPop5+Jx0qXp+A1NJxu\n" + "kx3j+U0ISGY7J6b2Pqt1msC/FzqpeFM7ybuHDRz+c5ZBONTp8wrs52d5NdjrYguz\n" + "UO6n9+yydSsO0FqbwPaqNZ6goBN0TfhYnToG4ZPJxlHa7gf7Su4KSMYKZdOtfx4=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_ecdsa256_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNl\n" - "Y2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTH7253zxDkZa0t37Iy\n" - "1hpqkLzmD7ZkuntW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1Bwv\n" - "sTqJAAAAmOuDchHrg3IRAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy\n" - "NTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPT\n" - "Ja+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAgEIN55hgC1fcSew/Stq3BWk1NoD/g\n" - "rU3pW8cyJRmVV2EAAAAA\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTH7253zxDkZa0t37Iy\n" + "1hpqkLzmD7ZkuntW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1Bwv\n" + "sTqJAAAAmOuDchHrg3IRAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy\n" + "NTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPT\n" + "Ja+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAgEIN55hgC1fcSew/Stq3BWk1NoD/g\n" + "rU3pW8cyJRmVV2EAAAAA\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa256_private_openssh_testkey_pasphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA+\n" - "O0w3yPZF2q0FjVBhQjn2AAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAy\n" - "NTYAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNB\n" - "GzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAACghvb4EX8M06UB\n" - "zigxOn9bg5cZkZ2yWY8jzxtOWH4YJXsuhON/jePDJuI2ro5u4iKFD1u2JLfcshdh\n" - "vKZyjixU9KdewykQQt/wFkrCfNUyCH8jFiQsAqhBfopRFyDJV9pmcUBL/3fJqwut\n" - "ZeBSfA7tXORp3xrwFI1tXiiUCM+/nhxiCsFaCJXeiM3tN+kFtwQ8kamINqwaC8Vj\n" - "lFLKHDfwJQ==\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA+\n" + "O0w3yPZF2q0FjVBhQjn2AAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAy\n" + "NTYAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNB\n" + "GzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAACghvb4EX8M06UB\n" + "zigxOn9bg5cZkZ2yWY8jzxtOWH4YJXsuhON/jePDJuI2ro5u4iKFD1u2JLfcshdh\n" + "vKZyjixU9KdewykQQt/wFkrCfNUyCH8jFiQsAqhBfopRFyDJV9pmcUBL/3fJqwut\n" + "ZeBSfA7tXORp3xrwFI1tXiiUCM+/nhxiCsFaCJXeiM3tN+kFtwQ8kamINqwaC8Vj\n" + "lFLKHDfwJQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa256_public_testkey[] = - "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" - "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" - "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" + "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" + "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; + +static const char torture_ecdsa256_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2\n" + "ZLp7VtFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END PUBLIC KEY-----\n"; static const char torture_ecdsa256_testkey_cert[] = - "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" - "HAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHvXWcdSrQeZL2/Z68V8ntbL7rDo" - "Qwrsc+ps6HbMGZrkAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPt" - "mS6e1bRRwNBGzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAAAAA" - "AAAAAAAEAAAAHbXlpZGVudAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVc" - "GVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGl" - "uZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0e" - "QAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1" - "uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7V" - "tFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQAAAGQAAAATZWN" - "kc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALDSBnmFF59tgTKDQ4meTJEI7/BP2Zgf1" - "AKg1H3kIijQAAAAIFYrqSg6GI03ohXqUVsZ3lCB/XIism2aV5Vz2bg1d9zo " - "./ec256.pub"; + "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHvXWcdSrQeZL2/Z68V8ntbL7rDo" + "Qwrsc+ps6HbMGZrkAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPt" + "mS6e1bRRwNBGzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAAAAA" + "AAAAAAAEAAAAHbXlpZGVudAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVc" + "GVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGl" + "uZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0e" + "QAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1" + "uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7V" + "tFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQAAAGQAAAATZWN" + "kc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALDSBnmFF59tgTKDQ4meTJEI7/BP2Zgf1" + "AKg1H3kIijQAAAAIFYrqSg6GI03ohXqUVsZ3lCB/XIism2aV5Vz2bg1d9zo " + "./ec256.pub"; static const char torture_ecdsa384_private_testkey[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "MIGkAgEBBDBY8jEa5DtRy4AVeTWhPJ/TK257behiC3uafEi6YA2oHORibqX55EDN\n" - "wz29MT40mQSgBwYFK4EEACKhZANiAARXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1P\n" - "bJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYC\n" - "jkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "MIGkAgEBBDBY8jEa5DtRy4AVeTWhPJ/TK257behiC3uafEi6YA2oHORibqX55EDN\n" + "wz29MT40mQSgBwYFK4EEACKhZANiAARXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1P\n" + "bJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYC\n" + "jkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBY8jEa5DtRy4AVeTWh\n" + "PJ/TK257behiC3uafEi6YA2oHORibqX55EDNwz29MT40mQShZANiAARXc4BN6BrV\n" + "o1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdzt\n" + "J0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END PRIVATE KEY-----\n"; static const char torture_ecdsa384_private_testkey_passphrase[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "Proc-Type: 4,ENCRYPTED\n" - "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" - "\n" - "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" - "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" - "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; static const char torture_ecdsa384_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIEuMnFkuHkDkCAggA\n" - "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/fjhqXxV/Dk7cg8XgPxzuBIHA\n" - "TbiloDCPfKKlkm9ZguahtfJOxcVBbMtrFAK2vA/jMXGnbB9Qe13uLl8fTd6QB4tE\n" - "Zbyucq4OA0L2HyhuEsJiLvf0ICX8APrBajNv3B8F7ZStrXx7hcJUg8qTlsbdovYq\n" - "nCjOKoq/F6ax/r1F9Rr5PlXQDoSKDJ3mQkZc4n8VNKFfXOPQ7C4rEYzglSyzGwyQ\n" - "2EwRwnkkJqcYotRyH4JWtXCRak7znLVDeGbavhpP6paSVsK8OpycAoJstfQb0L4q\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIEuMnFkuHkDkCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/fjhqXxV/Dk7cg8XgPxzuBIHA\n" + "TbiloDCPfKKlkm9ZguahtfJOxcVBbMtrFAK2vA/jMXGnbB9Qe13uLl8fTd6QB4tE\n" + "Zbyucq4OA0L2HyhuEsJiLvf0ICX8APrBajNv3B8F7ZStrXx7hcJUg8qTlsbdovYq\n" + "nCjOKoq/F6ax/r1F9Rr5PlXQDoSKDJ3mQkZc4n8VNKFfXOPQ7C4rEYzglSyzGwyQ\n" + "2EwRwnkkJqcYotRyH4JWtXCRak7znLVDeGbavhpP6paSVsK8OpycAoJstfQb0L4q\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_ecdsa384_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNl\n" - "Y2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRXc4BN6BrVo1QMi3+i\n" - "/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4\n" - "V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZoAAADIITfDfiE3w34A\n" - "AAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEV3OATega1aNU\n" - "DIt/ovwfOS7u0jLswYvtT2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG\n" - "2KMk+Fdwd2akfUchqupmAo5F5XOWG2B47ngW6dKHjV6rFxzQFkGaAAAAMFjyMRrk\n" - "O1HLgBV5NaE8n9Mrbntt6GILe5p8SLpgDagc5GJupfnkQM3DPb0xPjSZBAAAAAA=\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRXc4BN6BrVo1QMi3+i\n" + "/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4\n" + "V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZoAAADIITfDfiE3w34A\n" + "AAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEV3OATega1aNU\n" + "DIt/ovwfOS7u0jLswYvtT2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG\n" + "2KMk+Fdwd2akfUchqupmAo5F5XOWG2B47ngW6dKHjV6rFxzQFkGaAAAAMFjyMRrk\n" + "O1HLgBV5NaE8n9Mrbntt6GILe5p8SLpgDagc5GJupfnkQM3DPb0xPjSZBAAAAAA=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa384_private_openssh_testkey_passphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB4N\n" - "dKGEoxFeg6dqiR2vTl6AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzOD\n" - "QAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5\n" - "LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtg\n" - "eO54FunSh41eqxcc0BZBmgAAANDOL7sWcylFf8SsjGVFvr36mpyUBpAJ/e7o4RbQg\n" - "H8FDu1IxscOfbLDoB3CV7UEIgG58nVsDamfL6rXV/tzWnPxYxi6jUHcKT1BugO/Jt\n" - "/ncelMeoAS6MAZhElaGKzU1cJMlMTV9ofmuKuAwllQULG7L8lwHs9whBK4JmWPaGL\n" - "pU3i9ZoT33/g6pcvA83vicCNqj7ggl6Vb9MeO/zGW1+oV2HC3WiLTqBsYxEJu4YCM\n" - "ewfx9pWeWaCllNy/F1rCBu3cxqzcge9hqIlNtpT7Dq3k\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB4N\n" + "dKGEoxFeg6dqiR2vTl6AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzOD\n" + "QAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5\n" + "LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtg\n" + "eO54FunSh41eqxcc0BZBmgAAANDOL7sWcylFf8SsjGVFvr36mpyUBpAJ/e7o4RbQg\n" + "H8FDu1IxscOfbLDoB3CV7UEIgG58nVsDamfL6rXV/tzWnPxYxi6jUHcKT1BugO/Jt\n" + "/ncelMeoAS6MAZhElaGKzU1cJMlMTV9ofmuKuAwllQULG7L8lwHs9whBK4JmWPaGL\n" + "pU3i9ZoT33/g6pcvA83vicCNqj7ggl6Vb9MeO/zGW1+oV2HC3WiLTqBsYxEJu4YCM\n" + "ewfx9pWeWaCllNy/F1rCBu3cxqzcge9hqIlNtpT7Dq3k\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa384_public_testkey[] = - "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" - "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" - "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" - "ZBmg== aris@kalix86"; + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" + "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" + "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" + "ZBmg== aris@kalix86"; + +static const char torture_ecdsa384_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV3OATega1aNUDIt/ovwfOS7u0jLswYvt\n" + "T2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG2KMk+Fdwd2akfUchqupm\n" + "Ao5F5XOWG2B47ngW6dKHjV6rFxzQFkGa\n" + "-----END PUBLIC KEY-----\n"; static const char torture_ecdsa384_testkey_cert[] = - "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" - "HAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvggfi3v98HjOiqVi1O5aPy7JvMd" - "rTZe68GZ0qCaAN5MAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7" - "MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIar" - "qZgKOReVzlhtgeO54FunSh41eqxcc0BZBmgAAAAAAAAAAAAAAAQAAAAdteWlkZW50A" - "AAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmR" - "pbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" - "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXN" - "lci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwM" - "zg0AAAAYQRXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+" - "WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXH" - "NAWQZoAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQD5f0pF6U6eeBO" - "PrOV7Y3w5NuTzvuyDAq0kTv6VYNMp83TYpIJw16+tMAplOSzPTvwAAAAwWD9StvMEP" - "b+SDH2G5qqkMk+F5IaHI9fev8zcFzzdOlilLc/+CFM0NKMAFtOrrhv0 " - "./ec384.pub"; + "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvggfi3v98HjOiqVi1O5aPy7JvMd" + "rTZe68GZ0qCaAN5MAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7" + "MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIar" + "qZgKOReVzlhtgeO54FunSh41eqxcc0BZBmgAAAAAAAAAAAAAAAQAAAAdteWlkZW50A" + "AAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmR" + "pbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXN" + "lci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwM" + "zg0AAAAYQRXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+" + "WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXH" + "NAWQZoAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQD5f0pF6U6eeBO" + "PrOV7Y3w5NuTzvuyDAq0kTv6VYNMp83TYpIJw16+tMAplOSzPTvwAAAAwWD9StvMEP" + "b+SDH2G5qqkMk+F5IaHI9fev8zcFzzdOlilLc/+CFM0NKMAFtOrrhv0 " + "./ec384.pub"; static const char torture_ecdsa521_private_testkey[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "MIHbAgEBBEG83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrIaTUmNzk+60f0sCCm\n" - "Gll0dgrZLmeIw+TtnG1E20VZflCKq+IdkaAHBgUrgQQAI6GBiQOBhgAEAc6D728d\n" - "baQkHnSPtztaRwJw63CBl15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9d\n" - "Qnuwn8KQUh2T/z+BARjWPKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJ\n" - "KATU3h8gzTMkbrx8DJTFHEIjXBShs44HsSYVl3Xy\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "MIHbAgEBBEG83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrIaTUmNzk+60f0sCCm\n" + "Gll0dgrZLmeIw+TtnG1E20VZflCKq+IdkaAHBgUrgQQAI6GBiQOBhgAEAc6D728d\n" + "baQkHnSPtztaRwJw63CBl15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9d\n" + "Qnuwn8KQUh2T/z+BARjWPKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJ\n" + "KATU3h8gzTMkbrx8DJTFHEIjXBShs44HsSYVl3Xy\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAvN50idki6IgbxJLt\n" + "SbXkClsf17ekx5LIAu66yGk1Jjc5PutH9LAgphpZdHYK2S5niMPk7ZxtRNtFWX5Q\n" + "iqviHZGhgYkDgYYABAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT\n" + "8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsM\n" + "zGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd1\n" + "8g==\n" + "-----END PRIVATE KEY-----\n"; static const char torture_ecdsa521_private_testkey_passphrase[] = - "-----BEGIN EC PRIVATE KEY-----\n" - "Proc-Type: 4,ENCRYPTED\n" - "DEK-Info: AES-128-CBC,24C4F383915BC07D9C63209BF6AD3DEE\n" - "\n" - "M+JGfpGfoH3Wn6XWSoHrGGevaS6p2vJGQdkFEIgUfh16s+U/LcRhAhRnhX/MV6Ds\n" - "OZTpusrjInlZXNUR97fJbmjr/600qUlh4y3U9ikiX3IXE+RI80TPNdishOOjKRF7\n" - "aWDW8UxTlFfU2Zc1Ew0pTvMXXcuTpozW1NNVY+6S9uWfHwq1/EcR35dbnEmG0gId\n" - "qsiEdVKh7p+9Qto8jcVWzMh7ANMcIwmxQ4zbvnqypwgAgpMbamWqBZ9q4egsVZKd\n" - "uRzL95L05ctOBGYNYqpPNIX3UdQU07kzwNC+yaHOb2s=\n" - "-----END EC PRIVATE KEY-----\n"; + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,24C4F383915BC07D9C63209BF6AD3DEE\n" + "\n" + "M+JGfpGfoH3Wn6XWSoHrGGevaS6p2vJGQdkFEIgUfh16s+U/LcRhAhRnhX/MV6Ds\n" + "OZTpusrjInlZXNUR97fJbmjr/600qUlh4y3U9ikiX3IXE+RI80TPNdishOOjKRF7\n" + "aWDW8UxTlFfU2Zc1Ew0pTvMXXcuTpozW1NNVY+6S9uWfHwq1/EcR35dbnEmG0gId\n" + "qsiEdVKh7p+9Qto8jcVWzMh7ANMcIwmxQ4zbvnqypwgAgpMbamWqBZ9q4egsVZKd\n" + "uRzL95L05ctOBGYNYqpPNIX3UdQU07kzwNC+yaHOb2s=\n" + "-----END EC PRIVATE KEY-----\n"; static const char torture_ecdsa521_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIIBXTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY6X14D05Q7gCAggA\n" - "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCmngDUX2/kg+45m4qoCBLiBIIB\n" - "ANHV+GC6Hnend9cVScT5oNtOS2a/TD82N1h+9cYmxn953IRNk2rF7LFYFFeZzcZi\n" - "e840YFYFRiTScm1GbKgwyFLYzYguvpUpS3qz3yZMygoX3xlvFw0l8FWsfeUmOzG1\n" - "uQQPGeoFCus43D3k1iQCOafEe0DPbyfcF/IxajZ+P0N8A5ikgPsOfpTLAdWiYgFt\n" - "wkafVfXx5ZH1u8S34+kmoKRhf5zBFQI1BHD6bCQDANPBkbP4KEjH5mHRO99nHK9r\n" - "EhdLDBEXRo9xb1BhgPLdQA0AdPPqZ6Wugy3KyxkEiH/GB/oBoIpg0oALnowL129g\n" - "BV6jZHwXHuO4/CLJ9rN2tdE=\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBXTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY6X14D05Q7gCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCmngDUX2/kg+45m4qoCBLiBIIB\n" + "ANHV+GC6Hnend9cVScT5oNtOS2a/TD82N1h+9cYmxn953IRNk2rF7LFYFFeZzcZi\n" + "e840YFYFRiTScm1GbKgwyFLYzYguvpUpS3qz3yZMygoX3xlvFw0l8FWsfeUmOzG1\n" + "uQQPGeoFCus43D3k1iQCOafEe0DPbyfcF/IxajZ+P0N8A5ikgPsOfpTLAdWiYgFt\n" + "wkafVfXx5ZH1u8S34+kmoKRhf5zBFQI1BHD6bCQDANPBkbP4KEjH5mHRO99nHK9r\n" + "EhdLDBEXRo9xb1BhgPLdQA0AdPPqZ6Wugy3KyxkEiH/GB/oBoIpg0oALnowL129g\n" + "BV6jZHwXHuO4/CLJ9rN2tdE=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_ecdsa521_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNl\n" - "Y2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1tpCQedI+3\n" - "O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBS\n" - "HZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDN\n" - "MyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAAEAt6sYz7erGM8AAAATZWNkc2Etc2hh\n" - "Mi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEAc6D728dbaQkHnSPtztaRwJw63CB\n" - "l15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9dQnuwn8KQUh2T/z+BARjW\n" - "PKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJKATU3h8gzTMkbrx8DJTF\n" - "HEIjXBShs44HsSYVl3XyAAAAQgC83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrI\n" - "aTUmNzk+60f0sCCmGll0dgrZLmeIw+TtnG1E20VZflCKq+IdkQAAAAABAg==\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1tpCQedI+3\n" + "O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBS\n" + "HZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDN\n" + "MyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAAEAt6sYz7erGM8AAAATZWNkc2Etc2hh\n" + "Mi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEAc6D728dbaQkHnSPtztaRwJw63CB\n" + "l15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9dQnuwn8KQUh2T/z+BARjW\n" + "PKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJKATU3h8gzTMkbrx8DJTF\n" + "HEIjXBShs44HsSYVl3XyAAAAQgC83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrI\n" + "aTUmNzk+60f0sCCmGll0dgrZLmeIw+TtnG1E20VZflCKq+IdkQAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa521_private_openssh_testkey_passphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAj\n" - "9WBFa/piJcPFEE4CGZTKAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1\n" - "MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl1\n" - "7sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee1\n" - "6VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOO\n" - "B7EmFZd18gAAAQDLjaKp+DLEHFb98f5WnVFg6LgDN847sfeuPZVfVjeSAiIv016O\n" - "ld7DXb137B2xYVsuce6sHbypr10dJOvgMTLdzTl+crYNJL+8UufJP0rOIFaDenzQ\n" - "RW8wydwiQxwt1ZqtD8ASqFmadxngufJKZzPLGfjCbCz3uATKa2sXN66nRXRZJbVA\n" - "IlNYDY8ivAStNhfItUMqyM6PkYlKJECtJw7w7TYKpvts7t72JmtgqVjS45JI/YZ+\n" - "kitIG0YmG8rzL9d1vBB5m+MH/fnFz2uJqbQYCH9Ctc8HZodAVoTNDzXHU2mYF9PE\n" - "Z6+gi3jd+kOyUk3NifHcre9K6ie7LL33JayM\n" - "-----END OPENSSH PRIVATE KEY-----\n"; - + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAj\n" + "9WBFa/piJcPFEE4CGZTKAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1\n" + "MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl1\n" + "7sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee1\n" + "6VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOO\n" + "B7EmFZd18gAAAQDLjaKp+DLEHFb98f5WnVFg6LgDN847sfeuPZVfVjeSAiIv016O\n" + "ld7DXb137B2xYVsuce6sHbypr10dJOvgMTLdzTl+crYNJL+8UufJP0rOIFaDenzQ\n" + "RW8wydwiQxwt1ZqtD8ASqFmadxngufJKZzPLGfjCbCz3uATKa2sXN66nRXRZJbVA\n" + "IlNYDY8ivAStNhfItUMqyM6PkYlKJECtJw7w7TYKpvts7t72JmtgqVjS45JI/YZ+\n" + "kitIG0YmG8rzL9d1vBB5m+MH/fnFz2uJqbQYCH9Ctc8HZodAVoTNDzXHU2mYF9PE\n" + "Z6+gi3jd+kOyUk3NifHcre9K6ie7LL33JayM\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ecdsa521_public_testkey[] = - "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" - "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" - "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" - "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; + "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" + "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" + "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" + "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; + +static const char torture_ecdsa521_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBzoPvbx1tpCQedI+3O1pHAnDrcIGX\n" + "XlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBSHZP/P4EBGNY8\n" + "qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDNMyRuvHwMlMUc\n" + "QiNcFKGzjgexJhWXdfI=\n" + "-----END PUBLIC KEY-----\n"; static const char torture_ecdsa521_testkey_cert[] = - "ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" - "HA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggFIwlsx63C++kmCBDF4O14fvu5j" - "Icsm8uMbMp0smOVwAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwg" - "ZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jy" - "oXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI" - "1wUobOOB7EmFZd18gAAAAAAAAAAAAAAAQAAAAdteWlkZW50AAAAAAAAAAAAAAAA///" - "///////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blc" - "m1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5" - "nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAA" - "AAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1" - "tpCQedI+3O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce" - "7CfwpBSHZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNT" - "eHyDNMyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAACnAAAAE2VjZHNhLXNoYTItbmlzd" - "HA1MjEAAACMAAAAQgCJzTxw/hz2qE8Qkd4XW9Qn7fPxML6Ebtttg9C18AguyGyE6Nk" - "YH1NcToYxwQxrgzDXowXYm9eCbq9JEvaXDEtIfAAAAEIBk06LmKAYR2HDwwt4f5wVI" - "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" - "= ./ec521.pub"; + "ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggFIwlsx63C++kmCBDF4O14fvu5j" + "Icsm8uMbMp0smOVwAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwg" + "ZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jy" + "oXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI" + "1wUobOOB7EmFZd18gAAAAAAAAAAAAAAAQAAAAdteWlkZW50AAAAAAAAAAAAAAAA///" + "///////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blc" + "m1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5" + "nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAA" + "AAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1" + "tpCQedI+3O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce" + "7CfwpBSHZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNT" + "eHyDNMyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAACnAAAAE2VjZHNhLXNoYTItbmlzd" + "HA1MjEAAACMAAAAQgCJzTxw/hz2qE8Qkd4XW9Qn7fPxML6Ebtttg9C18AguyGyE6Nk" + "YH1NcToYxwQxrgzDXowXYm9eCbq9JEvaXDEtIfAAAAEIBk06LmKAYR2HDwwt4f5wVI" + "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" + "= ./ec521.pub"; /**************************************************************************** * ED25519 KEYS ****************************************************************************/ static const char torture_ed25519_private_pkcs8_testkey[] = - "-----BEGIN PRIVATE KEY-----\n" - "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" - "-----END PRIVATE KEY-----\n"; + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" + "-----END PRIVATE KEY-----\n"; static const char torture_ed25519_private_openssh_testkey[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" - "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" - "dQAAAAtzc2gtZWQyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlw\n" - "AAAEBgYXKi3utbZKlYyByhM8Ad6CDWrEh1hmyFl0FnCz5hjRWWnxuCYiOyvMYLtkgoEyEK\n" - "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" + "dQAAAAtzc2gtZWQyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlw\n" + "AAAEBgYXKi3utbZKlYyByhM8Ad6CDWrEh1hmyFl0FnCz5hjRWWnxuCYiOyvMYLtkgoEyEK\n" + "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ed25519_private_openssh_testkey_passphrase[] = - "-----BEGIN OPENSSH PRIVATE KEY-----\n" - "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" - "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" - "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" - "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" - "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" - "qMU3xAJ4ixnwhYFg==\n" - "-----END OPENSSH PRIVATE KEY-----\n"; + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" + "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" + "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" + "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" + "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" + "qMU3xAJ4ixnwhYFg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; static const char torture_ed25519_private_pkcs8_testkey_passphrase[] = - "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" - "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" - "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" - "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" - "qQhQtw0vgfnq1um/izg=\n" - "-----END ENCRYPTED PRIVATE KEY-----\n"; + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" + "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" + "qQhQtw0vgfnq1um/izg=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; static const char torture_ed25519_public_testkey[] = - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" - "BU6Nh3PmAiqX aris@kalix86"; + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" + "BU6Nh3PmAiqX aris@kalix86"; static const char torture_ed25519_testkey_cert[] = - "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" - "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" - "AIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAAAAAAAAAAAABAAAAB" - "215aWRlbnQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTE" - "tZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAF" - "nBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnB" - "lcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCY" - "iOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB" - "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" - "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; - -static const char *torture_get_testkey_internal(enum ssh_keytypes_e type, - bool with_passphrase, - int pubkey, - int format) + "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" + "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" + "AIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAAAAAAAAAAAABAAAAB" + "215aWRlbnQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTE" + "tZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAF" + "nBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnB" + "lcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCY" + "iOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB" + "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" + "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; + +static const char * +torture_get_testkey_public_internal(enum ssh_keytypes_e type, + enum torture_format_e format) { switch (type) { - case SSH_KEYTYPE_DSS: - if (pubkey) { - return torture_dsa_public_testkey; - } else if (with_passphrase) { - if (format == 1) { - return torture_dsa_private_openssh_testkey_passphrase; - } - if (format == 2) { - return torture_dsa_private_pkcs8_testkey_passphrase; - } else { - return torture_dsa_private_testkey_passphrase; - } - } - if (format == 1) { - return torture_dsa_private_openssh_testkey; - } + case SSH_KEYTYPE_DSS: + return torture_dsa_public_testkey; + case SSH_KEYTYPE_RSA: + if (format == FORMAT_OPENSSH) { + return torture_rsa_public_testkey; + } + return torture_rsa_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P521: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa521_public_testkey; + } + return torture_ecdsa521_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P384: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa384_public_testkey; + } + return torture_ecdsa384_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P256: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa256_public_testkey; + } + return torture_ecdsa256_public_testkey_pem; + case SSH_KEYTYPE_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_public_testkey; + } + /* not available in other formats */ + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + return torture_dsa_testkey_cert; + case SSH_KEYTYPE_RSA_CERT01: + return torture_rsa_testkey_cert; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return torture_ecdsa256_testkey_cert; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return torture_ecdsa384_testkey_cert; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return torture_ecdsa521_testkey_cert; + case SSH_KEYTYPE_ED25519_CERT01: + return torture_ed25519_testkey_cert; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_dsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_rsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey_pasphrase; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey; + case FORMAT_PEM: return torture_dsa_private_testkey; - case SSH_KEYTYPE_RSA: - if (pubkey) { - return torture_rsa_public_testkey; - } else if (with_passphrase) { - if (format == 1) { - return torture_rsa_private_openssh_testkey_passphrase; - } - if (format == 2) { - return torture_rsa_private_pkcs8_testkey_passphrase; - } else { - return torture_rsa_private_testkey_passphrase; - } - } - if (format == 1) { - return torture_rsa_private_openssh_testkey; - } + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey; + case FORMAT_PEM: return torture_rsa_private_testkey; - case SSH_KEYTYPE_ECDSA_P521: - if (pubkey) { - return torture_ecdsa521_public_testkey; - } else if (with_passphrase) { - if (format == 1) { - return torture_ecdsa521_private_openssh_testkey_passphrase; - } - if (format == 2) { - return torture_ecdsa521_private_pkcs8_testkey_passphrase; - } else { - return torture_ecdsa521_private_testkey_passphrase; - } - } - if (format == 1) { - return torture_ecdsa521_private_openssh_testkey; - } + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey; + case FORMAT_PEM: return torture_ecdsa521_private_testkey; - case SSH_KEYTYPE_ECDSA_P384: - if (pubkey) { - return torture_ecdsa384_public_testkey; - } else if (with_passphrase){ - if (format == 1) { - return torture_ecdsa384_private_openssh_testkey_passphrase; - } - if (format == 2) { - return torture_ecdsa384_private_pkcs8_testkey_passphrase; - } else { - return torture_ecdsa384_private_testkey_passphrase; - } - } - if (format == 1) { - return torture_ecdsa384_private_openssh_testkey; - } + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey; + case FORMAT_PEM: return torture_ecdsa384_private_testkey; - case SSH_KEYTYPE_ECDSA_P256: - if (pubkey) { - return torture_ecdsa256_public_testkey; - } else if (with_passphrase){ - if (format == 1) { - return torture_ecdsa256_private_openssh_testkey_pasphrase; - } - if (format == 2) { - return torture_ecdsa256_private_pkcs8_testkey_passphrase; - } else { - return torture_ecdsa256_private_testkey_passphrase; - } - } - if (format == 1) { - return torture_ecdsa256_private_openssh_testkey; - } + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey; + case FORMAT_PEM: return torture_ecdsa256_private_testkey; - case SSH_KEYTYPE_ED25519: - if (pubkey) { - return torture_ed25519_public_testkey; - } else if (with_passphrase) { - if (format == 1) { - return torture_ed25519_private_openssh_testkey_passphrase; - } - if (format == 2) { - return torture_ed25519_private_pkcs8_testkey_passphrase; - } - /* ed25519 keys are not available in legacy PEM format */ - return NULL; - } - if (format == 1) { - return torture_ed25519_private_openssh_testkey; - } - /* ed25519 keys are not available in legacy PEM format */ + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey; + case FORMAT_PKCS8: return torture_ed25519_private_pkcs8_testkey; - case SSH_KEYTYPE_DSS_CERT01: - return torture_dsa_testkey_cert; - case SSH_KEYTYPE_RSA_CERT01: - return torture_rsa_testkey_cert; - case SSH_KEYTYPE_ECDSA_P256_CERT01: - return torture_ecdsa256_testkey_cert; - case SSH_KEYTYPE_ECDSA_P384_CERT01: - return torture_ecdsa384_testkey_cert; - case SSH_KEYTYPE_ECDSA_P521_CERT01: - return torture_ecdsa521_testkey_cert; - case SSH_KEYTYPE_ED25519_CERT01: - return torture_ed25519_testkey_cert; - case SSH_KEYTYPE_RSA1: - case SSH_KEYTYPE_ECDSA: - case SSH_KEYTYPE_UNKNOWN: + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; } return NULL; } /* Return the encrypted private key in a new OpenSSH format */ -const char *torture_get_openssh_testkey(enum ssh_keytypes_e type, - bool with_passphrase) +const char * +torture_get_openssh_testkey(enum ssh_keytypes_e type, bool with_passphrase) { - return torture_get_testkey_internal(type, with_passphrase, 0, 1); + if (with_passphrase) { + return torture_get_testkey_encrypted_internal(type, FORMAT_OPENSSH); + } else { + return torture_get_testkey_internal(type, FORMAT_OPENSSH); + } } /* Return the private key in PEM format */ -const char *torture_get_testkey(enum ssh_keytypes_e type, - bool with_passphrase) +const char * +torture_get_testkey(enum ssh_keytypes_e type, bool with_passphrase) { + enum torture_format_e format = FORMAT_PEM; + + if (with_passphrase) { +/* This is the new PKCS8 PEM format, which works only in OpenSSL */ #if defined(HAVE_LIBCRYPTO) - return torture_get_testkey_internal(type, with_passphrase, 0, 2); -#else - return torture_get_testkey_internal(type, with_passphrase, 0, 0); + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_encrypted_internal(type, format); + } else { +/* The unencrypted format works also in mbedTLS */ +#if defined(HAVE_LIBCRYPTO) || defined(HAVE_LIBMBEDCRYPTO) + format = FORMAT_PKCS8; #endif + return torture_get_testkey_internal(type, format); + } +} + +const char * +torture_get_testkey_pub(enum ssh_keytypes_e type) +{ + return torture_get_testkey_public_internal(type, FORMAT_OPENSSH); } -const char *torture_get_testkey_pub(enum ssh_keytypes_e type) +const char * +torture_get_testkey_pub_pem(enum ssh_keytypes_e type) { - return torture_get_testkey_internal(type, 0, 1, 0); + return torture_get_testkey_public_internal(type, FORMAT_PEM); } -const char *torture_get_testkey_passphrase(void) +const char * +torture_get_testkey_passphrase(void) { return TORTURE_TESTKEY_PASSWORD; } diff --git a/libssh/tests/torture_key.h b/libssh/tests/torture_key.h index 961fdb9..5eacdab 100644 --- a/libssh/tests/torture_key.h +++ b/libssh/tests/torture_key.h @@ -39,4 +39,6 @@ const char *torture_get_testkey_passphrase(void); const char *torture_get_testkey_pub(enum ssh_keytypes_e type); +const char *torture_get_testkey_pub_pem(enum ssh_keytypes_e type); + #endif /* _TORTURE_KEY_H */ diff --git a/libssh/tests/unittests/CMakeLists.txt b/libssh/tests/unittests/CMakeLists.txt index 6f49e0d..28458d4 100644 --- a/libssh/tests/unittests/CMakeLists.txt +++ b/libssh/tests/unittests/CMakeLists.txt @@ -64,6 +64,14 @@ if (UNIX AND NOT WIN32) ) endif() + if (WITH_PKCS11_URI) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_rsa_uri + torture_pki_ecdsa_uri + ) + endif() + if (HAVE_ECC) set(LIBSSH_UNIT_TESTS ${LIBSSH_UNIT_TESTS} @@ -76,10 +84,15 @@ if (UNIX AND NOT WIN32) # requires pthread torture_threads_pki_rsa ) - # Not working correctly - #if (WITH_SERVER) - # add_cmocka_test(torture_server_x11 torture_server_x11.c ${TEST_TARGET_LIBRARIES}) - #endif (WITH_SERVER) + if (WITH_SERVER) + # 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 + # ) + endif (WITH_SERVER) endif (UNIX AND NOT WIN32) foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) @@ -88,6 +101,11 @@ foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT + LSAN_OPTIONS=suppressions=${libssh-tests_SOURCE_DIR}/suppressions/lsan.supp;OPENSSL_ENABLE_SHA1_SIGNATURES=1) endforeach() if (CMAKE_USE_PTHREADS_INIT) @@ -97,6 +115,11 @@ if (CMAKE_USE_PTHREADS_INIT) COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} Threads::Threads ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT + LSAN_OPTIONS=suppressions=${libssh-tests_SOURCE_DIR}/suppressions/lsan.supp;OPENSSL_ENABLE_SHA1_SIGNATURES=1) endforeach() endif () diff --git a/libssh/tests/unittests/torture_bind_config.c b/libssh/tests/unittests/torture_bind_config.c index 11d8672..f8e3d6b 100644 --- a/libssh/tests/unittests/torture_bind_config.c +++ b/libssh/tests/unittests/torture_bind_config.c @@ -64,37 +64,94 @@ extern LIBSSH_THREAD int ssh_log_level; #endif #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS "libssh_test_bind_config_listenaddress" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING "ListenAddress "LISTEN_ADDRESS"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2 "libssh_test_bind_config_listenaddress2" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING "ListenAddress "LISTEN_ADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE "libssh_test_bind_config_listenaddress_twice" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "ListenAddress "LISTEN_ADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC "libssh_test_bind_config_listenaddress_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n" #define LIBSSH_TEST_BIND_CONFIG_PORT "libssh_test_bind_config_port" +#define LIBSSH_TEST_BIND_CONFIG_PORT_STRING "Port 123\n" #define LIBSSH_TEST_BIND_CONFIG_PORT2 "libssh_test_bind_config_port2" +#define LIBSSH_TEST_BIND_CONFIG_PORT2_STRING "Port 456\n" #define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE "libssh_test_bind_config_port_twice" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING \ + "Port 123\n" \ + "Port 456\n" #define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC "libssh_test_bind_config_port_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING \ + "Port 123\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY "libssh_test_bind_config_hostkey" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2 "libssh_test_bind_config_hostkey2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING "HostKey "LIBSSH_RSA_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE "libssh_test_bind_config_hostkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "HostKey "LIBSSH_RSA_TESTKEY"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC "libssh_test_bind_config_hostkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n" #define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL "libssh_test_bind_config_loglevel" -#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING "LogLevel "LOGLEVEL"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING "LogLevel "LOGLEVEL2"\n" #define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE "libssh_test_bind_config_loglevel_twice" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "LogLevel "LOGLEVEL2"\n" #define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC "libssh_test_bind_config_loglevel_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS "libssh_test_bind_config_ciphers" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING "Ciphers "CIPHERS"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS2 "libssh_test_bind_config_ciphers2" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING "Ciphers "CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE "libssh_test_bind_config_ciphers_twice" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING \ + "Ciphers "CIPHERS"\n" \ + "Ciphers "CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC "libssh_test_bind_config_ciphers_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING \ + "Ciphers "CIPHERS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS "libssh_test_bind_config_macs" +#define LIBSSH_TEST_BIND_CONFIG_MACS_STRING "MACs "MACS"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS2 "libssh_test_bind_config_macs2" +#define LIBSSH_TEST_BIND_CONFIG_MACS2_STRING "MACs "MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE "libssh_test_bind_config_macs_twice" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING \ + "MACs "MACS"\n" \ + "MACs "MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC "libssh_test_bind_config_macs_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING \ + "MACs "MACS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS "libssh_test_bind_config_kexalgorithms" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING "KexAlgorithms "KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2 "libssh_test_bind_config_kexalgorithms2" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING "KexAlgorithms "KEXALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE "libssh_test_bind_config_kexalgorithms_twice" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "KexAlgorithms "KEXALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC "libssh_test_bind_config_kexalgorithms_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_FULL "libssh_test_bind_config_full" #define LIBSSH_TEST_BIND_CONFIG_INCLUDE "libssh_test_bind_config_include" #define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE "libssh_test_bind_config_include_recursive" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP "libssh_test_bind_config_include_recursive_loop" #define LIBSSH_TEST_BIND_CONFIG_CORNER_CASES "libssh_test_bind_config_corner_cases" #define LIBSSH_TEST_BIND_CONFIG_MATCH_ALL "libssh_test_bind_config_match_all" @@ -106,16 +163,34 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2 "libssh_test_bind_config_match_invalid2" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED "libssh_test_bind_config_pubkey" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2 "libssh_test_bind_config_pubkey2" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE "libssh_test_bind_config_pubkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC "libssh_test_bind_config_pubkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN "libssh_test_bind_config_pubkey_unknown" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS "libssh_test_bind_config_hostkey_alg" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2 "libssh_test_bind_config_hostkey_alg2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE "libssh_test_bind_config_hostkey_alg_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC "libssh_test_bind_config_hostkey_alg_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" #define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN "libssh_test_bind_config_hostkey_alg_unknown" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n" const char template[] = "temp_dir_XXXXXX"; @@ -168,81 +243,67 @@ static int setup_config_files(void **state) #endif torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, - "ListenAddress "LISTEN_ADDRESS"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, - "ListenAddress "LISTEN_ADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, - "ListenAddress "LISTEN_ADDRESS"\n" - "ListenAddress "LISTEN_ADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, - "ListenAddress "LISTEN_ADDRESS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n"); + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT, - "Port 123\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT2, - "Port 456\n"); + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, - "Port 123\n" - "Port 456\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, - "Port 123\n" - "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n"); + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY2, - "HostKey "LIBSSH_RSA_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" - "HostKey "LIBSSH_RSA_TESTKEY"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, - "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, - "LogLevel "LOGLEVEL"\n"); - torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2, - "LogLevel "LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, - "LogLevel "LOGLEVEL"\n" - "LogLevel "LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, - "LogLevel "LOGLEVEL"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2"\n"); + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS, - "Ciphers "CIPHERS"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS2, - "Ciphers "CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, - "Ciphers "CIPHERS"\n" - "Ciphers "CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, - "Ciphers "CIPHERS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n"); + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS, - "MACs "MACS"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS2, - "MACs "MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, - "MACs "MACS"\n" - "MACs "MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, - "MACs "MACS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n"); + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, - "KexAlgorithms "KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, - "KexAlgorithms "KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, - "KexAlgorithms "KEXALGORITHMS"\n" - "KexAlgorithms "KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, - "KexAlgorithms "KEXALGORITHMS"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, "ListenAddress "LISTEN_ADDRESS"\n" @@ -265,6 +326,9 @@ static int setup_config_files(void **state) torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP"\n"); + /* Unsupported options and corner cases */ torture_write_file(LIBSSH_TEST_BIND_CONFIG_CORNER_CASES, "\n" /* empty line */ @@ -334,30 +398,26 @@ static int setup_config_files(void **state) "\tLogLevel "LOGLEVEL4"\n"); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n"); + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, - "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, - "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, - "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" - "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING); torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, - "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n"); + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING); return 0; } @@ -405,11 +465,43 @@ static int sshbind_teardown(void **state) return 0; } -static void torture_bind_config_listen_address(void **state) +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_bind bind, + const char *file, + const char *string, + int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_bind_config_parse_file(bind, file); + } else if (string != NULL) { + ret = ssh_bind_config_parse_string(bind, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_return_code(ret, expected); +} + + +static void +torture_bind_config_listen_address(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -417,37 +509,84 @@ static void torture_bind_config_listen_address(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS); - assert_int_equal(rc, 0); - assert_non_null(bind->bindaddr); - assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE); - assert_int_equal(rc, 0); assert_non_null(bind->bindaddr); - assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + assert_string_equal(bind->bindaddr, expect); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC); - assert_int_equal(rc, 0); - assert_non_null(bind->bindaddr); - assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); +static void torture_bind_config_listen_address_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + NULL, + LISTEN_ADDRESS); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2); - assert_int_equal(rc, 0); - assert_non_null(bind->bindaddr); - assert_string_equal(bind->bindaddr, LISTEN_ADDRESS2); +static void torture_bind_config_listen_address_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address2_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + NULL, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address2_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address_twice_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING, + LISTEN_ADDRESS); +} +static void torture_bind_config_listen_address_twice_rec_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + NULL, + LISTEN_ADDRESS); } -static void torture_bind_config_port(void **state) +static void torture_bind_config_listen_address_twice_rec_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING, + LISTEN_ADDRESS); +} + +static void +torture_bind_config_port(void **state, + const char *file, + const char *string, + int expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -455,29 +594,82 @@ static void torture_bind_config_port(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT); - assert_int_equal(rc, 0); - assert_int_equal(bind->bindport, 123); + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT_TWICE); - assert_int_equal(rc, 0); - assert_int_equal(bind->bindport, 123); + assert_int_equal(bind->bindport, expect); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC); - assert_int_equal(rc, 0); - assert_int_equal(bind->bindport, 123); +static void torture_bind_config_port_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT, + NULL, + 123); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_PORT2); - assert_int_equal(rc, 0); - assert_int_equal(bind->bindport, 456); +static void torture_bind_config_port_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING, + 123); +} + +static void torture_bind_config_port2_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT2, + NULL, + 456); +} + +static void torture_bind_config_port2_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING, + 456); +} + +static void torture_bind_config_port_twice_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + NULL, + 123); +} + +static void torture_bind_config_port_twice_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING, + 123); +} + +static void torture_bind_config_port_twice_rec_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + NULL, + 123); +} + +static void torture_bind_config_port_twice_rec_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING, + 123); } -static void torture_bind_config_hostkey(void **state) +static void +torture_bind_config_hostkey(void **state, + const char *file, + const char *string) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -485,25 +677,19 @@ static void torture_bind_config_hostkey(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY); - assert_int_equal(rc, 0); - assert_non_null(bind->ecdsakey); - assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE); - assert_int_equal(rc, 0); assert_non_null(bind->ecdsakey); assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); - assert_non_null(bind->rsakey); - assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); } -static void torture_bind_config_hostkey_twice_rec(void **state) +static void +torture_bind_config_hostkey2(void **state, + const char *file, + const char *string) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -511,15 +697,44 @@ static void torture_bind_config_hostkey_twice_rec(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC); - assert_int_equal(rc, 0); + _parse_config(bind, file, string, SSH_OK); + assert_non_null(bind->ecdsakey); assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); assert_non_null(bind->rsakey); assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); } +static void torture_bind_config_hostkey_file(void **state) +{ + torture_bind_config_hostkey(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY, NULL); +} + +static void torture_bind_config_hostkey_string(void **state) +{ + torture_bind_config_hostkey(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); +} + +static void torture_bind_config_hostkey_twice_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, NULL); +} + +static void torture_bind_config_hostkey_twice_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); +} + +static void torture_bind_config_hostkey_twice_rec_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, NULL); +} + +static void torture_bind_config_hostkey_twice_rec_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); +} + static void torture_bind_config_hostkey_separately(void **state) { struct bind_st *test_state; @@ -545,12 +760,17 @@ static void torture_bind_config_hostkey_separately(void **state) assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); } -static void torture_bind_config_loglevel(void **state) +static void +torture_bind_config_loglevel(void **state, + const char *file, + const char *string, + int expect) { struct bind_st *test_state; ssh_bind bind; - int rc; - int previous_level, new_level; + int previous_level, new_level, rc; + + previous_level = ssh_get_log_level(); assert_non_null(state); test_state = *((struct bind_st **)state); @@ -558,45 +778,88 @@ static void torture_bind_config_loglevel(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - previous_level = ssh_get_log_level(); - - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_LOGLEVEL); - assert_int_equal(rc, 0); + _parse_config(bind, file, string, SSH_OK); new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); + assert_int_equal(new_level, expect); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE); - assert_int_equal(rc, 0); + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); +static void torture_bind_config_loglevel_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + NULL, + 2); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_loglevel_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING, + 2); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 2); +static void torture_bind_config_loglevel1_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + NULL, + 1); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_LOGLEVEL2); - assert_int_equal(rc, 0); +static void torture_bind_config_loglevel1_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING, + 1); +} - new_level = ssh_get_log_level(); - assert_int_equal(new_level, 1); +static void torture_bind_config_loglevel_twice_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + NULL, + 2); +} - rc = ssh_set_log_level(previous_level); - assert_int_equal(rc, SSH_OK); +static void torture_bind_config_loglevel_twice_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING, + 2); } -static void torture_bind_config_ciphers(void **state) +static void +torture_bind_config_ciphers(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; char *fips_ciphers = NULL; - char *fips_ciphers2 = NULL; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -605,73 +868,96 @@ static void torture_bind_config_ciphers(void **state) bind = test_state->bind; if (ssh_fips_mode()) { - fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, expect); assert_non_null(fips_ciphers); - fips_ciphers2 = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS2); - assert_non_null(fips_ciphers2); } - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CIPHERS); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); - assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); - } else { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); - } + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE); - assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); if (ssh_fips_mode()) { assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + SAFE_FREE(fips_ciphers); } else { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], expect); } +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_ciphers_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS, + NULL, + CIPHERS); +} - assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); - assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); - } else { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); - } +static void torture_bind_config_ciphers_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING, + CIPHERS); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CIPHERS2); - assert_int_equal(rc, 0); +static void torture_bind_config_ciphers2_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + NULL, + CIPHERS2); +} - assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); - assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers2); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers2); - } else { - assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS2); - assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS2); - } +static void torture_bind_config_ciphers2_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING, + CIPHERS2); +} - SAFE_FREE(fips_ciphers); - SAFE_FREE(fips_ciphers2); +static void torture_bind_config_ciphers_twice_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING, + CIPHERS); } -static void torture_bind_config_macs(void **state) +static void torture_bind_config_ciphers_twice_rec_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING, + CIPHERS); +} + +static void +torture_bind_config_macs(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -679,59 +965,87 @@ static void torture_bind_config_macs(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_MACS); - assert_int_equal(rc, 0); - - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + _parse_config(bind, file, string, SSH_OK); assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); - - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_MACS_TWICE); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], expect); +} - assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); +static void torture_bind_config_macs_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS, + NULL, + MACS); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC); - assert_int_equal(rc, 0); +static void torture_bind_config_macs_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING, + MACS); +} - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); +static void torture_bind_config_macs2_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS2, + NULL, + MACS2); +} - assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); +static void torture_bind_config_macs2_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING, + MACS2); +} - rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_MACS2); - assert_int_equal(rc, 0); +static void torture_bind_config_macs_twice_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + NULL, + MACS); +} - assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); - assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS2); +static void torture_bind_config_macs_twice_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING, + MACS); +} - assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); - assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS2); +static void torture_bind_config_macs_twice_rec_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + NULL, + MACS); +} + +static void torture_bind_config_macs_twice_rec_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING, + MACS); } -static void torture_bind_config_kexalgorithms(void **state) +static void +torture_bind_config_kexalgorithms(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; char *fips_kex = NULL; - char *fips_kex2 = NULL; - int rc; - - if (ssh_fips_mode()) { - fips_kex = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); - assert_non_null(fips_kex); - fips_kex2 = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS2); - assert_non_null(fips_kex2); - } assert_non_null(state); test_state = *((struct bind_st **)state); @@ -739,64 +1053,95 @@ static void torture_bind_config_kexalgorithms(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_KEX]); if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); - } else { - assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + fips_kex = ssh_keep_fips_algos(SSH_KEX, expect); + assert_non_null(fips_kex); } - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_KEX]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); - } else { - assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); - } + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC); - assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_KEX]); if (ssh_fips_mode()) { assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + SAFE_FREE(fips_kex); } else { - assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + assert_string_equal(bind->wanted_methods[SSH_KEX], expect); } +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_KEX]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex2); - } else { - assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS2); - } +static void torture_bind_config_kexalgorithms_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + NULL, + KEXALGORITHMS); +} - SAFE_FREE(fips_kex); - SAFE_FREE(fips_kex2); +static void torture_bind_config_kexalgorithms_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms2_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + NULL, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms2_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms_twice_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + NULL, + KEXALGORITHMS); } -static void torture_bind_config_pubkey_accepted(void **state) +static void torture_bind_config_kexalgorithms_twice_rec_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING, + KEXALGORITHMS); +} + +static void +torture_bind_config_pubkey_accepted(void **state, + const char *file, + const char *string, + const char *expect) { struct bind_st *test_state; ssh_bind bind; - int rc; char *fips_pubkeys = NULL; - char *fips_pubkeys2 = NULL; - - if (ssh_fips_mode()) { - fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); - assert_non_null(fips_pubkeys); - fips_pubkeys2 = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES2); - assert_non_null(fips_pubkeys2); - } assert_non_null(state); test_state = *((struct bind_st **)state); @@ -804,75 +1149,111 @@ static void torture_bind_config_pubkey_accepted(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED); - assert_int_equal(rc, 0); - assert_non_null(bind->pubkey_accepted_key_types); if (ssh_fips_mode()) { - assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); - } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_pubkeys); } - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2); - assert_int_equal(rc, 0); - assert_non_null(bind->pubkey_accepted_key_types); - if (ssh_fips_mode()) { - assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys2); - } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES2); - } + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE); - assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); if (ssh_fips_mode()) { assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + SAFE_FREE(fips_pubkeys); } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); + assert_string_equal(bind->pubkey_accepted_key_types, expect); } +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC); - assert_int_equal(rc, 0); - assert_non_null(bind->pubkey_accepted_key_types); - if (ssh_fips_mode()) { - assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys2); - } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES2); - } +static void torture_bind_config_pubkey_accepted_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + NULL, + PUBKEYACCEPTEDTYPES); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN); - assert_int_equal(rc, 0); - assert_non_null(bind->pubkey_accepted_key_types); - if (ssh_fips_mode()) { - assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); - } else { - assert_string_equal(bind->pubkey_accepted_key_types, PUBKEYACCEPTEDTYPES); - } +static void torture_bind_config_pubkey_accepted_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING, + PUBKEYACCEPTEDTYPES); +} - SAFE_FREE(fips_pubkeys); - SAFE_FREE(fips_pubkeys2); +static void torture_bind_config_pubkey_accepted_twice_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + NULL, + PUBKEYACCEPTEDTYPES); } -static void torture_bind_config_hostkey_algorithms(void **state) +static void torture_bind_config_pubkey_accepted_twice_string(void **state) { - struct bind_st *test_state; - ssh_bind bind; - int rc; + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING, + PUBKEYACCEPTEDTYPES); +} - char *fips_hostkeys = NULL; - char *fips_hostkeys2 = NULL; +static void torture_bind_config_pubkey_accepted_twice_rec_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + NULL, + PUBKEYACCEPTEDTYPES2); +} - if (ssh_fips_mode()) { - fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); - assert_non_null(fips_hostkeys); - fips_hostkeys2 = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS2); - assert_non_null(fips_hostkeys2); - } +static void torture_bind_config_pubkey_accepted_twice_rec_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted_unknown_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_unknown_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted2_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted2_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void +torture_bind_config_hostkey_algorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_hostkey = NULL; assert_non_null(state); test_state = *((struct bind_st **)state); @@ -880,58 +1261,100 @@ static void torture_bind_config_hostkey_algorithms(void **state) assert_non_null(test_state->bind); bind = test_state->bind; - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); - } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + fips_hostkey = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_hostkey); } - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys2); - } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS2); - } + _parse_config(bind, file, string, SSH_OK); - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE); - assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkey); + SAFE_FREE(fips_hostkey); } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], expect); } +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys2); - } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS2); - } +static void torture_bind_config_hostkey_algorithms_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + NULL, + HOSTKEYALGORITHMS); +} - rc = ssh_bind_config_parse_file(bind, - LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN); - assert_int_equal(rc, 0); - assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); - if (ssh_fips_mode()) { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkeys); - } else { - assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); - } +static void torture_bind_config_hostkey_algorithms_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + NULL, + HOSTKEYALGORITHMS2); +} - SAFE_FREE(fips_hostkeys); - SAFE_FREE(fips_hostkeys2); +static void torture_bind_config_hostkey_algorithms2_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_unknown_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_unknown_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING, + HOSTKEYALGORITHMS); } static int assert_full_bind_config(void **state) @@ -1076,6 +1499,23 @@ static void torture_bind_config_include_recursive(void **state) assert_int_equal(rc, SSH_OK); } +static void torture_bind_config_include_recursive_loop(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP); + assert_int_equal(rc, 0); +} + /** * @brief Verify the configuration parser does not choke on unknown * or unsupported configuration options @@ -1263,23 +1703,115 @@ int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_bind_config_listen_address, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_port, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_string, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec, + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_string, sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_separately, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_loglevel, + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_ciphers, + cmocka_unit_test_setup_teardown(torture_bind_config_macs_string, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_macs, + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms, + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_string, sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_full, sshbind_setup, sshbind_teardown), @@ -1287,6 +1819,8 @@ int torture_run_tests(void) sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive, sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive_loop, + sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_corner_cases, sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_match_all, @@ -1301,9 +1835,45 @@ int torture_run_tests(void) sshbind_setup, sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_config_match_invalid, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted, + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_file, sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms, + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_string, sshbind_setup, sshbind_teardown), }; diff --git a/libssh/tests/unittests/torture_callbacks.c b/libssh/tests/unittests/torture_callbacks.c index 85f4d1f..25111b2 100644 --- a/libssh/tests/unittests/torture_callbacks.c +++ b/libssh/tests/unittests/torture_callbacks.c @@ -43,7 +43,7 @@ static int teardown(void **state) } static void torture_callbacks_size(void **state) { - struct ssh_callbacks_struct *cb = *state;; + struct ssh_callbacks_struct *cb = *state; assert_int_not_equal(cb->size, 0); } diff --git a/libssh/tests/unittests/torture_config.c b/libssh/tests/unittests/torture_config.c index f91112a..31dadae 100644 --- a/libssh/tests/unittests/torture_config.c +++ b/libssh/tests/unittests/torture_config.c @@ -2,14 +2,32 @@ #define LIBSSH_STATIC +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + #include "torture.h" #include "libssh/options.h" #include "libssh/session.h" #include "libssh/config_parser.h" #include "match.c" +#include "config.c" extern LIBSSH_THREAD int ssh_log_level; +#define USERNAME "testuser" +#define PROXYCMD "ssh -q -W %h:%p gateway.example.com" +#define ID_FILE "/etc/xxx" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define USER_KNOWN_HOSTS "%d/my_known_hosts" +#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" +#define BIND_ADDRESS "::1" + + #define LIBSSH_TESTCONFIG1 "libssh_testconfig1.tmp" #define LIBSSH_TESTCONFIG2 "libssh_testconfig2.tmp" #define LIBSSH_TESTCONFIG3 "libssh_testconfig3.tmp" @@ -23,25 +41,190 @@ extern LIBSSH_THREAD int ssh_log_level; #define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" #define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" #define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" -#define LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" +#define LIBSSH_TEST_NONEWLINEEND "libssh_test_NoNewLineEnd.tmp" +#define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" +#define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" + +#define LIBSSH_TESTCONFIG_STRING1 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" + +#define LIBSSH_TESTCONFIG_STRING2 \ + "Include "LIBSSH_TESTCONFIG3"\n" \ + "ProxyCommand "PROXYCMD"\n\n" + +#define LIBSSH_TESTCONFIG_STRING3 \ + "\n\nIdentityFile "ID_FILE"\n" \ + "\n\nKexAlgorithms "KEXALGORITHMS"\n" \ + "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "\n\nPubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" \ + "\n\nMACs "MACS"\n" + +/* Multiple Port settings -> parsing returns early. */ +#define LIBSSH_TESTCONFIG_STRING4 \ + "Port 123\nPort 456\n" + +/* Testing glob include */ +#define LIBSSH_TESTCONFIG_STRING5 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIGGLOB"\n\n" \ + +#define LIBSSH_TESTCONFIG_STRING6 \ + "ProxyCommand "PROXYCMD"\n\n" + +/* new options */ +#define LIBSSH_TESTCONFIG_STRING7 \ + "\tBindAddress "BIND_ADDRESS"\n" \ + "\tConnectTimeout 30\n" \ + "\tLogLevel DEBUG3\n" \ + "\tGlobalKnownHostsFile "GLOBAL_KNOWN_HOSTS"\n" \ + "\tCompression yes\n" \ + "\tStrictHostkeyChecking no\n" \ + "\tGSSAPIDelegateCredentials yes\n" \ + "\tGSSAPIServerIdentity example.com\n" \ + "\tGSSAPIClientIdentity home.sweet\n" \ + "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" + +/* authentication methods */ +#define LIBSSH_TESTCONFIG_STRING8 \ + "Host gss\n" \ + "\tGSSAPIAuthentication yes\n" \ + "Host kbd\n" \ + "\tKbdInteractiveAuthentication yes\n" \ + "Host pass\n" \ + "\tPasswordAuthentication yes\n" \ + "Host pubkey\n" \ + "\tPubkeyAuthentication yes\n" \ + "Host nogss\n" \ + "\tGSSAPIAuthentication no\n" \ + "Host nokbd\n" \ + "\tKbdInteractiveAuthentication no\n" \ + "Host nopass\n" \ + "\tPasswordAuthentication no\n" \ + "Host nopubkey\n" \ + "\tPubkeyAuthentication no\n" + +/* unsupported options and corner cases */ +#define LIBSSH_TESTCONFIG_STRING9 \ + "\n" /* empty line */ \ + "# comment line\n" \ + " # comment line not starting with hash\n" \ + "UnknownConfigurationOption yes\n" \ + "GSSAPIKexAlgorithms yes\n" \ + "ControlMaster auto\n" /* SOC_NA */ \ + "VisualHostkey yes\n" /* SOC_UNSUPPORTED */ \ + "HostName =equal.sign\n" /* valid */ \ + "ProxyJump = many-spaces.com\n" /* valid */ + +/* Match keyword */ +#define LIBSSH_TESTCONFIG_STRING10 \ + "Match host example\n" \ + "\tHostName example.com\n" \ + "Match host example1,example2\n" \ + "\tHostName exampleN\n" \ + "Match user guest\n" \ + "\tHostName guest.com\n" \ + "Match user tester host testhost\n" \ + "\tHostName testhost.com\n" \ + "Match !user tester host testhost\n" \ + "\tHostName nonuser-testhost.com\n" \ + "Match all\n" \ + "\tHostName all-matched.com\n" \ + /* Unsupported options */ \ + "Match originalhost example\n" \ + "\tHostName original-example.com\n" \ + "Match localuser guest\n" \ + "\tHostName local-guest.com\n" + +/* ProxyJump */ +#define LIBSSH_TESTCONFIG_STRING11 \ + "Host simple\n" \ + "\tProxyJump jumpbox\n" \ + "Host user\n" \ + "\tProxyJump user@jumpbox\n" \ + "Host port\n" \ + "\tProxyJump jumpbox:2222\n" \ + "Host two-step\n" \ + "\tProxyJump u1@first:222,u2@second:33\n" \ + "Host none\n" \ + "\tProxyJump none\n" \ + "Host only-command\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "\tProxyJump jumpbox\n" \ + "Host only-jump\n" \ + "\tProxyJump jumpbox\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "Host ipv6\n" \ + "\tProxyJump [2620:52:0::fed]\n" + +/* RekeyLimit combinations */ +#define LIBSSH_TESTCONFIG_STRING12 \ + "Host default\n" \ + "\tRekeyLimit default none\n" \ + "Host data1\n" \ + "\tRekeyLimit 42G\n" \ + "Host data2\n" \ + "\tRekeyLimit 31M\n" \ + "Host data3\n" \ + "\tRekeyLimit 521K\n" \ + "Host time1\n" \ + "\tRekeyLimit default 3D\n" \ + "Host time2\n" \ + "\tRekeyLimit default 2h\n" \ + "Host time3\n" \ + "\tRekeyLimit default 160m\n" \ + "Host time4\n" \ + "\tRekeyLimit default 9600\n" + +/* Multiple IdentityFile settings all are aplied */ +#define LIBSSH_TESTCONFIG_STRING13 \ + "IdentityFile id_rsa_one\n" \ + "IdentityFile id_ecdsa_two\n" + +#define LIBSSH_TEST_PUBKEYTYPES_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_PUBKEYALGORITHMS_STRING \ + "PubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_NONEWLINEEND_STRING \ + "ConnectTimeout 30\n" \ + "LogLevel DEBUG3" + +#define LIBSSH_TEST_NONEWLINEONELINE_STRING \ + "ConnectTimeout 30" + +#define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ + "Include " LIBSSH_TEST_RECURSIVE_INCLUDE -#define USERNAME "testuser" -#define PROXYCMD "ssh -q -W %h:%p gateway.example.com" -#define ID_FILE "/etc/xxx" -#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" -#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" -#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" -#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" -#define USER_KNOWN_HOSTS "%d/my_known_hosts" -#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" -#define BIND_ADDRESS "::1" +/** + * @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) +{ + 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); +} static int setup_config_files(void **state) { - ssh_session session; - int verbosity; + (void) state; /* unused */ unlink(LIBSSH_TESTCONFIG1); unlink(LIBSSH_TESTCONFIG2); @@ -55,152 +238,72 @@ static int setup_config_files(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); - unlink(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); torture_write_file(LIBSSH_TESTCONFIG1, - "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n"); + LIBSSH_TESTCONFIG_STRING1); torture_write_file(LIBSSH_TESTCONFIG2, - "Include "LIBSSH_TESTCONFIG3"\n" - "ProxyCommand "PROXYCMD"\n\n"); + LIBSSH_TESTCONFIG_STRING2); torture_write_file(LIBSSH_TESTCONFIG3, - "\n\nIdentityFile "ID_FILE"\n" - "\n\nKexAlgorithms "KEXALGORITHMS"\n" - "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" - "\n\nPubkeyAcceptedTypes "PUBKEYACCEPTEDTYPES"\n" - "\n\nMACs "MACS"\n"); + LIBSSH_TESTCONFIG_STRING3); /* Multiple Port settings -> parsing returns early. */ torture_write_file(LIBSSH_TESTCONFIG4, - "Port 123\nPort 456\n"); + LIBSSH_TESTCONFIG_STRING4); /* Testing glob include */ torture_write_file(LIBSSH_TESTCONFIG5, - "User "USERNAME"\nInclude "LIBSSH_TESTCONFIGGLOB"\n\n"); + LIBSSH_TESTCONFIG_STRING5); torture_write_file(LIBSSH_TESTCONFIG6, - "ProxyCommand "PROXYCMD"\n\n"); + LIBSSH_TESTCONFIG_STRING6); /* new options */ torture_write_file(LIBSSH_TESTCONFIG7, - "\tBindAddress "BIND_ADDRESS"\n" - "\tConnectTimeout 30\n" - "\tLogLevel DEBUG3\n" - "\tGlobalKnownHostsFile "GLOBAL_KNOWN_HOSTS"\n" - "\tCompression yes\n" - "\tStrictHostkeyChecking no\n" - "\tGSSAPIDelegateCredentials yes\n" - "\tGSSAPIServerIdentity example.com\n" - "\tGSSAPIClientIdentity home.sweet\n" - "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n"); + LIBSSH_TESTCONFIG_STRING7); /* authentication methods */ torture_write_file(LIBSSH_TESTCONFIG8, - "Host gss\n" - "\tGSSAPIAuthentication yes\n" - "Host kbd\n" - "\tKbdInteractiveAuthentication yes\n" - "Host pass\n" - "\tPasswordAuthentication yes\n" - "Host pubkey\n" - "\tPubkeyAuthentication yes\n" - "Host nogss\n" - "\tGSSAPIAuthentication no\n" - "Host nokbd\n" - "\tKbdInteractiveAuthentication no\n" - "Host nopass\n" - "\tPasswordAuthentication no\n" - "Host nopubkey\n" - "\tPubkeyAuthentication no\n"); + LIBSSH_TESTCONFIG_STRING8); /* unsupported options and corner cases */ torture_write_file(LIBSSH_TESTCONFIG9, - "\n" /* empty line */ - "# comment line\n" - " # comment line not starting with hash\n" - "UnknownConfigurationOption yes\n" - "GSSAPIKexAlgorithms yes\n" - "ControlMaster auto\n" /* SOC_NA */ - "VisualHostkey yes\n" /* SOC_UNSUPPORTED */ - ""); + LIBSSH_TESTCONFIG_STRING9); /* Match keyword */ torture_write_file(LIBSSH_TESTCONFIG10, - "Match host example\n" - "\tHostName example.com\n" - "Match host example1,example2\n" - "\tHostName exampleN\n" - "Match user guest\n" - "\tHostName guest.com\n" - "Match user tester host testhost\n" - "\tHostName testhost.com\n" - "Match !user tester host testhost\n" - "\tHostName nonuser-testhost.com\n" - "Match all\n" - "\tHostName all-matched.com\n" - /* Unsupported options */ - "Match originalhost example\n" - "\tHostName original-example.com\n" - "Match localuser guest\n" - "\tHostName local-guest.com\n" - ""); + LIBSSH_TESTCONFIG_STRING10); /* ProxyJump */ torture_write_file(LIBSSH_TESTCONFIG11, - "Host simple\n" - "\tProxyJump jumpbox\n" - "Host user\n" - "\tProxyJump user@jumpbox\n" - "Host port\n" - "\tProxyJump jumpbox:2222\n" - "Host two-step\n" - "\tProxyJump u1@first:222,u2@second:33\n" - "Host none\n" - "\tProxyJump none\n" - "Host only-command\n" - "\tProxyCommand "PROXYCMD"\n" - "\tProxyJump jumpbox\n" - "Host only-jump\n" - "\tProxyJump jumpbox\n" - "\tProxyCommand "PROXYCMD"\n" - "Host ipv6\n" - "\tProxyJump [2620:52:0::fed]\n" - ""); + LIBSSH_TESTCONFIG_STRING11); /* RekeyLimit combinations */ torture_write_file(LIBSSH_TESTCONFIG12, - "Host default\n" - "\tRekeyLimit default none\n" - "Host data1\n" - "\tRekeyLimit 42G\n" - "Host data2\n" - "\tRekeyLimit 31M\n" - "Host data3\n" - "\tRekeyLimit 521K\n" - "Host time1\n" - "\tRekeyLimit default 3D\n" - "Host time2\n" - "\tRekeyLimit default 2h\n" - "Host time3\n" - "\tRekeyLimit default 160m\n" - "Host time4\n" - "\tRekeyLimit default 9600\n" - ""); - - torture_write_file(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES, - "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n"); + LIBSSH_TESTCONFIG_STRING12); - session = ssh_new(); + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, + LIBSSH_TEST_PUBKEYTYPES_STRING); - verbosity = torture_libssh_verbosity(); - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + torture_write_file(LIBSSH_TEST_PUBKEYALGORITHMS, + LIBSSH_TEST_PUBKEYALGORITHMS_STRING); - *state = session; + torture_write_file(LIBSSH_TEST_NONEWLINEEND, + LIBSSH_TEST_NONEWLINEEND_STRING); + + torture_write_file(LIBSSH_TEST_NONEWLINEONELINE, + LIBSSH_TEST_NONEWLINEONELINE_STRING); return 0; } -static int teardown(void **state) +static int teardown_config_files(void **state) { + (void) state; /* unused */ + unlink(LIBSSH_TESTCONFIG1); unlink(LIBSSH_TESTCONFIG2); unlink(LIBSSH_TESTCONFIG3); @@ -213,27 +316,67 @@ static int teardown(void **state) unlink(LIBSSH_TESTCONFIG10); unlink(LIBSSH_TESTCONFIG11); unlink(LIBSSH_TESTCONFIG12); - unlink(LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + + return 0; +} + +static int setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + *state = session; + + return 0; +} + +static int setup_no_sshdir(void **state) +{ + ssh_session session = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ ssh_free(*state); return 0; } /** - * @brief tests ssh_config_parse_file with Include directives + * @brief tests ssh config parsing with Include directives */ -static void torture_config_from_file(void **state) { - ssh_session session = *state; +static void torture_config_include(void **state, + const char *file, const char *string) +{ int ret; char *v = NULL; char *fips_algos = NULL; + ssh_session session = *state; - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG1); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); /* Test the variable presence */ - ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); assert_true(ret == 0); assert_non_null(v); @@ -262,7 +405,8 @@ static void torture_config_from_file(void **state) { SAFE_FREE(fips_algos); fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); assert_non_null(fips_algos); - assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + fips_algos); SAFE_FREE(fips_algos); fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); assert_non_null(fips_algos); @@ -270,19 +414,24 @@ static void torture_config_from_file(void **state) { SAFE_FREE(fips_algos); fips_algos = ssh_keep_fips_algos(SSH_MAC_C_S, MACS); assert_non_null(fips_algos); - assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], + fips_algos); SAFE_FREE(fips_algos); fips_algos = ssh_keep_fips_algos(SSH_MAC_S_C, MACS); assert_non_null(fips_algos); - assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + fips_algos); SAFE_FREE(fips_algos); } else { assert_non_null(session->opts.wanted_methods[SSH_KEX]); - assert_string_equal(session->opts.wanted_methods[SSH_KEX], KEXALGORITHMS); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + KEXALGORITHMS); assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); - assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], HOSTKEYALGORITHMS); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + HOSTKEYALGORITHMS); assert_non_null(session->opts.pubkey_accepted_types); - assert_string_equal(session->opts.pubkey_accepted_types, PUBKEYACCEPTEDTYPES); + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], MACS); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); @@ -290,28 +439,66 @@ static void torture_config_from_file(void **state) { } } +/** + * @brief tests ssh_config_parse_file with Include directives from file + */ +static void torture_config_include_file(void **state) +{ + torture_config_include(state, LIBSSH_TESTCONFIG1, NULL); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_string(void **state) +{ + torture_config_include(state, NULL, LIBSSH_TESTCONFIG_STRING1); +} + +/** + * @brief tests ssh_config_parse_file with recursive Include directives from file + */ +static void torture_config_include_recursive_file(void **state) +{ + _parse_config(*state, LIBSSH_TEST_RECURSIVE_INCLUDE, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_recursive_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TEST_RECURSIVE_INCLUDE_STRING, SSH_OK); +} + /** * @brief tests ssh_config_parse_file with multiple Port settings. */ -static void torture_config_double_ports(void **state) { - ssh_session session = *state; - int ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG4); - assert_true(ret == 0); +static void torture_config_double_ports_file(void **state) +{ + _parse_config(*state, LIBSSH_TESTCONFIG4, NULL, SSH_OK); } -static void torture_config_glob(void **state) { - ssh_session session = *state; - int ret; +/** + * @brief tests ssh_config_parse_string with multiple Port settings. + */ +static void torture_config_double_ports_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TESTCONFIG_STRING4, SSH_OK); +} + +static void torture_config_glob(void **state, + const char *file, const char *string) +{ #if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + int ret; char *v; -#endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ + ssh_session session = *state; - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG5); - assert_true(ret == 0); /* non-existing files should not error */ + _parse_config(session, file, string, SSH_OK); /* Test the variable presence */ -#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); assert_true(ret == 0); assert_non_null(v); @@ -328,16 +515,25 @@ static void torture_config_glob(void **state) { #endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ } +static void torture_config_glob_file(void **state) +{ + torture_config_glob(state, LIBSSH_TESTCONFIG5, NULL); +} + +static void torture_config_glob_string(void **state) +{ + torture_config_glob(state, NULL, LIBSSH_TESTCONFIG_STRING5); +} + /** * @brief Verify the new options are passed from configuration */ -static void torture_config_new(void **state) +static void torture_config_new(void ** state, + const char *file, const char *string) { ssh_session session = *state; - int ret = 0; - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG7); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.knownhosts, USER_KNOWN_HOSTS); assert_string_equal(session->opts.global_knownhosts, GLOBAL_KNOWN_HOSTS); @@ -345,12 +541,14 @@ static void torture_config_new(void **state) assert_string_equal(session->opts.bindaddr, BIND_ADDRESS); #ifdef WITH_ZLIB assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,zlib,none"); assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], - "zlib@openssh.com,zlib"); + "zlib@openssh.com,zlib,none"); #else - assert_null(session->opts.wanted_methods[SSH_COMP_C_S]); - assert_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); #endif /* WITH_ZLIB */ assert_int_equal(session->opts.StrictHostKeyChecking, 0); assert_int_equal(session->opts.gss_delegate_creds, 1); @@ -361,33 +559,40 @@ static void torture_config_new(void **state) assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); } +static void torture_config_new_file(void **state) +{ + torture_config_new(state, LIBSSH_TESTCONFIG7, NULL); +} + +static void torture_config_new_string(void **state) +{ + torture_config_new(state, NULL, LIBSSH_TESTCONFIG_STRING7); +} + /** * @brief Verify the authentication methods from configuration are effective */ -static void torture_config_auth_methods(void **state) { +static void torture_config_auth_methods(void **state, + const char *file, const char *string) +{ ssh_session session = *state; - int ret = 0; /* gradually disable all the methods based on different hosts */ ssh_options_set(session, SSH_OPTIONS_HOST, "nogss"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_false(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "nokbd"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "nopass"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_false(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "nopubkey"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_false(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); /* no method should be left enabled */ @@ -396,508 +601,682 @@ static void torture_config_auth_methods(void **state) { /* gradually enable them again */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "gss"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_true(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "kbd"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "pass"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_true(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); ssh_options_set(session, SSH_OPTIONS_HOST, "pubkey"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG8); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); assert_true(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); } +/** + * @brief Verify the authentication methods from configuration file + * are effective + */ +static void torture_config_auth_methods_file(void **state) +{ + torture_config_auth_methods(state, LIBSSH_TESTCONFIG8, NULL); +} + +/** + * @brief Verify the authentication methods from configuration string + * are effective + */ +static void torture_config_auth_methods_string(void **state) +{ + torture_config_auth_methods(state, NULL, LIBSSH_TESTCONFIG_STRING8); +} + /** * @brief Verify the configuration parser does not choke on unknown * or unsupported configuration options */ -static void torture_config_unknown(void **state) { +static void torture_config_unknown(void **state, + const char *file, const char *string) +{ ssh_session session = *state; int ret = 0; /* test corner cases */ - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG9); - assert_true(ret == 0); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "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); } +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration file + */ +static void torture_config_unknown_file(void **state) +{ + torture_config_unknown(state, LIBSSH_TESTCONFIG9, NULL); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration string + */ +static void torture_config_unknown_string(void **state) +{ + torture_config_unknown(state, NULL, LIBSSH_TESTCONFIG_STRING9); +} /** * @brief Verify the configuration parser accepts Match keyword with * full OpenSSH syntax. */ -static void torture_config_match(void **state) +static void torture_config_match(void **state, + const char *file, const char *string) { ssh_session session = *state; char *localuser = NULL; - char config[1024]; - int ret = 0; + const char *config; + char config_string[1024]; /* Without any settings we should get all-matched.com hostname */ + torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "all-matched.com"); /* Hostname example does simple hostname matching */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "example"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "example.com"); /* We can match also both hosts from a comma separated list */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "example1"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "exampleN"); torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "example2"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "exampleN"); /* We can match by user */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_USER, "guest"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "guest.com"); /* We can combine two options on a single line to match both of them */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_USER, "tester"); ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "testhost.com"); /* We can also negate conditions */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_USER, "not-tester"); ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "nonuser-testhost.com"); + /* In this part, we try various other config files and strings. */ + /* Match final is not completely supported, but should do quite much the * same as "match all". The trailing "all" is not mandatory. */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match final all\n" - "\tHostName final-all.com\n" - ""); + config = "Match final all\n" + "\tHostName final-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "final-all.com"); - torture_write_file(LIBSSH_TESTCONFIG10, - "Match final\n" - "\tHostName final.com\n" - ""); + config = "Match final\n" + "\tHostName final.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "final.com"); - /* Match canonical is not completely supported, but should do quite much the - * same as "match all". The trailing "all" is not mandatory. */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match canonical all\n" - "\tHostName canonical-all.com\n" - ""); + /* Match canonical is not completely supported, but should do quite + * much the same as "match all". The trailing "all" is not mandatory. */ + config = "Match canonical all\n" + "\tHostName canonical-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "canonical-all.com"); - torture_write_file(LIBSSH_TESTCONFIG10, - "Match canonical all\n" - "\tHostName canonical.com\n" - ""); + config = "Match canonical all\n" + "\tHostName canonical.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.host, "canonical.com"); localuser = ssh_get_local_username(); assert_non_null(localuser); - snprintf(config, sizeof(config), + snprintf(config_string, sizeof(config_string), "Match localuser %s\n" - "\tHostName otherhost\n" - "", localuser); + "\tHostName otherhost\n", + localuser); + config = config_string; free(localuser); - torture_write_file(LIBSSH_TESTCONFIG10, config); + if (file != NULL) { + 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, "otherhost"); + + config = "Match exec true\n" + "\tHostName execed-true.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-true.com"); +#endif + + config = "Match !exec false\n" + "\tHostName execed-false.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-false.com"); +#endif + + config = "Match exec \"test 1 -eq 1\"\n" + "\tHostName execed-arguments.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-arguments.com"); +#endif /* Try to create some invalid configurations */ /* Missing argument to Match*/ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match\n" - "\tHost missing.com\n" - ""); + config = "Match\n" + "\tHost missing.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing argument to unsupported option originalhost */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match originalhost\n" - "\tHost originalhost.com\n" - ""); + config = "Match originalhost\n" + "\tHost originalhost.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing argument to option localuser */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match localuser\n" - "\tUser localuser2\n" - ""); + config = "Match localuser\n" + "\tUser localuser2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing argument to option user */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match user\n" - "\tUser user2\n" - ""); + config = "Match user\n" + "\tUser user2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing argument to option host */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match host\n" - "\tUser host2\n" - ""); + config = "Match host\n" + "\tUser host2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); - /* Missing argument to unsupported option exec */ - torture_write_file(LIBSSH_TESTCONFIG10, - "Match exec\n" - "\tUser exec\n" - ""); + /* Missing argument to option exec */ + config = "Match exec\n" + "\tUser exec\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG10); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration file. + */ +static void torture_config_match_file(void **state) +{ + torture_config_match(state, LIBSSH_TESTCONFIG10, NULL); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration string. + */ +static void torture_config_match_string(void **state) +{ + torture_config_match(state, NULL, LIBSSH_TESTCONFIG_STRING10); } /** * @brief Verify we can parse ProxyJump configuration option */ -static void torture_config_proxyjump(void **state) { +static void torture_config_proxyjump(void **state, + const char *file, const char *string) +{ ssh_session session = *state; - int ret = 0; + const char *config; /* Simplest version with just a hostname */ + torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); /* With username */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "user"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, "ssh -l user -W [%h]:%p jumpbox"); /* With port */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "port"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, "ssh -p 2222 -W [%h]:%p jumpbox"); /* Two step jump */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, "ssh -l u1 -p 222 -J u2@second:33 -W [%h]:%p first"); /* none */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "none"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_true(session->opts.ProxyCommand == NULL); /* If also ProxyCommand is specifed, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, PROXYCMD); /* If also ProxyCommand is specifed, the first is applied */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); /* IPv6 address */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); 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 */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host bad-port\n" - "\tProxyJump jumpbox:22bad22\n" - ""); + 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"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Too many @ */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host bad-hostname\n" - "\tProxyJump user@principal.com@jumpbox:22\n" - ""); + config = "Host bad-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, "bad-hostname"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Braces mismatch in hostname */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host mismatch\n" - "\tProxyJump [::1\n" - ""); + config = "Host mismatch\n" + "\tProxyJump [::1\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Bad host-port separator */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host beef\n" - "\tProxyJump [dead::beef]::22\n" - ""); + config = "Host beef\n" + "\tProxyJump [dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing hostname */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-host\n" - "\tProxyJump user@:22\n" - ""); + config = "Host no-host\n" + "\tProxyJump user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing user */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-user\n" - "\tProxyJump @host:22\n" - ""); + config = "Host no-user\n" + "\tProxyJump @host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing port */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-port\n" - "\tProxyJump host:\n" - ""); + config = "Host no-port\n" + "\tProxyJump host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Non-numeric port in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host bad-port-2\n" - "\tProxyJump localhost,jumpbox:22bad22\n" - ""); + config = "Host bad-port-2\n" + "\tProxyJump localhost,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-2"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Too many @ in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host bad-hostname\n" - "\tProxyJump localhost,user@principal.com@jumpbox:22\n" - ""); + 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"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Braces mismatch in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host mismatch\n" - "\tProxyJump localhost,[::1:20\n" - ""); + config = "Host mismatch\n" + "\tProxyJump localhost,[::1:20\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Bad host-port separator in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host beef\n" - "\tProxyJump localhost,[dead::beef]::22\n" - ""); + config = "Host beef\n" + "\tProxyJump localhost,[dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing hostname in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-host\n" - "\tProxyJump localhost,user@:22\n" - ""); + config = "Host no-host\n" + "\tProxyJump localhost,user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing user in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-user\n" - "\tProxyJump localhost,@host:22\n" - ""); + config = "Host no-user\n" + "\tProxyJump localhost,@host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); /* Missing port in second jump */ - torture_write_file(LIBSSH_TESTCONFIG11, - "Host no-port\n" - "\tProxyJump localhost,host:\n" - ""); + config = "Host no-port\n" + "\tProxyJump localhost,host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); - assert_ssh_return_code_equal(session, ret, SSH_ERROR); + _parse_config(session, file, string, SSH_ERROR); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from file + */ +static void torture_config_proxyjump_file(void **state) +{ + torture_config_proxyjump(state, LIBSSH_TESTCONFIG11, NULL); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from string + */ +static void torture_config_proxyjump_string(void **state) +{ + torture_config_proxyjump(state, NULL, LIBSSH_TESTCONFIG_STRING11); } /** * @brief Verify the configuration parser handles all the possible * versions of RekeyLimit configuration option. */ -static void torture_config_rekey(void **state) +static void torture_config_rekey(void **state, + const char *file, const char *string) { ssh_session session = *state; - int ret = 0; /* Default values */ ssh_options_set(session, SSH_OPTIONS_HOST, "default"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 0); assert_int_equal(session->opts.rekey_time, 0); /* 42 GB */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "data1"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); - assert_int_equal(session->opts.rekey_data, (uint64_t) 42 * 1024 * 1024 * 1024); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, + (uint64_t) 42 * 1024 * 1024 * 1024); assert_int_equal(session->opts.rekey_time, 0); /* 41 MB */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "data2"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 31 * 1024 * 1024); assert_int_equal(session->opts.rekey_time, 0); /* 521 KB */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "data3"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 521 * 1024); assert_int_equal(session->opts.rekey_time, 0); /* default 3D */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "time1"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 0); assert_int_equal(session->opts.rekey_time, 3 * 24 * 60 * 60 * 1000); /* default 2h */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "time2"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 0); assert_int_equal(session->opts.rekey_time, 2 * 60 * 60 * 1000); /* default 160m */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "time3"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 0); assert_int_equal(session->opts.rekey_time, 160 * 60 * 1000); /* default 9600 [s] */ torture_reset_config(session); ssh_options_set(session, SSH_OPTIONS_HOST, "time4"); - ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG12); - assert_ssh_return_code(session, ret); + _parse_config(session, file, string, SSH_OK); assert_int_equal(session->opts.rekey_data, 0); assert_int_equal(session->opts.rekey_time, 9600 * 1000); } /** - * @brief test ssh_config_parse_file with PubkeyAcceptedKeyTypes + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in file + */ +static void torture_config_rekey_file(void **state) +{ + torture_config_rekey(state, LIBSSH_TESTCONFIG12, NULL); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in string + */ +static void torture_config_rekey_string(void **state) +{ + torture_config_rekey(state, NULL, LIBSSH_TESTCONFIG_STRING12); +} + +/** + * @brief test PubkeyAcceptedKeyTypes helper function */ -static void torture_config_pubkeyacceptedkeytypes(void **state) +static void torture_config_pubkeytypes(void **state, + const char *file, const char *string) { ssh_session session = *state; - int rc; char *fips_algos; - rc = ssh_config_parse_file(session, LIBSSH_TEST_PUBKEYACCEPTEDKEYTYPES); - assert_int_equal(rc, SSH_OK); + _parse_config(session, file, string, SSH_OK); if (ssh_fips_mode()) { fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); @@ -905,10 +1284,324 @@ static void torture_config_pubkeyacceptedkeytypes(void **state) assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); SAFE_FREE(fips_algos); } else { - assert_string_equal(session->opts.pubkey_accepted_types, PUBKEYACCEPTEDTYPES); + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); } } +/** + * @brief test parsing PubkeyAcceptedKeyTypes from file + */ +static void torture_config_pubkeytypes_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYTYPES, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedKeyTypes from string + */ +static void torture_config_pubkeytypes_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYTYPES_STRING); +} + +/** + * @brief test parsing PubkeyAcceptedKAlgorithms from file + */ +static void torture_config_pubkeyalgorithms_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYALGORITHMS, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedAlgorithms from string + */ +static void torture_config_pubkeyalgorithms_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYALGORITHMS_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineend(void **state, + const char *file, const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineend_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEEND, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineend_string(void **state) +{ + torture_config_nonewlineend(state, NULL, LIBSSH_TEST_NONEWLINEEND_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineoneline(void **state, + const char *file, + const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineoneline_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEONELINE, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineoneline_string(void **state) +{ + torture_config_nonewlineoneline(state, + NULL, LIBSSH_TEST_NONEWLINEONELINE_STRING); +} + +/* ssh_config_get_cmd() does three things: + * * Strips leading whitespace + * * Terminate the characted on the end of next quotes-enclosed string + * * Terminate on the end of line + */ +static void torture_config_parser_get_cmd(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; + + (void) state; + + /* Ignore leading whitespace */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* but keeps the trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string \t\t "); + assert_int_equal(*p, '\0'); + + /* should drop the quotes and split them into separate arguments */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, ' '); + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* But it does not split tokens by whitespace + * if they are not quoted, which is weird */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "multi string something"); + assert_int_equal(*p, '\0'); +} + +/* ssh_config_get_token() should behave as expected + * * Strip leading whitespace + * * Return first token separated by whitespace or equal sign, + * respecting quotes! + */ +static void torture_config_parser_get_token(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; + + (void) state; + + /* Ignore leading whitespace (from get_cmd() already */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \t\t string", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* drops trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, "string \t\t ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* Correctly handles tokens in quotes */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\"multi string\" something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* Consistently splits unquoted strings */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "multi string something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* It is made to parse also option=value pairs as well */ + strncpy(data, " key=value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* spaces are allowed also around the equal sign */ + strncpy(data, " key = value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key = value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* correctly parses even key=value pairs with either one in quotes */ + strncpy(data, " key=\"value with spaces\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=\"value with spaces\" ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + /* Only one equal sign is allowed */ + strncpy(data, "key==value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, "key==value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* Unmatched quotes */ + strncpy(data, " \"value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \"value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); +} + /* match_pattern() sanity tests */ static void torture_config_match_pattern(void **state) @@ -936,7 +1629,8 @@ static void torture_config_match_pattern(void **state) assert_int_equal(rv, 1); rv = match_pattern("aa", "?", MAX_MATCH_RECURSION); assert_int_equal(rv, 0); - rv = match_pattern("?", "a", MAX_MATCH_RECURSION); /* Wildcard in search string */ + /* Wildcard in search string */ + rv = match_pattern("?", "a", MAX_MATCH_RECURSION); assert_int_equal(rv, 0); rv = match_pattern("?", "?", MAX_MATCH_RECURSION); assert_int_equal(rv, 1); @@ -946,7 +1640,8 @@ static void torture_config_match_pattern(void **state) assert_int_equal(rv, 1); rv = match_pattern("aa", "*", MAX_MATCH_RECURSION); assert_int_equal(rv, 1); - rv = match_pattern("*", "a", MAX_MATCH_RECURSION); /* Wildcard in search string */ + /* Wildcard in search string */ + rv = match_pattern("*", "a", MAX_MATCH_RECURSION); assert_int_equal(rv, 0); rv = match_pattern("*", "*", MAX_MATCH_RECURSION); assert_int_equal(rv, 1); @@ -986,32 +1681,208 @@ static void torture_config_match_pattern(void **state) /* Limit the maximum recursion */ rv = match_pattern("hostname", "*p*a*t*t*e*r*n*", 5); assert_int_equal(rv, 0); - rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5); /* Too much recursion */ + /* Too much recursion */ + rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5); assert_int_equal(rv, 0); } +/* Identity file can be specified multiple times in the configuration + */ +static void torture_config_identity(void **state) +{ + const char *id = NULL; + struct ssh_iterator *it = NULL; + ssh_session session = *state; + + _parse_config(session, NULL, LIBSSH_TESTCONFIG_STRING13, SSH_OK); + + it = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it); + id = it->data; + /* The identities are prepended to the list so we start with second one */ + assert_string_equal(id, "id_ecdsa_two"); + + it = it->next; + assert_non_null(it); + id = it->data; + assert_string_equal(id, "id_rsa_one"); +} + +/* Make absolute path for config include + */ +static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) +{ + ssh_session session = *state; + char *result = NULL; +#ifndef _WIN32 + char h[256]; + char *user; + char *home; + + user = getenv("USER"); + if (user == NULL) { + user = getenv("LOGNAME"); + } + + /* in certain CIs there no such variables */ + if (!user) { + struct passwd *pw = getpwuid(getuid()); + if (pw){ + user = pw->pw_name; + } + } + + home = getenv("HOME"); + assert_non_null(home); +#endif + + /* Absolute path already -- should not change in any case */ + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + + /* Global is relative to /etc/ssh/ */ + result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf"); + free(result); + result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf"); + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + result = ssh_config_make_absolute(session, "my_config", 0); + if (no_sshdir_fails) { + assert_null(result); + } else { + /* The path depends on the PWD so lets skip checking the actual path here */ + assert_non_null(result); + } + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh"); + result = ssh_config_make_absolute(session, "my_config", 0); + assert_string_equal(result, "/tmp/ssh/my_config"); + free(result); + +#ifndef _WIN32 + /* Tilde expansion works only in user config */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + /* in global config its just prefixed without expansion */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/~/.ssh/config.d/*.conf"); + free(result); + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 1); + snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); + assert_string_equal(result, h); + free(result); +#endif +} + +static void torture_config_make_absolute(void **state) +{ + torture_config_make_absolute_int(state, 0); +} + +static void torture_config_make_absolute_no_sshdir(void **state) +{ + torture_config_make_absolute_int(state, 1); +} -int torture_run_tests(void) { +int torture_run_tests(void) +{ int rc; struct CMUnitTest tests[] = { - cmocka_unit_test(torture_config_from_file), - cmocka_unit_test(torture_config_double_ports), - cmocka_unit_test(torture_config_glob), - cmocka_unit_test(torture_config_new), - cmocka_unit_test(torture_config_auth_methods), - cmocka_unit_test(torture_config_unknown), - cmocka_unit_test(torture_config_match), - cmocka_unit_test(torture_config_proxyjump), - cmocka_unit_test(torture_config_rekey), - cmocka_unit_test(torture_config_pubkeyacceptedkeytypes), - cmocka_unit_test(torture_config_match_pattern), + cmocka_unit_test_setup_teardown(torture_config_include_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_new_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_new_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_match_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_match_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_file, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_string, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_cmd, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_token, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_match_pattern, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_identity, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, teardown), }; ssh_init(); torture_filter_tests(tests); - rc = cmocka_run_group_tests(tests, setup_config_files, teardown); + rc = cmocka_run_group_tests(tests, + setup_config_files, teardown_config_files); ssh_finalize(); return rc; } diff --git a/libssh/tests/unittests/torture_crypto.c b/libssh/tests/unittests/torture_crypto.c index fd5e775..3f84e19 100644 --- a/libssh/tests/unittests/torture_crypto.c +++ b/libssh/tests/unittests/torture_crypto.c @@ -4,6 +4,7 @@ #include "torture.h" #include "libssh/crypto.h" +#include "libssh/chacha20-poly1305-common.h" uint8_t key[32] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" @@ -110,10 +111,223 @@ static void torture_crypto_aes256_cbc(void **state) ssh_cipher_clear(&cipher); } +uint8_t chacha20poly1305_key[CHACHA20_KEYLEN*2] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c" + "\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b" + "\x3c\x3d\x3e\x3f"; + +#define CLEARTEXT_LENGTH 144 +uint8_t chacha20poly1305_cleartext[CLEARTEXT_LENGTH] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint64_t chacha20poly1305_seq = (uint64_t)1234567890 * 98765431; + +uint8_t chacha20poly1305_encrypted[sizeof(uint32_t) + CLEARTEXT_LENGTH + POLY1305_TAGLEN] = + "\xac\x2e\x4c\x54\xf6\x97\x75\xb4\x3b\x8f\xb0\x8e\xb0\x0a\x8e" + "\xb3\x90\x21\x0d\x7a\xb6\xd3\x03\xf6\xbc\x6e\x3a\x32\x67\xe1" + "\x13\x65\x43\x3b\x34\x9d\xcb\x62\x7e\x0a\x80\xb0\x45\x87\x07" + "\x85\x49\x8d\x23\x5f\xac\x9c\x8b\xa8\xd5\x01\x12\xfe\x52\xc6" + "\x99\xb4\xf2\xde\x12\x78\x79\xea\x1c\x5f\x45\xcd\xf7\xe4\xa0" + "\x66\x15\x7f\xe3\xf4\x73\x3b\xe0\x52\xac\x2a\x00\x73\xd0\xd7" + "\x95\xa9\xb9\x3a\xe0\x50\x13\xf4\xdc\xfc\x2a\x64\xb5\xcf\x29" + "\x88\xef\x4c\x56\x10\x30\x28\xbb\x59\xb8\x23\x58\xab\x01\xa2" + "\xab\x6b\xdd\xee\x20\x43\xe1\xec\x7a\xe1\xaa\x8b\x60\x19\xde" + "\x3a\xd1\xd6\x80\x49\x7d\x5c\x81\xb8\x96\xad\x62\x32\xe5\x25" + "\x72\xe9\x63\x96\xa1\x44\x25\x91\xe1\xdc\x01\xc7\x5c\xa9"; + +static void torture_crypto_chacha20poly1305(void **state) +{ + uint8_t input[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)]; + uint8_t output[sizeof(input) + POLY1305_TAGLEN] = {0}; + uint8_t *outtag = output + sizeof(input); + struct ssh_cipher_struct cipher = {0}; + uint32_t in_length; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + assert_int_equal(sizeof(output), sizeof(chacha20poly1305_encrypted)); + + in_length = htonl(sizeof(chacha20poly1305_cleartext)); + memcpy(input, &in_length, sizeof(uint32_t)); + memcpy(input + sizeof(uint32_t), chacha20poly1305_cleartext, + sizeof(chacha20poly1305_cleartext)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sizeof(chacha20poly1305_key) * 8, cipher.keysize); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.aead_encrypt); + + rc = cipher.set_encrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + cipher.aead_encrypt(&cipher, input, output, sizeof(input), outtag, + chacha20poly1305_seq); + assert_memory_equal(output, chacha20poly1305_encrypted, + sizeof(chacha20poly1305_encrypted)); + ssh_cipher_clear(&cipher); + + memset(output, '\0', sizeof(output)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.aead_decrypt); + assert_non_null(cipher.aead_decrypt_length); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, chacha20poly1305_encrypted, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, chacha20poly1305_encrypted, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + assert_memory_equal(output, input, sizeof(input)); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_packet_length(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted packet length */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_data(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted data */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[100] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_tag(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted tag */ + assert_int_equal(sizeof(encrypted_bad), sizeof(chacha20poly1305_encrypted)); + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[sizeof(encrypted_bad) - 1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + int torture_run_tests(void) { int rc; const struct CMUnitTest tests[] = { cmocka_unit_test(torture_crypto_aes256_cbc), + cmocka_unit_test(torture_crypto_chacha20poly1305), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_packet_length), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_data), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_tag), }; ssh_init(); diff --git a/libssh/tests/unittests/torture_knownhosts_parsing.c b/libssh/tests/unittests/torture_knownhosts_parsing.c index 1c2ccc1..fffa829 100644 --- a/libssh/tests/unittests/torture_knownhosts_parsing.c +++ b/libssh/tests/unittests/torture_knownhosts_parsing.c @@ -4,10 +4,10 @@ #define LIBSSH_STATIC #include -#include "torture.h" #include "knownhosts.c" +#include "torture.h" #if (defined _WIN32) || (defined _WIN64) #ifndef S_IRWXO #define S_IRWXO 0 @@ -17,6 +17,7 @@ #endif #endif +#define LOCALHOST_DSS_LINE "localhost,127.0.0.1 ssh-dss AAAAB3NzaC1kc3MAAACBAIK3RTEWBw+rAPcYUM2Qq4kEw59gXpUQ/WvkdeY7QDO64MHaaorySj8xsraNudmQFh4xb/i5Q1EMnNchOFxtilfU5bUJgdTvetyZEWFL+2HxqBs8GaWRyB1vtSFAw3GO8VUEnjF844N3dNyLoc0NX8IvzwNIaQho6KTsueQlG1X9AAAAFQCXUl4a5UvElL4thi/8QlxR5PtEewAAAIBqNpl5MTBxKQu5jT0+WASa7pAqwT53ofv7ZTDIEokYRb57/nwzDgkcs1fsBRrI6eczJ/VlXWwKbsgkx2Nh3ZiWYwC+HY5uqRpDaj3HERC6LMn4dzdcl29fYeziEibCbRjJX5lZF2vIaA1Ewv8yT0UlunyHZRiyw4WlEglkf/NITAAAAIBxLsdBBXn+8qEYwWK9KT+arRqNXC/lrl0Fp5YyxGNGCv82JcnuOShGGTzhYf8AtTCY1u5oixiW9kea6KXGAKgTjfJShr7n47SZVfOPOrBT3VLhRdGGO3GblDUppzfL8wsEdoqXjzrJuxSdrGnkFu8S9QjkPn9dCtScvWEcluHqMw==" #define LOCALHOST_RSA_LINE "localhost,127.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDD7g+vV5cvxxGN0Ldmda4WZCPgRaxV1tV+1KRZoGUNUI61h0X4bmmGaAPRQBCz4G1d9bawqDqEqnpFWazrxBU5cQtISSjzuDJKovLGliky/ShTszee1Thszg3qVNk9gGOWj7jn/HDaOxRlp003Bp47MOdnMnK/oftllFDfY2fF5IRpE6sSIGtg2ZDtF95TV5/9W2oMOIAy8u/83tuibYlNPa1X/von5LgdaPLn6Bk16bQKIhAhlMtFZH8MBYEWe4ZtOGaSWKOsK9MM/RTMlwPi6PkfoHNl4MCMupjx+CdLXwbQEt9Ww+bBIaCui2VWBEiruVbIgJh0W2Tal0e2BzYZ What a Wurst!" #define LOCALHOST_ECDSA_SHA1_NISTP256_LINE "localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWmI0n0Tn5+zR7pPGcKYszRbJ/T0T3QfzRBSMMiyebGKRY8tjkU5h2l/UMugzOrOyWqMGQDgQn+a0aMunhKMg0=" #define LOCALHOST_DEFAULT_ED25519 "localhost ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" @@ -144,6 +145,38 @@ static int setup_knownhosts_file_duplicate(void **state) return rc; } +#ifndef HAVE_DSA +static int setup_knownhosts_file_unsupported_type(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DSS_LINE, + sizeof(char), + strlen(LOCALHOST_DSS_LINE), + fp); + if (nwritten != strlen(LOCALHOST_DSS_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} +#endif + static int teardown_knownhosts_file(void **state) { char *tmp_file = *state; @@ -396,6 +429,31 @@ static void torture_knownhosts_get_algorithms_names(void **state) ssh_free(session); } +#ifndef HAVE_DSA +/* Do not remove this test if we completly remove DSA support! */ +static void torture_knownhosts_get_algorithms_names_unsupported(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_null(names); + + ssh_free(session); +} +#endif + static void torture_knownhosts_algorithms_wanted(void **state) { const char *knownhosts_file = *state; @@ -574,13 +632,9 @@ static void torture_knownhosts_algorithms(void **state) char *algo_list = NULL; ssh_session session; bool process_config = false; - const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," - "ecdsa-sha2-nistp256" -#ifdef HAVE_DSA - ",ssh-dss" -#endif - ; + "ecdsa-sha2-nistp256"; const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; @@ -613,13 +667,9 @@ static void torture_knownhosts_algorithms_global(void **state) char *algo_list = NULL; ssh_session session; bool process_config = false; - const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa," + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," - "ecdsa-sha2-nistp256" -#ifdef HAVE_DSA - ",ssh-dss" -#endif - ; + "ecdsa-sha2-nistp256"; const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; @@ -668,6 +718,11 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, setup_knownhosts_file, teardown_knownhosts_file), +#ifndef HAVE_DSA + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names_unsupported, + setup_knownhosts_file_unsupported_type, + teardown_knownhosts_file), +#endif cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, setup_knownhosts_file, teardown_knownhosts_file), diff --git a/libssh/tests/unittests/torture_misc.c b/libssh/tests/unittests/torture_misc.c index 0a48abb..9e346ff 100644 --- a/libssh/tests/unittests/torture_misc.c +++ b/libssh/tests/unittests/torture_misc.c @@ -4,8 +4,8 @@ #include #endif #include -#ifndef _WIN32 +#ifndef _WIN32 #define _POSIX_PTHREAD_SEMANTICS #include #endif @@ -13,8 +13,8 @@ #define LIBSSH_STATIC #include -#include "torture.h" #include "misc.c" +#include "torture.h" #include "error.c" #define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." @@ -168,17 +168,25 @@ static void torture_path_expand_tilde_unix(void **state) { static void torture_path_expand_escape(void **state) { ssh_session session = *state; - const char *s = "%d/%h/by/%r"; + const char *s = "%d/%h/%p/by/%r"; char *e; session->opts.sshdir = strdup("guru"); session->opts.host = strdup("meditation"); + session->opts.port = 0; session->opts.username = strdup("root"); e = ssh_path_expand_escape(session, s); assert_non_null(e); - assert_string_equal(e, "guru/meditation/by/root"); - free(e); + assert_string_equal(e, "guru/meditation/22/by/root"); + ssh_string_free_char(e); + + session->opts.port = 222; + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/222/by/root"); + ssh_string_free_char(e); } static void torture_path_expand_known_hosts(void **state) { @@ -656,6 +664,102 @@ static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) assert_string_equal(buffer, "a\\nb\\n"); } +static void torture_ssh_strreplace(void **state) +{ + char test_string1[] = "this;is;a;test"; + char test_string2[] = "test;is;a;this"; + char test_string3[] = "this;test;is;a"; + char *replaced_string = NULL; + + (void) state; + + /* pattern and replacement are of the same size */ + replaced_string = ssh_strreplace(test_string1, "test", "kiwi"); + assert_string_equal(replaced_string, "this;is;a;kiwi"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "kiwi"); + assert_string_equal(replaced_string, "kiwi;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "kiwi"); + assert_string_equal(replaced_string, "this;kiwi;is;a"); + free(replaced_string); + + /* replacement is greater than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an;apple"); + assert_string_equal(replaced_string, "this;is;a;an;apple"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an;apple"); + assert_string_equal(replaced_string, "an;apple;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an;apple"); + assert_string_equal(replaced_string, "this;an;apple;is;a"); + free(replaced_string); + + /* replacement is less than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an"); + assert_string_equal(replaced_string, "this;is;a;an"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an"); + assert_string_equal(replaced_string, "an;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an"); + assert_string_equal(replaced_string, "this;an;is;a"); + free(replaced_string); + + /* pattern not found in teststring */ + replaced_string = ssh_strreplace(test_string1, "banana", "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* pattern is NULL */ + replaced_string = ssh_strreplace(test_string1, NULL , "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* replacement is NULL */ + replaced_string = ssh_strreplace(test_string1, "test", NULL); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* src is NULL */ + replaced_string = ssh_strreplace(NULL, "test", "kiwi"); + assert_null(replaced_string); +} + +static void torture_ssh_strerror(void **state) +{ + char buf[1024]; + size_t bufflen = sizeof(buf); + char *out = NULL; + + (void) state; + + out = ssh_strerror(ENOENT, buf, 1); /* too short */ + assert_string_equal(out, "\0"); + + out = ssh_strerror(256, buf, bufflen); /* unknown error code */ + /* This error is always different: + * Freebd: "Unknown error: 256" + * MinGW/Win: "Unknown error" + * Linux/glibc: "Unknown error 256" + * Alpine/musl: "No error information" + */ + assert_non_null(out); + + out = ssh_strerror(ENOMEM, buf, bufflen); + /* This actually differs too for glibc/musl: + * musl: "Out of memory" + * everything else: "Cannot allocate memory" + */ + assert_non_null(out); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -678,6 +782,8 @@ int torture_run_tests(void) { cmocka_unit_test(torture_ssh_newline_vis), cmocka_unit_test(torture_ssh_mkdirs), cmocka_unit_test(torture_ssh_quote_file_name), + cmocka_unit_test(torture_ssh_strreplace), + cmocka_unit_test(torture_ssh_strerror), }; ssh_init(); diff --git a/libssh/tests/unittests/torture_options.c b/libssh/tests/unittests/torture_options.c index d0fdaed..e1d16f0 100644 --- a/libssh/tests/unittests/torture_options.c +++ b/libssh/tests/unittests/torture_options.c @@ -6,6 +6,7 @@ #define _POSIX_PTHREAD_SEMANTICS # include #endif +#include #include "torture.h" #include "torture_key.h" @@ -65,6 +66,13 @@ static void torture_options_set_host(void **state) { assert_string_equal(session->opts.host, "meditation"); assert_non_null(session->opts.username); assert_string_equal(session->opts.username, "guru"); + + 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"); } static void torture_options_set_ciphers(void **state) { @@ -691,11 +699,11 @@ static void torture_options_config_match(void **state) session->opts.port = 0; - /* The Match exec keyword is ignored */ + /* The Match exec keyword */ torture_reset_config(session); config = fopen("test_config", "w"); assert_non_null(config); - fputs("Match exec /bin/true\n" + fputs("Match exec true\n" "\tPort 33\n" "Match all\n" "\tPort 34\n", @@ -704,15 +712,20 @@ static void torture_options_config_match(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif session->opts.port = 0; - /* The Match exec keyword can accept more arguments */ + /* Commands containing whitespace characters must be quoted. */ torture_reset_config(session); config = fopen("test_config", "w"); assert_non_null(config); - fputs("Match exec /bin/true 1 \n" + fputs("Match exec \"true 1\"\n" "\tPort 33\n" "Match all\n" "\tPort 34\n", @@ -721,15 +734,33 @@ static void torture_options_config_match(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif session->opts.port = 0; - /* Commands containing whitespace characters must be quoted. */ + unlink("test_config"); +} + +static void torture_options_config_match_multi(void **state) +{ + ssh_session session = *state; + FILE *config = NULL; + struct stat sb; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* Exec is not executed when it can not be matched */ torture_reset_config(session); config = fopen("test_config", "w"); assert_non_null(config); - fputs("Match exec \"/bin/true 1\"\n" + fputs("Match host wronghost exec \"touch test_config_wrong\"\n" "\tPort 33\n" "Match all\n" "\tPort 34\n", @@ -739,9 +770,45 @@ static void torture_options_config_match(void **state) rv = ssh_options_parse_config(session, "test_config"); assert_ssh_return_code(session, rv); assert_int_equal(session->opts.port, 34); + assert_int_equal(stat("test_config_wrong", &sb), -1); session->opts.port = 0; + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host testhost1\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifdef _WIN32 + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host otherhost\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + unlink("test_config"); } @@ -777,7 +844,7 @@ static void torture_options_copy(void **state) "MACs hmac-sha2-256\n" "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" "Compression yes\n" - "PubkeyAcceptedTypes ssh-ed25519,ecdsa-sha2-nistp521\n" + "PubkeyAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" "ProxyCommand nc 127.0.0.10 22\n" /* ops.custombanner */ "ConnectTimeout 42\n" @@ -853,6 +920,173 @@ static void torture_options_copy(void **state) ssh_free(new); } +#define EXECUTABLE_NAME "test-exec" +static void torture_options_getopt(void **state) +{ + ssh_session session = *state; + int rc; + int previous_level, new_level; + const char *argv[] = {EXECUTABLE_NAME, "-l", "username", "-p", "222", + "-vv", "-v", "-r", "-c", "aes128-ctr", + "-i", "id_rsa", "-C", "-2", "-1", NULL}; + int argc = sizeof(argv)/sizeof(char *) - 1; + const char *argv_invalid[] = {EXECUTABLE_NAME, "-r", "-d", NULL}; + + previous_level = ssh_get_log_level(); + + /* Test with all the supported options */ + rc = ssh_options_getopt(session, &argc, (char **)argv); +#ifdef _MSC_VER + /* Not supported in windows */ + assert_ssh_return_code_equal(session, rc, -1); +#else + assert_ssh_return_code(session, rc); + + /* Restore the log level to previous value first */ + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); /* 2 + 1 -v's */ + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); + + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.username, "username"); + assert_int_equal(session->opts.port, 222); + /* The -r (usersa) is noop */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr"); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr"); + assert_string_equal(session->opts.identity->root->data, "id_rsa"); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,zlib,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,zlib,none"); +#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 + /* -1 and -2 are noop */ + + + /* It should ignore unknown arguments */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 3); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + + + /* It should not mess with unknown arguments order */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "-X"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "-X"); + + + /* Trailing arguments should be passed as they are */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "example.com"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "example.com"); + + + /* Invalid configuration combination -d and -r (for some reason?) */ + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv_invalid); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + assert_int_equal(argc, 3); + assert_string_equal(argv_invalid[0], EXECUTABLE_NAME); + assert_string_equal(argv_invalid[1], "-r"); + assert_string_equal(argv_invalid[2], "-d"); + + + /* Corner case: only one argument */ + argv[1] = "-C"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + assert_ssh_return_code(session, rc); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none,zlib@openssh.com,zlib"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com,zlib"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,zlib,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,zlib,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 + + /* Corner case: only hostname is not parsed */ + argv[1] = "example.com"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 2); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "example.com"); + + /* Corner case: no arguments */ + argv[1] = NULL; + argc = 1; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); + +#endif /* _NSC_VER */ +} + #ifdef WITH_SERVER const char template[] = "temp_dir_XXXXXX"; @@ -1643,6 +1877,10 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_config_match, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match_multi, + setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt, + setup, teardown), }; #ifdef WITH_SERVER diff --git a/libssh/tests/unittests/torture_packet.c b/libssh/tests/unittests/torture_packet.c index 922c832..9f07f39 100644 --- a/libssh/tests/unittests/torture_packet.c +++ b/libssh/tests/unittests/torture_packet.c @@ -93,17 +93,21 @@ torture_packet(const char *cipher, const char *mac_type, crypto->decryptMAC = copy_data(mac, sizeof(mac)); in_cipher = session->current_crypto->in_cipher; - rc = in_cipher->set_decrypt_key(in_cipher, - session->current_crypto->decryptkey, - session->current_crypto->decryptIV); - assert_int_equal(rc, SSH_OK); + if (in_cipher->set_decrypt_key != NULL) { + rc = in_cipher->set_decrypt_key(in_cipher, + session->current_crypto->decryptkey, + session->current_crypto->decryptIV); + assert_int_equal(rc, SSH_OK); + } out_cipher = session->current_crypto->out_cipher; - rc = out_cipher->set_encrypt_key(out_cipher, - session->current_crypto->encryptkey, - session->current_crypto->encryptIV); + if (out_cipher->set_decrypt_key != NULL) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->current_crypto->encryptkey, + session->current_crypto->encryptIV); + assert_int_equal(rc, SSH_OK); + } session->current_crypto->used = SSH_DIRECTION_BOTH; - assert_int_equal(rc, SSH_OK); assert_non_null(session->out_buffer); ssh_buffer_add_data(session->out_buffer, test_data, payload_len); @@ -161,6 +165,35 @@ static void torture_packet_aes256_ctr_etm(UNUSED_PARAM(void **state)) } } +#ifdef WITH_INSECURE_NONE +static void torture_packet_none_sha1(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_ctr_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "none", "none", i); + } +} + +static void torture_packet_none_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "none", "none", i); + } +} +#endif /* WITH_INSECURE_NONE */ + static void torture_packet_aes128_ctr(void **state) { int i; @@ -260,6 +293,12 @@ static void torture_packet_chacha20(void **state) { int i; (void)state; /* unused */ + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + for (i=1;i<256;++i){ torture_packet("chacha20-poly1305@openssh.com", "none", "none", i); } @@ -323,6 +362,11 @@ int torture_run_tests(void) { cmocka_unit_test(torture_packet_aes256_gcm), cmocka_unit_test(torture_packet_compress_zlib), cmocka_unit_test(torture_packet_compress_zlib_openssh), +#ifdef WITH_INSECURE_NONE + cmocka_unit_test(torture_packet_none_sha1), + cmocka_unit_test(torture_packet_aes128_ctr_none), + cmocka_unit_test(torture_packet_none_none), +#endif }; ssh_init(); diff --git a/libssh/tests/unittests/torture_pki.c b/libssh/tests/unittests/torture_pki.c index d886245..008f29a 100644 --- a/libssh/tests/unittests/torture_pki.c +++ b/libssh/tests/unittests/torture_pki.c @@ -5,10 +5,10 @@ #include #include +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; @@ -164,11 +164,11 @@ struct key_attrs key_attrs_list[][5] = { {0, 1, "", 521, 0, "", 0}, /* ECDSA, SHA512 */ }, { - {1, 1, "ssh-ed25519", 0, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ - {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA1 */ - {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA256 */ - {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA384 */ - {1, 1, "ssh-ed25519", 0, 0, "", 0}, /* ED25519, SHA512 */ + {1, 1, "ssh-ed25519", 255, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA1 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA256 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA384 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA512 */ }, #ifdef HAVE_DSA { @@ -260,6 +260,7 @@ static void torture_pki_verify_mismatch(void **state) enum ssh_digest_e hash; size_t input_length = sizeof(INPUT); struct key_attrs skey_attrs, vkey_attrs; + int bits; (void) state; @@ -294,6 +295,8 @@ static void torture_pki_verify_mismatch(void **state) assert_non_null(key); assert_int_equal(key->type, sig_type); assert_string_equal(key->type_c, skey_attrs.type_c); + bits = ssh_key_size(key); + assert_int_equal(bits, skey_attrs.size_arg); SSH_LOG(SSH_LOG_TRACE, "Creating signature %d with hash %d", sig_type, hash); diff --git a/libssh/tests/unittests/torture_pki_dsa.c b/libssh/tests/unittests/torture_pki_dsa.c index b555e48..a98f1bb 100644 --- a/libssh/tests/unittests/torture_pki_dsa.c +++ b/libssh/tests/unittests/torture_pki_dsa.c @@ -6,10 +6,10 @@ #include #include +#include "pki.c" #include "torture.h" #include "torture_key.h" #include "torture_pki.h" -#include "pki.c" #define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" #define LIBSSH_DSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_dsa" diff --git a/libssh/tests/unittests/torture_pki_ecdsa.c b/libssh/tests/unittests/torture_pki_ecdsa.c index 22f06a4..7d09d98 100644 --- a/libssh/tests/unittests/torture_pki_ecdsa.c +++ b/libssh/tests/unittests/torture_pki_ecdsa.c @@ -5,10 +5,10 @@ #include #include +#include "pki.c" #include "torture.h" #include "torture_key.h" #include "torture_pki.h" -#include "pki.c" #define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_ecdsa" #define LIBSSH_ECDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ecdsa" diff --git a/libssh/tests/unittests/torture_pki_ecdsa_uri.c b/libssh/tests/unittests/torture_pki_ecdsa_uri.c new file mode 100644 index 0000000..038780d --- /dev/null +++ b/libssh/tests/unittests/torture_pki_ecdsa_uri.c @@ -0,0 +1,560 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_" +#define LIBSSH_ECDSA_TESTKEY_PEM "libssh_testkey_pem.id_" +#define SOFTHSM_CONF "softhsm.conf" +#define PUB_URI_FMT_256 "pkcs11:token=ecdsa256;object=ecdsa256;type=public" +#define PRIV_URI_FMT_256 "pkcs11:token=ecdsa256;object=ecdsa256;type=private?pin-value=1234" +#define PUB_URI_FMT_384 "pkcs11:token=ecdsa384;object=ecdsa384;type=public" +#define PRIV_URI_FMT_384 "pkcs11:token=ecdsa384;object=ecdsa384;type=private?pin-value=1234" +#define PUB_URI_FMT_521 "pkcs11:token=ecdsa521;object=ecdsa521;type=public" +#define PRIV_URI_FMT_521 "pkcs11:token=ecdsa521;object=ecdsa521;type=private?pin-value=1234" +#define PRIV_URI_FMT_256_NO_PUB "pkcs11:token=ecdsa256_no_pub_uri;object=ecdsa256_no_pub_uri;type=private?pin-value=1234" +#define PRIV_URI_FMT_384_NO_PUB "pkcs11:token=ecdsa384_no_pub_uri;object=ecdsa384_no_pub_uri;type=private?pin-value=1234" +#define PRIV_URI_FMT_521_NO_PUB "pkcs11:token=ecdsa521_no_pub_uri;object=ecdsa521_no_pub_uri;type=private?pin-value=1234" + +/** PKCS#11 URIs with invalid fields**/ + +#define PRIV_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#define PRIV_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#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 unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_tokens_ecdsa(void **state, int ecdsa_bits, const char *obj_tempname, const char *load_public) +{ + + struct pki_st *test_state = *state; + char priv_filename[1024]; + char pub_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + snprintf(priv_filename, sizeof(priv_filename), "%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname); + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname, ".pub"); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(priv_filename, + torture_get_testkey(test_state->type, 0)); + torture_write_file(pub_filename, + torture_get_testkey_pub_pem(test_state->type)); + torture_setup_tokens(cwd, priv_filename, obj_tempname, load_public); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir; + int rc; + char conf_path[1024] = {0}; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + 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); + SAFE_FREE(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", test_state->temp_dir); + setenv("SOFTHSM2_CONF", conf_path, 1); + + setup_tokens_ecdsa(state, 256, "ecdsa256", "1"); + setup_tokens_ecdsa(state, 384, "ecdsa384", "1"); + setup_tokens_ecdsa(state, 521, "ecdsa521", "1"); + setup_tokens_ecdsa(state, 256, "ecdsa256_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 384, "ecdsa384_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 521, "ecdsa521_no_pub_uri", "0"); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + unsetenv("SOFTHSM2_CONF"); + + 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); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_uri(void **state, const char *uri) +{ + ssh_key pubkey = NULL; + int rc; + + rc = ssh_pki_import_pubkey_file(uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_true(rc == 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_uri_256(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_256); +} + +static void torture_pki_ecdsa_import_pubkey_uri_384(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_384); +} + +static void torture_pki_ecdsa_import_pubkey_uri_521(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, PUB_URI_FMT_521); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, const char *uri, const char *type) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + char convert_key_to_pem[4096]; + char pub_filename[1024]; + char pub_filename_generated[1024]; + char pub_filename_pem[1024]; + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_return_code(rc, errno); + assert_true(rc == SSH_OK); + assert_non_null(pblob); + ssh_string_free(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s", LIBSSH_ECDSA_TESTKEY, type, ".pub"); + snprintf(pub_filename_generated, sizeof(pub_filename_generated), "%s%s%s", + LIBSSH_ECDSA_TESTKEY_PEM, type, "generated.pub"); + snprintf(pub_filename_pem, sizeof(pub_filename_pem), "%s%s%s", LIBSSH_ECDSA_TESTKEY_PEM, type, ".pub"); + + rc = torture_read_one_line(pub_filename, + pubkey_original, + sizeof(pubkey_original)); + assert_true(rc == 0); + + rc = ssh_pki_export_pubkey_file(pubkey, pub_filename_generated); + assert_return_code(rc, errno); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(pub_filename); + + snprintf(convert_key_to_pem, sizeof(convert_key_to_pem), "ssh-keygen -e -f %s -m PKCS8 > %s ", + pub_filename_generated, pub_filename_pem); + + system(convert_key_to_pem); + + rc = torture_read_one_line(pub_filename_pem, + pubkey_generated, + sizeof(pubkey_generated)); + assert_true(rc == 0); + + assert_int_equal(strncmp(pubkey_original, pubkey_generated, strlen(pubkey_original)), 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_256(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_256, "ecdsa256"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_384(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_384, "ecdsa384"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_521(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, PRIV_URI_FMT_521, "ecdsa521"); +} + +static void import_pubkey_without_loading_public_uri(void **state, const char *uri, const char *type) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_int_not_equal(rc, 0); + assert_null(pblob); + ssh_string_free(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256(void **state) +{ + import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_256_NO_PUB, "ecdsa256_no_pub_uri"); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384(void **state) +{ + import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_384_NO_PUB, "ecdsa384_no_pub_uri"); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521(void **state) +{ + import_pubkey_without_loading_public_uri(state, PRIV_URI_FMT_521_NO_PUB, "ecdsa521_no_pub_uri"); +} + +static void torture_ecdsa_sign_verify_uri(void **state, const char *uri, enum ssh_digest_e dig_type) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session=ssh_new(); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), dig_type); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + assert_true(rc == SSH_OK); + + type = ssh_key_type(privkey); + type_char = ssh_key_type_to_char(type); + etype_char = ssh_pki_key_ecdsa_name(privkey); + + switch (dig_type) { + case SSH_DIGEST_SHA256: + assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + break; + case SSH_DIGEST_SHA384: + assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + break; + case SSH_DIGEST_SHA512: + assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + break; + default: + printf("Invalid hash type: %d\n", dig_type); + } + + ssh_free(session); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_ecdsa_sign_verify_uri_256(void **state) +{ + torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_256, SSH_DIGEST_SHA256); +} + +static void torture_ecdsa_sign_verify_uri_384(void **state) +{ + torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_384, SSH_DIGEST_SHA384); +} + +static void torture_ecdsa_sign_verify_uri_521(void **state) +{ + torture_ecdsa_sign_verify_uri(state, PRIV_URI_FMT_521, SSH_DIGEST_SHA512); +} + +static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *priv_uri, const char *pub_uri) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(pub_uri, &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_true(rc == SSH_OK); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(rc == 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_true(rc == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +static void torture_pki_ecdsa_duplicate_key_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_256, PUB_URI_FMT_256); +} + +static void torture_pki_ecdsa_duplicate_key_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_384, PUB_URI_FMT_384); +} + +static void torture_pki_ecdsa_duplicate_key_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, PRIV_URI_FMT_521, PUB_URI_FMT_521); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char *priv_uri) +{ + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_256); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_384); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, PRIV_URI_FMT_521); +} + +static void torture_pki_ecdsa_import_pubkey_uri_invalid_configurations(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + + /** invalid token for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_384_INVALID_TOKEN, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + /** invalid object for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_521_INVALID_OBJECT, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + /** invalid token for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_384_INVALID_TOKEN, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + /** invalid object for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_521_INVALID_OBJECT, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_521), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_521), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_256), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_384), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_521), + + /** Expect fail on these negative test cases **/ + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_invalid_configurations), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521), + }; + ssh_session session = ssh_new(); + int verbosity = SSH_LOG_FUNCTIONS; + + ssh_init(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup_directory_structure, teardown_directory_structure); + + ssh_free(session); + ssh_finalize(); + + return rc; +} diff --git a/libssh/tests/unittests/torture_pki_ed25519.c b/libssh/tests/unittests/torture_pki_ed25519.c index ff59b19..2fe53bc 100644 --- a/libssh/tests/unittests/torture_pki_ed25519.c +++ b/libssh/tests/unittests/torture_pki_ed25519.c @@ -2,10 +2,10 @@ #define LIBSSH_STATIC +#include "pki.c" #include "torture.h" #include "torture_key.h" #include "torture_pki.h" -#include "pki.c" #include #include diff --git a/libssh/tests/unittests/torture_pki_rsa.c b/libssh/tests/unittests/torture_pki_rsa.c index a986706..7b9d83f 100644 --- a/libssh/tests/unittests/torture_pki_rsa.c +++ b/libssh/tests/unittests/torture_pki_rsa.c @@ -6,10 +6,10 @@ #include #include +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" #define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" #define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" @@ -541,9 +541,13 @@ static void torture_pki_rsa_generate_key(void **state) int rc; ssh_key key = NULL, pubkey = NULL; ssh_signature sign = NULL; - ssh_session session=ssh_new(); + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + (void) state; + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (!ssh_fips_mode()) { rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); assert_true(rc == SSH_OK); @@ -665,6 +669,44 @@ static void torture_pki_rsa_sha2(void **state) ssh_free(session); } +static void torture_pki_rsa_key_size(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + unsigned int length = 4096; + + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + + /* Set the minumum RSA key size to 4k */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &length); + assert_ssh_return_code(session, rc); + + /* the verification should fail now */ + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + static int test_sign_verify_data(ssh_key key, enum ssh_digest_e hash_type, const unsigned char *input, @@ -985,6 +1027,7 @@ int torture_run_tests(void) { setup_rsa_key, teardown), cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test(torture_pki_rsa_key_size), #if defined(HAVE_LIBCRYPTO) cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, setup_rsa_key, diff --git a/libssh/tests/unittests/torture_pki_rsa_uri.c b/libssh/tests/unittests/torture_pki_rsa_uri.c new file mode 100644 index 0000000..1d15db6 --- /dev/null +++ b/libssh/tests/unittests/torture_pki_rsa_uri.c @@ -0,0 +1,302 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" +#define SOFTHSM_CONF "softhsm.conf" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" + +const char template[] = "temp_dir_XXXXXX"; +const 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 conf_path[1024] = {0}; + 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%s", cwd, "/", LIBSSH_RSA_TESTKEY); + + snprintf(keys_path_pub, sizeof(keys_path_pub), "%s%s%s%s", cwd, "/", LIBSSH_RSA_TESTKEY, ".pub"); + + 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_RSA, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_RSA)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", cwd); + + setenv("SOFTHSM2_CONF", conf_path, 1); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir; + 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); + + 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; + + 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); + + unsetenv("SOFTHSM2_CONF"); + + return 0; +} + +static void torture_pki_rsa_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_true(rc == 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_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_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_true(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_int_equal(rc, SSH_OK); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_true(rc == SSH_OK); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_rsa_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_true(rc == 0); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_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); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_rsa_import_pubkey_uri), + cmocka_unit_test(torture_pki_rsa_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_rsa_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_rsa_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = SSH_LOG_FUNCTIONS; + 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_finalize(); + + return rc; +} diff --git a/libssh/tests/unittests/torture_threads_pki_rsa.c b/libssh/tests/unittests/torture_threads_pki_rsa.c index a04a746..5ec8055 100644 --- a/libssh/tests/unittests/torture_threads_pki_rsa.c +++ b/libssh/tests/unittests/torture_threads_pki_rsa.c @@ -26,10 +26,10 @@ #include #include +#include "pki.c" #include "torture.h" #include "torture_pki.h" #include "torture_key.h" -#include "pki.c" #include diff --git a/libssh/tests/unittests/torture_unit_server.c b/libssh/tests/unittests/torture_unit_server.c new file mode 100644 index 0000000..ca7515e --- /dev/null +++ b/libssh/tests/unittests/torture_unit_server.c @@ -0,0 +1,160 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include +#include + +#include +#include "torture.h" +#include "torture_key.h" + +#define TEST_SERVER_PORT 2222 + +struct test_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct test_state *ts = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + ts = malloc(sizeof(struct test_state)); + assert_non_null(ts); + + ts->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + ts->fd = mkstemp(ts->hostkey_path); + umask(mask); + assert_return_code(ts->fd, errno); + close(ts->fd); + + ts->key_type = SSH_KEYTYPE_ECDSA_P256; + ts->hostkey = torture_get_testkey(ts->key_type, 0); + + torture_write_file(ts->hostkey_path, ts->hostkey); + + *state = ts; + + return 0; +} + +static int teardown(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + + unlink(ts->hostkey); + free(ts->hostkey_path); + free(ts); + + ssh_finalize(); + + return 0; +} + +/* TODO the signals are handled by cmocka so they are not testable her :( */ +static void *int_thread(void *arg) +{ + usleep(1); + kill(getpid(), SIGUSR1); + return NULL; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, "localhost", + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static void test_ssh_accept_interrupt(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + int rc; + pthread_t client_pthread, interrupt_pthread; + ssh_bind sshbind; + ssh_session server; + + /* Create server */ + sshbind = torture_ssh_bind("localhost", + TEST_SERVER_PORT, + ts->key_type, + ts->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + /* Send interupt in 1 second */ + rc = pthread_create(&interrupt_pthread, NULL, int_thread, NULL); + assert_return_code(rc, errno); + + rc = pthread_join(interrupt_pthread, NULL); + assert_int_equal(rc, 0); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_ERROR); + assert_int_equal(ssh_get_error_code(sshbind), SSH_EINTR); + + /* Get client to connect now */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + /* Now, try again */ + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ssh_accept_interrupt, + setup, + teardown) + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +}