aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Jelen <jjelen@redhat.com>2018-11-16 12:49:08 +0100
committerAndreas Schneider <asn@cryptomilk.org>2019-01-09 10:31:49 +0100
commit347af845ab65b42410dc08f9fa0aa1d8862664b2 (patch)
tree6280f92d446188796fad84aeec86f5305d2098b1
parentbfff7db5ffc87f170122fca0ff75c1f478b92893 (diff)
downloadlibssh-347af845ab65b42410dc08f9fa0aa1d8862664b2.tar.gz
libssh-347af845ab65b42410dc08f9fa0aa1d8862664b2.tar.xz
libssh-347af845ab65b42410dc08f9fa0aa1d8862664b2.zip
tests: Verify that rekey limits are effective from the client side
Signed-off-by: Jakub Jelen <jjelen@redhat.com> Reviewed-by: Daiki Ueno <dueno@redhat.com>
-rw-r--r--tests/client/CMakeLists.txt1
-rw-r--r--tests/client/torture_rekey.c385
2 files changed, 386 insertions, 0 deletions
diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt
index 6916bfc6..6da5452c 100644
--- a/tests/client/CMakeLists.txt
+++ b/tests/client/CMakeLists.txt
@@ -8,6 +8,7 @@ set(LIBSSH_CLIENT_TESTS
torture_connect
torture_hostkey
torture_auth
+ torture_rekey
torture_forward
torture_knownhosts
torture_knownhosts_verify
diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c
new file mode 100644
index 00000000..dd792ef3
--- /dev/null
+++ b/tests/client/torture_rekey.c
@@ -0,0 +1,385 @@
+/*
+ * This file is part of the SSH Library
+ *
+ * Copyright (c) 2018 by Red Hat, Inc.
+ *
+ * Authors: Jakub Jelen <jjelen@redhat.com>
+ *
+ * The SSH Library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at your
+ * option) any later version.
+ *
+ * The SSH Library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the SSH Library; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#define LIBSSH_STATIC
+
+#include "torture.h"
+#include "libssh/sftp.h"
+#include "libssh/libssh.h"
+#include "libssh/priv.h"
+#include "libssh/session.h"
+#include "libssh/crypto.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+static int sshd_setup(void **state)
+{
+ torture_setup_sshd_server(state, false);
+
+ return 0;
+}
+
+static int sshd_teardown(void **state)
+{
+ torture_teardown_sshd_server(state);
+
+ return 0;
+}
+
+static int session_setup(void **state)
+{
+ struct torture_state *s = *state;
+ int verbosity = torture_libssh_verbosity();
+ struct passwd *pwd;
+ bool b = false;
+ int rc;
+
+ pwd = getpwnam("bob");
+ assert_non_null(pwd);
+
+ rc = setuid(pwd->pw_uid);
+ assert_return_code(rc, errno);
+
+ s->ssh.session = ssh_new();
+ assert_non_null(s->ssh.session);
+
+ ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
+ ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER);
+
+ /* Authenticate as alice with bob his pubkey */
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE);
+ assert_int_equal(rc, SSH_OK);
+
+ /* Make sure no other configuration options from system will get used */
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ /* Make sure we do not interfere with another ssh-agent */
+ unsetenv("SSH_AUTH_SOCK");
+ unsetenv("SSH_AGENT_PID");
+
+ return 0;
+}
+
+static int session_teardown(void **state)
+{
+ struct torture_state *s = *state;
+
+ ssh_free(s->ssh.session);
+
+ return 0;
+}
+
+/* Check that the default limits for rekeying are enforced.
+ * the limits are too high for testsuite to verify so
+ * we should be fine with checking the values in internal
+ * structures
+ */
+static void torture_rekey_default(void **state)
+{
+ struct torture_state *s = *state;
+ int rc;
+ struct ssh_crypto_struct *c = NULL;
+
+ /* Define preferred ciphers: (out) C->S has 8B block, (in) S->C has 16B block */
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S,
+ "chacha20-poly1305@openssh.com");
+ assert_ssh_return_code(s->ssh.session, rc);
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_S_C,
+ "aes128-cbc");
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ rc = ssh_connect(s->ssh.session);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ c = s->ssh.session->current_crypto;
+ /* The blocks limit is set correctly */
+ /* For S->C (in) we have 16B block => 2**(L/4) blocks */
+ assert_int_equal(c->in_cipher->max_blocks,
+ (uint64_t)1 << (2 * c->in_cipher->blocksize));
+ /* The C->S (out) we have 8B block => 1 GB limit */
+ assert_int_equal(c->out_cipher->max_blocks,
+ ((uint64_t)1 << 30) / c->out_cipher->blocksize);
+
+ ssh_disconnect(s->ssh.session);
+}
+
+/* We lower the rekey limits manually and check that the rekey
+ * really happens when sending data
+ */
+static void torture_rekey_send(void **state)
+{
+ struct torture_state *s = *state;
+ int rc;
+ char data[256];
+ unsigned int i;
+ long long bytes = 2048; /* 2KB (more than the authentication phase) */
+ struct ssh_crypto_struct *c = NULL;
+ unsigned char *secret_hash = NULL;
+
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ rc = ssh_connect(s->ssh.session);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ /* The blocks limit is set correctly */
+ c = s->ssh.session->current_crypto;
+ assert_int_equal(c->in_cipher->max_blocks,
+ bytes / c->in_cipher->blocksize);
+ assert_int_equal(c->out_cipher->max_blocks,
+ bytes / c->out_cipher->blocksize);
+ /* We should have less encrypted packets than transfered (first are not encrypted) */
+ assert_true(c->out_cipher->packets < s->ssh.session->send_seq);
+ assert_true(c->in_cipher->packets < s->ssh.session->recv_seq);
+ /* Copy the initial secret hash = session_id so we know we changed keys later */
+ secret_hash = malloc(c->digest_len);
+ assert_non_null(secret_hash);
+ memcpy(secret_hash, c->secret_hash, c->digest_len);
+
+ /* OpenSSH can not rekey before authentication so authenticate here */
+ rc = ssh_userauth_none(s->ssh.session, NULL);
+ /* This request should return a SSH_REQUEST_DENIED error */
+ if (rc == SSH_ERROR) {
+ assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED);
+ }
+ rc = ssh_userauth_list(s->ssh.session, NULL);
+ assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY);
+
+ rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL);
+ assert_int_equal(rc, SSH_AUTH_SUCCESS);
+
+ /* send ignore packets of up to 1KB to trigger rekey */
+ memset(data, 0, sizeof(data));
+ memset(data, 'A', 128);
+ for (i = 0; i < 16; i++) {
+ ssh_send_ignore(s->ssh.session, data);
+ ssh_handle_packets(s->ssh.session, 50);
+ }
+
+ /* The rekey limit was restored in the new crypto to the same value */
+ c = s->ssh.session->current_crypto;
+ assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize);
+ assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize);
+ /* Check that the secret hash is different than initially */
+ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len);
+ free(secret_hash);
+
+ ssh_disconnect(s->ssh.session);
+}
+
+#ifdef WITH_SFTP
+#define MAX_XFER_BUF_SIZE 16384
+
+/* To trigger rekey by receiving data, the easiest thing is probably to
+ * use sftp
+ */
+static void torture_rekey_recv(void **state)
+{
+ struct torture_state *s = *state;
+ int rc;
+ long long bytes = 2048; /* 2KB */
+ struct ssh_crypto_struct *c = NULL;
+ unsigned char *secret_hash = NULL;
+
+ char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX";
+ char buf[MAX_XFER_BUF_SIZE];
+ ssize_t bytesread;
+ ssize_t byteswritten;
+ int fd;
+ sftp_file file;
+ mode_t mask;
+
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ rc = ssh_connect(s->ssh.session);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ /* The blocks limit is set correctly */
+ c = s->ssh.session->current_crypto;
+ assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize);
+ assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize);
+ /* We should have less encrypted packets than transfered (first are not encrypted) */
+ assert_true(c->out_cipher->packets < s->ssh.session->send_seq);
+ assert_true(c->in_cipher->packets < s->ssh.session->recv_seq);
+ /* Copy the initial secret hash = session_id so we know we changed keys later */
+ secret_hash = malloc(c->digest_len);
+ assert_non_null(secret_hash);
+ memcpy(secret_hash, c->secret_hash, c->digest_len);
+
+ /* OpenSSH can not rekey before authentication so authenticate here */
+ rc = ssh_userauth_none(s->ssh.session, NULL);
+ /* This request should return a SSH_REQUEST_DENIED error */
+ if (rc == SSH_ERROR) {
+ assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED);
+ }
+ rc = ssh_userauth_list(s->ssh.session, NULL);
+ assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY);
+
+ rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL);
+ assert_int_equal(rc, SSH_AUTH_SUCCESS);
+
+ /* Initialize SFTP session */
+ s->ssh.tsftp = torture_sftp_session(s->ssh.session);
+ assert_non_null(s->ssh.tsftp);
+
+ /* Download a file */
+ file = sftp_open(s->ssh.tsftp->sftp, "/usr/bin/ssh", O_RDONLY, 0);
+ assert_non_null(file);
+
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(libssh_tmp_file);
+ umask(mask);
+ unlink(libssh_tmp_file);
+
+ for (;;) {
+ bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE);
+ if (bytesread == 0) {
+ break; /* EOF */
+ }
+ assert_false(bytesread < 0);
+
+ byteswritten = write(fd, buf, bytesread);
+ assert_int_equal(byteswritten, bytesread);
+ }
+
+ close(fd);
+
+ /* The rekey limit was restored in the new crypto to the same value */
+ c = s->ssh.session->current_crypto;
+ assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize);
+ assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize);
+ /* Check that the secret hash is different than initially */
+ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len);
+ free(secret_hash);
+
+ torture_sftp_close(s->ssh.tsftp);
+ ssh_disconnect(s->ssh.session);
+}
+#endif /* WITH_SFTP */
+
+/* Rekey time requires rekey after specified time and is off by default.
+ * Setting the time to small enough value and waiting, we should trigger
+ * rekey on the first sent packet afterward.
+ */
+static void torture_rekey_time(void **state)
+{
+ struct torture_state *s = *state;
+ int rc;
+ char data[256];
+ unsigned int i;
+ long long time = 3; /* 3 seconds */
+ struct ssh_crypto_struct *c = NULL;
+ unsigned char *secret_hash = NULL;
+
+ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_TIME, &time);
+ assert_ssh_return_code(s->ssh.session, rc);
+ /* The time is internally stored in microseconds */
+ assert_int_equal(time * 1000, s->ssh.session->opts.rekey_time);
+
+ rc = ssh_connect(s->ssh.session);
+ assert_ssh_return_code(s->ssh.session, rc);
+
+ /* Copy the initial secret hash = session_id so we know we changed keys later */
+ c = s->ssh.session->current_crypto;
+ secret_hash = malloc(c->digest_len);
+ assert_non_null(secret_hash);
+ memcpy(secret_hash, c->secret_hash, c->digest_len);
+
+ /* OpenSSH can not rekey before authentication so authenticate here */
+ rc = ssh_userauth_none(s->ssh.session, NULL);
+ /* This request should return a SSH_REQUEST_DENIED error */
+ if (rc == SSH_ERROR) {
+ assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED);
+ }
+ rc = ssh_userauth_list(s->ssh.session, NULL);
+ assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY);
+
+ rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL);
+ assert_int_equal(rc, SSH_AUTH_SUCCESS);
+
+ /* Send some data. This should not trigger rekey yet */
+ memset(data, 0, sizeof(data));
+ memset(data, 'A', 8);
+ for (i = 0; i < 3; i++) {
+ ssh_send_ignore(s->ssh.session, data);
+ ssh_handle_packets(s->ssh.session, 50);
+ }
+
+ /* Check that the secret hash is the same */
+ c = s->ssh.session->current_crypto;
+ assert_memory_equal(secret_hash, c->secret_hash, c->digest_len);
+
+ /* Wait some more time */
+ sleep(3);
+
+ /* send some more data to trigger rekey and handle the
+ * key exchange "in background" */
+ for (i = 0; i < 8; i++) {
+ ssh_send_ignore(s->ssh.session, data);
+ ssh_handle_packets(s->ssh.session, 50);
+ }
+
+ /* Check that the secret hash is different than initially */
+ c = s->ssh.session->current_crypto;
+ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len);
+ free(secret_hash);
+
+ ssh_disconnect(s->ssh.session);
+}
+
+int torture_run_tests(void) {
+ int rc;
+ struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(torture_rekey_default,
+ session_setup,
+ session_teardown),
+ cmocka_unit_test_setup_teardown(torture_rekey_time,
+ session_setup,
+ session_teardown),
+#ifdef WITH_SFTP
+ cmocka_unit_test_setup_teardown(torture_rekey_recv,
+ session_setup,
+ session_teardown),
+#endif /* WITH_SFTP */
+ cmocka_unit_test_setup_teardown(torture_rekey_send,
+ session_setup,
+ session_teardown),
+ };
+
+ ssh_init();
+
+ torture_filter_tests(tests);
+ rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown);
+
+ ssh_finalize();
+
+ return rc;
+}