[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] tests: introduce pkd_hello (resend: rebased)


Hi,

I've rebased the pkd_hello test on top of the latest master and thought I'd
resend out.  I'd love to get these tests running somehow on test.libssh.org,
maybe with a valgrind pass, too, to help squash bugs like
https://red.libssh.org/issues/175.

I'm happy to rework any of this as needed, any feedback is very welcome.


Thanks,
-Jon
From 1b66b79cc02ffd263791338b43b341e9d779c54b Mon Sep 17 00:00:00 2001
From: Jon Simons <jon@xxxxxxxxxxxxx>
Date: Fri, 10 Oct 2014 12:38:45 -0700
Subject: [PATCH] tests: introduce pkd_hello

Introduce a sample public-key testing daemon to the 'pkd' test directory,
and add support code for cycling through various combinations of different
key exchange, cipher, and MAC algorithms.

The goal of the 'pkd_hello' test is to make it easy to test interactions
between non-libssh clients and a libssh-server, and to provide a starting
point for testing new implementations for key types, ciphers, MACs, and
so on.  The thinking is that testing a new algorithm should be as simple
as adding a new line for it in the PKDTESTS_* lists.

Macros are used to generate the tests and helper functions for a couple of
clients -- here, OpenSSH and dropbear are included for the first cut.  If
binaries are found for these clients, their test lists will be enabled;
when binaries are not found for a given client, those tests are skipped.

Tests are run in one large batch by default, but can also be run individually
to help with tracking down things like signature bugs that may take many
iterations to reproduce.

Each test logs its stdout and stderr to its own file, which is cleaned up
when a test succeeds.  For failures, those logs can be combined with verbose
libssh output from pkd itself to start debugging things.

Some example usages:

  pkd_hello
    Run all tests with default number of iterations.

  pkd_hello --list
    List available individual test names.

  pkd_hello -i 1000 -t torture_pkd_openssh_ecdsa_256_ecdh_sha2_nistp256
    Run only the torture_pkd_openssh_ecdsa_256_ecdh_sha2_nistp256
    testcase 1000 times.

  pkd_hello -v -v -v -v -e -o
    Run all tests with maximum libssh and pkd logging.

Included in the tests are passes for all existing kex, cipher, and MAC
algorithms.

BUG: https://red.libssh.org/issues/144

Signed-off-by: Jon Simons <jon@xxxxxxxxxxxxx>
---
 tests/CMakeLists.txt     |   2 +
 tests/pkd/CMakeLists.txt |  35 ++++
 tests/pkd/pkd_client.h   |  69 ++++++
 tests/pkd/pkd_daemon.c   | 497 +++++++++++++++++++++++++++++++++++++++++++
 tests/pkd/pkd_daemon.h   |  40 ++++
 tests/pkd/pkd_hello.c    | 534 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/pkd/pkd_keyutil.c  | 138 ++++++++++++
 tests/pkd/pkd_keyutil.h  |  40 ++++
 tests/pkd/pkd_util.c     |  45 ++++
 tests/pkd/pkd_util.h     |  16 ++
 10 files changed, 1416 insertions(+)
 create mode 100644 tests/pkd/CMakeLists.txt
 create mode 100644 tests/pkd/pkd_client.h
 create mode 100644 tests/pkd/pkd_daemon.c
 create mode 100644 tests/pkd/pkd_daemon.h
 create mode 100644 tests/pkd/pkd_hello.c
 create mode 100644 tests/pkd/pkd_keyutil.c
 create mode 100644 tests/pkd/pkd_keyutil.h
 create mode 100644 tests/pkd/pkd_util.c
 create mode 100644 tests/pkd/pkd_util.h

diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e1a8166..cba1d30 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -38,6 +38,7 @@ set(TEST_TARGET_LIBRARIES
 )
 
 add_subdirectory(unittests)
+
 if (WITH_CLIENT_TESTING)
     add_subdirectory(client)
 endif (WITH_CLIENT_TESTING)
@@ -46,3 +47,4 @@ if (WITH_BENCHMARKS)
     add_subdirectory(benchmarks)
 endif (WITH_BENCHMARKS)
 
+add_subdirectory(pkd)
diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt
new file mode 100644
index 0000000..d438959
--- /dev/null
+++ b/tests/pkd/CMakeLists.txt
@@ -0,0 +1,35 @@
+project(pkd C)
+
+if (UNIX AND NOT WIN32)
+
+include_directories(
+  ${LIBSSH_PUBLIC_INCLUDE_DIRS}
+  ${CMOCKA_INCLUDE_DIR}
+  ${OPENSSL_INCLUDE_DIRS}
+  ${GCRYPT_INCLUDE_DIRS}
+  ${ZLIB_INCLUDE_DIRS}
+  ${CMAKE_BINARY_DIR}
+  ${CMAKE_SOURCE_DIR}/src
+  ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+set(pkd_hello_src
+  pkd_daemon.c
+  pkd_hello.c
+  pkd_keyutil.c
+  pkd_util.c
+)
+
+set(pkd_libs
+    ${CMOCKA_LIBRARY}
+    ${LIBSSH_STATIC_LIBRARY}
+    ${LIBSSH_LINK_LIBRARIES}
+    ${LIBSSH_THREADS_STATIC_LIBRARY}
+    ${LIBSSH_THREADS_LINK_LIBRARIES}
+    ${ARGP_LIBRARIES}
+)
+
+add_executable(pkd_hello ${pkd_hello_src})
+target_link_libraries(pkd_hello ${pkd_libs})
+
+endif (UNIX AND NOT WIN32)
diff --git a/tests/pkd/pkd_client.h b/tests/pkd/pkd_client.h
new file mode 100644
index 0000000..c4a8a60
--- /dev/null
+++ b/tests/pkd/pkd_client.h
@@ -0,0 +1,69 @@
+/*
+ * pkd_client.h -- macros for generating client-specific command
+ *                 invocations for use with pkd testing
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#ifndef __PKD_CLIENT_H__
+#define __PKD_CLIENT_H__
+
+/* OpenSSH */
+
+#define OPENSSH_BINARY "ssh"
+#define OPENSSH_KEYGEN "ssh-keygen"
+
+#define OPENSSH_CMD_START \
+    OPENSSH_BINARY " "                 \
+    "-o UserKnownHostsFile=/dev/null " \
+    "-o StrictHostKeyChecking=no "     \
+    "-i " CLIENT_ID_FILE " "           \
+    "1> %s.out "                       \
+    "2> %s.err "                       \
+    "-vvv "
+
+#define OPENSSH_CMD_END "-p 1234 localhost ls"
+
+#define OPENSSH_CMD \
+    OPENSSH_CMD_START OPENSSH_CMD_END
+
+#define OPENSSH_KEX_CMD(kexalgo) \
+    OPENSSH_CMD_START "-o KexAlgorithms=" kexalgo " " OPENSSH_CMD_END
+
+#define OPENSSH_CIPHER_CMD(ciphers) \
+    OPENSSH_CMD_START "-c " ciphers " " OPENSSH_CMD_END
+
+#define OPENSSH_MAC_CMD(macs) \
+    OPENSSH_CMD_START "-o MACs=" macs " " OPENSSH_CMD_END
+
+
+/* Dropbear */
+
+#define DROPBEAR_BINARY "dbclient"
+#define DROPBEAR_KEYGEN "dropbearkey"
+
+#define DROPBEAR_CMD_START \
+    DROPBEAR_BINARY " "      \
+    "-y -y "                 \
+    "-i " CLIENT_ID_FILE " " \
+    "-v "                    \
+    "1> %s.out "             \
+    "2> %s.err "
+
+#define DROPBEAR_CMD_END "-p 1234 localhost ls"
+
+#define DROPBEAR_CMD \
+    DROPBEAR_CMD_START DROPBEAR_CMD_END
+
+#if 0 /* dbclient does not expose control over kex algo */
+#define DROPBEAR_KEX_CMD(kexalgo) \
+    DROPBEAR_CMD
+#endif
+
+#define DROPBEAR_CIPHER_CMD(ciphers) \
+    DROPBEAR_CMD_START "-c " ciphers " " DROPBEAR_CMD_END
+
+#define DROPBEAR_MAC_CMD(macs) \
+    DROPBEAR_CMD_START "-m " macs " " DROPBEAR_CMD_END
+
+#endif /* __PKD_CLIENT_H__ */
diff --git a/tests/pkd/pkd_daemon.c b/tests/pkd/pkd_daemon.c
new file mode 100644
index 0000000..de4e536
--- /dev/null
+++ b/tests/pkd/pkd_daemon.c
@@ -0,0 +1,497 @@
+/*
+ * pkd_daemon.c -- a sample public-key testing daemon using libssh
+ *
+ * Uses public key authentication to establish an exec channel and
+ * echo back payloads to the user.
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libssh/callbacks.h>
+#include <libssh/libssh.h>
+#include <libssh/server.h>
+
+#include "pkd_daemon.h"
+
+#include <setjmp.h> // for cmocka
+#include <cmocka.h>
+
+static int pkdout_enabled;
+static int pkderr_enabled;
+
+static void pkdout(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
+static void pkderr(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
+
+static void pkdout(const char *fmt, ...) {
+    va_list vargs;
+    if (pkdout_enabled) {
+        va_start(vargs, fmt);
+        vfprintf(stdout, fmt, vargs);
+        va_end(vargs);
+    }
+}
+
+static void pkderr(const char *fmt, ...) {
+    va_list vargs;
+    if (pkderr_enabled) {
+        va_start(vargs, fmt);
+        vfprintf(stderr, fmt, vargs);
+        va_end(vargs);
+    }
+}
+
+/*
+ * pkd state: only one thread can run pkd at a time ---------------------
+ */
+
+static struct {
+    int rc;
+    pthread_t tid;
+    int keep_going;
+    int pkd_ready;
+} ctx;
+
+static struct {
+    int server_fd;
+    int req_exec_received;
+    int close_received;
+    int eof_received;
+} pkd_state;
+
+static void pkd_sighandler(int signum) {
+    (void) signum;
+}
+
+static int pkd_init_libssh() {
+    int rc = ssh_threads_set_callbacks(ssh_threads_get_pthread());
+    return (rc == SSH_OK) ? 0 : 1;
+}
+
+static int pkd_init_server_fd(short port) {
+    int rc = 0;
+    int yes = 1;
+    struct sockaddr_in addr;
+
+    int server_fd = socket(PF_INET, SOCK_STREAM, 0);
+    if (server_fd < 0) {
+        rc = -1;
+        goto out;
+    }
+
+    rc = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
+    if (rc != 0) {
+        goto outclose;
+    }
+
+    memset(&addr, 0x0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    addr.sin_addr.s_addr = INADDR_ANY;
+    rc = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
+    if (rc != 0) {
+        goto outclose;
+    }
+
+    rc = listen(server_fd, 128);
+    if (rc == 0) {
+        goto out;
+    }
+
+outclose:
+    close(server_fd);
+    server_fd = -1;
+out:
+    pkd_state.server_fd = server_fd;
+    return rc;
+}
+
+static int pkd_accept_fd() {
+    int fd = -1;
+    struct sockaddr_in addr;
+    socklen_t len = sizeof(addr);
+
+    do {
+        fd = accept(pkd_state.server_fd, (struct sockaddr *) &addr, &len);
+    } while ((ctx.keep_going != 0) && (fd < 0) && (errno == EINTR));
+
+    return fd;
+}
+
+static void pkd_eof(ssh_session session,
+                    ssh_channel channel,
+                    void *userdata) {
+    (void) session;
+    (void) channel;
+    (void) userdata;
+    pkdout("pkd_eof\n");
+    pkd_state.eof_received = 1;
+}
+
+static void pkd_chan_close(ssh_session session,
+                           ssh_channel channel,
+                           void *userdata) {
+    (void) session;
+    (void) channel;
+    (void) userdata;
+    pkdout("pkd_chan_close\n");
+    pkd_state.close_received = 1;
+}
+
+static int pkd_req_exec(ssh_session s,
+                        ssh_channel c,
+                        const char *cmd,
+                        void *userdata) {
+    (void) s;
+    (void) c;
+    (void) cmd;
+    (void) userdata;
+    /* assumes pubkey authentication has already succeeded */
+    pkdout("pkd_req_exec\n");
+    pkd_state.req_exec_received = 1;
+    return 0;
+}
+
+/* assumes there is only ever a single channel */
+static struct ssh_channel_callbacks_struct pkd_channel_cb = {
+    .channel_eof_function = pkd_eof,
+    .channel_close_function = pkd_chan_close,
+    .channel_exec_request_function = pkd_req_exec,
+};
+
+static int pkd_auth_pubkey_cb(ssh_session s,
+                              const char *user,
+                              ssh_key key,
+                              char state,
+                              void *userdata) {
+    (void) s;
+    (void) user;
+    (void) key;
+    (void) state;
+    (void) userdata;
+    pkdout("pkd_auth_pubkey_cb keytype %s, state: %d\n",
+           ssh_key_type_to_char(ssh_key_type(key)), state);
+    if ((state == SSH_PUBLICKEY_STATE_NONE) ||
+        (state == SSH_PUBLICKEY_STATE_VALID)) {
+        return SSH_AUTH_SUCCESS;
+    }
+    return SSH_AUTH_DENIED;
+}
+
+static int pkd_service_request_cb(ssh_session session,
+                                  const char *service,
+                                  void *userdata) {
+    (void) session;
+    (void) userdata;
+    pkdout("pkd_service_request_cb: %s\n", service);
+    return (0 == (strcmp(service, "ssh-userauth"))) ? 0 : -1;
+}
+
+static ssh_channel pkd_channel_openreq_cb(ssh_session s,
+                                          void *userdata) {
+    ssh_channel c = NULL;
+    ssh_channel *out = (ssh_channel *) userdata;
+
+    /* assumes pubkey authentication has already succeeded */
+    pkdout("pkd_channel_openreq_cb\n");
+
+    c = ssh_channel_new(s);
+    if (c == NULL) {
+        pkderr("ssh_channel_new: %s\n", ssh_get_error(s));
+        return NULL;
+    }
+
+    ssh_callbacks_init(&pkd_channel_cb);
+    pkd_channel_cb.userdata = userdata;
+    if (ssh_set_channel_callbacks(c, &pkd_channel_cb) != SSH_OK) {
+        pkderr("ssh_set_channel_callbacks: %s\n", ssh_get_error(s));
+        ssh_channel_free(c);
+        c = NULL;
+    }
+
+    *out = c;
+
+    return c;
+}
+
+static struct ssh_server_callbacks_struct pkd_server_cb = {
+    .auth_pubkey_function = pkd_auth_pubkey_cb,
+    .service_request_function = pkd_service_request_cb,
+    .channel_open_request_session_function = pkd_channel_openreq_cb,
+};
+
+static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) {
+    int rc = -1;
+    ssh_bind b = NULL;
+    ssh_session s = NULL;
+    ssh_event e = NULL;
+    ssh_channel c = NULL;
+    enum ssh_bind_options_e opts = -1;
+
+    int level = args->opts.libssh_log_level;
+    enum pkd_hostkey_type_e type = args->type;
+    const char *hostkeypath = args->hostkeypath;
+
+    pkd_state.eof_received = 0;
+    pkd_state.close_received  = 0;
+    pkd_state.req_exec_received = 0;
+
+    b = ssh_bind_new();
+    if (b == NULL) {
+        pkderr("ssh_bind_new\n");
+        goto outclose;
+    }
+
+    if (type == PKD_RSA) {
+        opts = SSH_BIND_OPTIONS_RSAKEY;
+    } else if (type == PKD_DSA) {
+        opts = SSH_BIND_OPTIONS_DSAKEY;
+    } else if (type == PKD_ECDSA) {
+        opts = SSH_BIND_OPTIONS_ECDSAKEY;
+    } else {
+        pkderr("unknown kex algorithm: %d\n", type);
+        rc = -1;
+        goto outclose;
+    }
+
+    rc = ssh_bind_options_set(b, opts, hostkeypath);
+    if (rc != 0) {
+        pkderr("ssh_bind_options_set: %s\n", ssh_get_error(b));
+        goto outclose;
+    }
+
+    rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_LOG_VERBOSITY, &level);
+    if (rc != 0) {
+        pkderr("ssh_bind_options_set log verbosity: %s\n", ssh_get_error(b));
+        goto outclose;
+    }
+
+    s = ssh_new();
+    if (s == NULL) {
+        pkderr("ssh_new\n");
+        goto outclose;
+    }
+
+    /*
+     * ssh_bind_accept loads host key as side-effect.  If this
+     * succeeds, the given 'fd' will be closed upon 'ssh_free(s)'.
+     */
+    rc = ssh_bind_accept_fd(b, s, fd);
+    if (rc != SSH_OK) {
+        pkderr("ssh_bind_accept_fd: %s\n", ssh_get_error(b));
+        goto outclose;
+    }
+
+    /* accept only publickey-based auth */
+    ssh_set_auth_methods(s, SSH_AUTH_METHOD_PUBLICKEY);
+
+    /* initialize callbacks */
+    ssh_callbacks_init(&pkd_server_cb);
+    pkd_server_cb.userdata = &c;
+    rc = ssh_set_server_callbacks(s, &pkd_server_cb);
+    if (rc != SSH_OK) {
+        pkderr("ssh_set_server_callbacks: %s\n", ssh_get_error(s));
+        goto out;
+    }
+
+    /* first do key exchange */
+    rc = ssh_handle_key_exchange(s);
+    if (rc != SSH_OK) {
+        pkderr("ssh_handle_key_exchange: %s\n", ssh_get_error(s));
+        goto out;
+    }
+
+    /* setup and pump event to carry out exec channel */
+    e = ssh_event_new();
+    if (e == NULL) {
+        pkderr("ssh_event_new\n");
+        goto out;
+    }
+
+    rc = ssh_event_add_session(e, s);
+    if (rc != SSH_OK) {
+        pkderr("ssh_event_add_session\n");
+        goto out;
+    }
+
+    /* poll until exec channel established */
+    while ((ctx.keep_going != 0) &&
+           (rc != SSH_ERROR) && (pkd_state.req_exec_received == 0)) {
+        rc = ssh_event_dopoll(e, -1 /* infinite timeout */);
+    }
+
+    if (rc == SSH_ERROR) {
+        pkderr("ssh_event_dopoll\n");
+        goto out;
+    } else if (c == NULL) {
+        pkderr("poll loop exited but exec channel not ready\n");
+        rc = -1;
+        goto out;
+    }
+
+    rc = ssh_channel_write(c, "hello\n", 6); /* XXX: customizable payloads */
+    if (rc != 6) {
+        pkderr("ssh_channel_write partial (%d)\n", rc);
+    }
+
+    rc = ssh_channel_request_send_exit_status(c, 0);
+    if (rc != SSH_OK) {
+        pkderr("ssh_channel_request_send_exit_status: %s\n",
+                        ssh_get_error(s));
+        goto out;
+    }
+
+    rc = ssh_channel_send_eof(c);
+    if (rc != SSH_OK) {
+        pkderr("ssh_channel_send_eof: %s\n", ssh_get_error(s));
+        goto out;
+    }
+
+    rc = ssh_channel_close(c);
+    if (rc != SSH_OK) {
+        pkderr("ssh_channel_close: %s\n", ssh_get_error(s));
+        goto out;
+    }
+
+    while ((ctx.keep_going != 0) &&
+           (pkd_state.eof_received == 0) &&
+           (pkd_state.close_received == 0) &&
+           (ssh_channel_is_closed(c) == 0)) {
+        rc = ssh_event_dopoll(e, 1000 /* milliseconds */);
+        if (rc == SSH_ERROR) {
+            pkderr("ssh_event_dopoll for eof + close: %s\n", ssh_get_error(s));
+            break;
+        } else {
+            rc = 0;
+        }
+    }
+    goto out;
+
+outclose:
+    close(fd);
+out:
+    if (c != NULL) {
+        ssh_channel_free(c);
+    }
+    if (e != NULL) {
+        ssh_event_remove_session(e, s);
+        ssh_event_free(e);
+    }
+    if (s != NULL) {
+        ssh_disconnect(s);
+        ssh_free(s);
+    }
+    if (b != NULL) {
+        ssh_bind_free(b);
+    }
+    return rc;
+}
+
+/*
+ * main loop ------------------------------------------------------------
+ */
+
+static void *pkd_main(void *args) {
+    int rc = -1;
+    struct pkd_daemon_args *a = (struct pkd_daemon_args *) args;
+
+    struct sigaction act = { .sa_handler = pkd_sighandler, };
+
+    pkd_state.server_fd = -1;
+    pkd_state.req_exec_received = 0;
+    pkd_state.close_received = 0;
+    pkd_state.eof_received = 0;
+
+    /* SIGUSR1 is used to interrupt 'pkd_accept_fd'. */
+    rc = sigaction(SIGUSR1, &act, NULL);
+    if (rc != 0) {
+        pkderr("sigaction: %d\n", rc);
+        goto out;
+    }
+
+    rc = pkd_init_libssh();
+    if (rc != 0) {
+        pkderr("pkd_init_libssh: %d\n", rc);
+        goto out;
+    }
+
+    rc = pkd_init_server_fd(1234);
+    if (rc != 0) {
+        pkderr("pkd_init_server_fd: %d\n", rc);
+        goto out;
+    }
+
+    ctx.pkd_ready = 1;
+
+    while (ctx.keep_going != 0) {
+        int fd = pkd_accept_fd();
+        if (fd < 0) {
+            if (ctx.keep_going != 0) {
+                pkderr("pkd_accept_fd");
+                rc = -1;
+            } else {
+                rc = 0;
+            }
+            break;
+        }
+
+        rc = pkd_exec_hello(fd, a);
+        if (rc != 0) {
+            pkderr("pkd_exec_hello: %d\n", rc);
+            break;
+        }
+    }
+
+    close(pkd_state.server_fd);
+    pkd_state.server_fd = -1;
+out:
+    ctx.rc = rc;
+
+    return NULL;
+}
+
+/*
+ * pkd start and stop used by setup/teardown test scaffolding -----------
+ */
+
+int pkd_start(struct pkd_daemon_args *args) {
+    int rc = 0;
+
+    pkdout_enabled = args->opts.log_stdout;
+    pkderr_enabled = args->opts.log_stderr;
+
+    /* Initialize the pkd context. */
+    ctx.rc = -1;
+    ctx.keep_going = 1;
+    ctx.pkd_ready = 0;
+    rc = pthread_create(&ctx.tid, NULL, &pkd_main, args);
+    assert_int_equal(rc, 0);
+
+    /* Busy-spin until pkd thread is ready. */
+    while (ctx.pkd_ready == 0);
+
+    return rc;
+}
+
+void pkd_stop(struct pkd_result *out) {
+    int rc = 0;
+
+    ctx.keep_going = 0;
+
+    rc = pthread_kill(ctx.tid, SIGUSR1);
+    assert_int_equal(rc, 0);
+
+    rc = pthread_join(ctx.tid, NULL);
+    assert_int_equal(rc, 0);
+
+    assert_non_null(out);
+    out->ok = (ctx.rc == 0);
+
+    return;
+}
diff --git a/tests/pkd/pkd_daemon.h b/tests/pkd/pkd_daemon.h
new file mode 100644
index 0000000..c42573c
--- /dev/null
+++ b/tests/pkd/pkd_daemon.h
@@ -0,0 +1,40 @@
+/*
+ * pkd_daemon.h -- tests use this interface to start, stop pkd
+ *                 instances and get results
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#ifndef __PKD_DAEMON_H__
+#define __PKD_DAEMON_H__
+
+enum pkd_hostkey_type_e {
+    PKD_RSA,
+    PKD_DSA,
+    PKD_ECDSA
+};
+
+struct pkd_daemon_args {
+    enum pkd_hostkey_type_e type;
+    const char *hostkeypath;
+
+    struct {
+        int list;
+
+        int log_stdout;
+        int log_stderr;
+        int libssh_log_level;
+
+        const char *testname;
+        unsigned int iterations;
+    } opts;
+};
+
+struct pkd_result {
+    int ok;
+};
+
+int pkd_start(struct pkd_daemon_args *args);
+void pkd_stop(struct pkd_result *out);
+
+#endif /* __PKD_DAEMON_H__ */
diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c
new file mode 100644
index 0000000..2183f89
--- /dev/null
+++ b/tests/pkd/pkd_hello.c
@@ -0,0 +1,534 @@
+/*
+ * pkd_hello.c --
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#include <setjmp.h> // for cmocka
+#include <stdarg.h> // for cmocka
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h> // for cmocka
+#include <cmocka.h>
+
+#include "libssh/priv.h"
+
+#include "pkd_client.h"
+#include "pkd_daemon.h"
+#include "pkd_keyutil.h"
+#include "pkd_util.h"
+
+#define DEFAULT_ITERATIONS 10
+static struct pkd_daemon_args pkd_dargs;
+
+#ifdef HAVE_ARGP_H
+#include <argp.h>
+#define PROGNAME "pkd_hello"
+#define ARGP_PROGNAME "libssh " PROGNAME
+const char *argp_program_version = ARGP_PROGNAME " 2014-04-12";
+const char *argp_program_bug_address = "Jon Simons <jon@xxxxxxxxxxxxx>";
+//static char **cmdline;
+static char doc[] = \
+    "\nExample usage:\n\n"
+    "    " PROGNAME "\n"
+    "        Run all tests with default number of iterations.\n"
+    "    " PROGNAME " --list\n"
+    "        List available individual test names.\n"
+    "    " PROGNAME " -i 1000 -t torture_pkd_rsa_ecdh_sha2_nistp256\n"
+    "        Run only the torture_pkd_rsa_ecdh_sha2_nistp256 testcase 1000 times.\n"
+    "    " PROGNAME " -v -v -v -v -e -o\n"
+    "        Run all tests with maximum libssh and pkd logging.\n"
+;
+
+static struct argp_option options[] = {
+    { "stderr", 'e', NULL, 0,
+      "Emit pkd stderr messages", 0 },
+    { "list", 'l', NULL, 0,
+      "List available individual test names", 0 },
+    { "iterations", 'i', "number", 0,
+      "Run each test for the given number of iterations (default is 10)", 0 },
+    { "stdout", 'o', NULL, 0,
+      "Emit pkd stdout messages", 0 },
+    { "test", 't', "testname", 0,
+      "Run tests matching the given testname", 0 },
+    { "verbose", 'v', NULL, 0,
+      "Increase libssh verbosity (can be used multiple times)", 0 },
+    { NULL, 0, NULL, 0,
+      NULL, 0 },
+};
+
+static error_t parse_opt(int key, char *arg, struct argp_state *state) {
+    (void) arg;
+    (void) state;
+
+    switch(key) {
+    case 'e':
+        pkd_dargs.opts.log_stderr = 1;
+        break;
+    case 'l':
+        pkd_dargs.opts.list = 1;
+        break;
+    case 'i':
+        pkd_dargs.opts.iterations = atoi(arg);
+        break;
+    case 'o':
+        pkd_dargs.opts.log_stdout = 1;
+        break;
+    case 't':
+        pkd_dargs.opts.testname = arg;
+        break;
+    case 'v':
+        pkd_dargs.opts.libssh_log_level += 1;
+        break;
+    default:
+        return ARGP_ERR_UNKNOWN;
+    }
+
+    return 0;
+}
+
+static struct argp parser = {
+    options,
+    parse_opt,
+    NULL,
+    doc,
+    NULL,
+    NULL,
+    NULL
+};
+#endif /* HAVE_ARGP_H */
+
+static struct pkd_state *torture_pkd_setup(enum pkd_hostkey_type_e type,
+                                           const char *hostkeypath) {
+    int rc = 0;
+
+    pkd_dargs.type = type;
+    pkd_dargs.hostkeypath = hostkeypath;
+
+    rc = pkd_start(&pkd_dargs);
+    assert_int_equal(rc, 0);
+
+    return NULL;
+}
+
+static void torture_pkd_teardown(void **state) {
+    struct pkd_result result = { .ok = 0 };
+
+    (void) state;
+
+    pkd_stop(&result);
+    assert_int_equal(result.ok, 1);
+}
+
+/*
+ * one setup for each server keytype ------------------------------------
+ */
+
+static void torture_pkd_setup_noop(void **state) {
+    *state = (void *) torture_pkd_setup(PKD_RSA, NULL /*path*/);
+}
+
+static void torture_pkd_setup_rsa(void **state) {
+    setup_rsa_key();
+    *state = (void *) torture_pkd_setup(PKD_RSA, LIBSSH_RSA_TESTKEY);
+}
+
+static void torture_pkd_setup_dsa(void **state) {
+    setup_dsa_key();
+    *state = (void *) torture_pkd_setup(PKD_DSA, LIBSSH_DSA_TESTKEY);
+}
+
+static void torture_pkd_setup_ecdsa_256(void **state) {
+    setup_ecdsa_keys();
+    *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_256_TESTKEY);
+}
+
+static void torture_pkd_setup_ecdsa_384(void **state) {
+    setup_ecdsa_keys();
+    *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_384_TESTKEY);
+}
+
+static void torture_pkd_setup_ecdsa_521(void **state) {
+    setup_ecdsa_keys();
+    *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_521_TESTKEY);
+}
+
+/*
+ * Test matrices: f(clientname, testname, ssh-command, setup-function, teardown-function).
+ */
+
+#define PKDTESTS_DEFAULT(f, client, cmd) \
+    /* Default passes by server key type. */ \
+    f(client, rsa_default,        cmd,  setup_rsa,        teardown) \
+    f(client, dsa_default,        cmd,  setup_dsa,        teardown) \
+    f(client, ecdsa_256_default,  cmd,  setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_default,  cmd,  setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_default,  cmd,  setup_ecdsa_521,  teardown)
+
+#define PKDTESTS_KEX(f, client, kexcmd) \
+    /* Kex algorithms. */ \
+    f(client, rsa_curve25519_sha256,                  kexcmd("curve25519-sha256@xxxxxxxxxx"),  setup_rsa,        teardown) \
+    f(client, rsa_ecdh_sha2_nistp256,                 kexcmd("ecdh-sha2-nistp256 "),           setup_rsa,        teardown) \
+    f(client, rsa_diffie_hellman_group14_sha1,        kexcmd("diffie-hellman-group14-sha1"),   setup_rsa,        teardown) \
+    f(client, rsa_diffie_hellman_group1_sha1,         kexcmd("diffie-hellman-group1-sha1"),    setup_rsa,        teardown) \
+    f(client, dsa_curve25519_sha256,                  kexcmd("curve25519-sha256@xxxxxxxxxx"),  setup_dsa,        teardown) \
+    f(client, dsa_ecdh_sha2_nistp256,                 kexcmd("ecdh-sha2-nistp256 "),           setup_dsa,        teardown) \
+    f(client, dsa_diffie_hellman_group14_sha1,        kexcmd("diffie-hellman-group14-sha1"),   setup_dsa,        teardown) \
+    f(client, dsa_diffie_hellman_group1_sha1,         kexcmd("diffie-hellman-group1-sha1"),    setup_dsa,        teardown) \
+    f(client, ecdsa_256_curve25519_sha256,            kexcmd("curve25519-sha256@xxxxxxxxxx"),  setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_ecdh_sha2_nistp256,           kexcmd("ecdh-sha2-nistp256 "),           setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_diffie_hellman_group14_sha1,  kexcmd("diffie-hellman-group14-sha1"),   setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_diffie_hellman_group1_sha1,   kexcmd("diffie-hellman-group1-sha1"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_curve25519_sha256,            kexcmd("curve25519-sha256@xxxxxxxxxx"),  setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_ecdh_sha2_nistp256,           kexcmd("ecdh-sha2-nistp256 "),           setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_diffie_hellman_group14_sha1,  kexcmd("diffie-hellman-group14-sha1"),   setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_diffie_hellman_group1_sha1,   kexcmd("diffie-hellman-group1-sha1"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_curve25519_sha256,            kexcmd("curve25519-sha256@xxxxxxxxxx"),  setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_ecdh_sha2_nistp256,           kexcmd("ecdh-sha2-nistp256 "),           setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_diffie_hellman_group14_sha1,  kexcmd("diffie-hellman-group14-sha1"),   setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_diffie_hellman_group1_sha1,   kexcmd("diffie-hellman-group1-sha1"),    setup_ecdsa_521,  teardown)
+
+#define PKDTESTS_CIPHER(f, client, ciphercmd) \
+    /* Ciphers. */ \
+    f(client, rsa_3des_cbc,            ciphercmd("3des-cbc"),      setup_rsa,        teardown) \
+    f(client, rsa_aes128_cbc,          ciphercmd("aes128-cbc"),    setup_rsa,        teardown) \
+    f(client, rsa_aes128_ctr,          ciphercmd("aes128-ctr"),    setup_rsa,        teardown) \
+    f(client, rsa_aes256_cbc,          ciphercmd("aes256-cbc"),    setup_rsa,        teardown) \
+    f(client, rsa_aes256_ctr,          ciphercmd("aes256-ctr"),    setup_rsa,        teardown) \
+    f(client, rsa_blowfish_cbc,        ciphercmd("blowfish-cbc"),  setup_rsa,        teardown) \
+    f(client, dsa_3des_cbc,            ciphercmd("3des-cbc"),      setup_dsa,        teardown) \
+    f(client, dsa_aes128_cbc,          ciphercmd("aes128-cbc"),    setup_dsa,        teardown) \
+    f(client, dsa_aes128_ctr,          ciphercmd("aes128-ctr"),    setup_dsa,        teardown) \
+    f(client, dsa_aes256_cbc,          ciphercmd("aes256-cbc"),    setup_dsa,        teardown) \
+    f(client, dsa_aes256_ctr,          ciphercmd("aes256-ctr"),    setup_dsa,        teardown) \
+    f(client, dsa_blowfish_cbc,        ciphercmd("blowfish-cbc"),  setup_dsa,        teardown) \
+    f(client, ecdsa_256_3des_cbc,      ciphercmd("3des-cbc"),      setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_aes128_cbc,    ciphercmd("aes128-cbc"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_aes128_ctr,    ciphercmd("aes128-ctr"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_aes256_cbc,    ciphercmd("aes256-cbc"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_aes256_ctr,    ciphercmd("aes256-ctr"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_blowfish_cbc,  ciphercmd("blowfish-cbc"),  setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_3des_cbc,      ciphercmd("3des-cbc"),      setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_aes128_cbc,    ciphercmd("aes128-cbc"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_aes128_ctr,    ciphercmd("aes128-ctr"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_aes256_cbc,    ciphercmd("aes256-cbc"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_aes256_ctr,    ciphercmd("aes256-ctr"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_blowfish_cbc,  ciphercmd("blowfish-cbc"),  setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_3des_cbc,      ciphercmd("3des-cbc"),      setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_aes128_cbc,    ciphercmd("aes128-cbc"),    setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_aes128_ctr,    ciphercmd("aes128-ctr"),    setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_aes256_cbc,    ciphercmd("aes256-cbc"),    setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_aes256_ctr,    ciphercmd("aes256-ctr"),    setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_blowfish_cbc,  ciphercmd("blowfish-cbc"),  setup_ecdsa_521,  teardown)
+
+#define PKDTESTS_CIPHER_AES192(f, client, ciphercmd) \
+    /* Ciphers. */ \
+    f(client, rsa_aes192_cbc,          ciphercmd("aes192-cbc"),    setup_rsa,        teardown) \
+    f(client, rsa_aes192_ctr,          ciphercmd("aes192-ctr"),    setup_rsa,        teardown) \
+    f(client, dsa_aes192_cbc,          ciphercmd("aes192-cbc"),    setup_dsa,        teardown) \
+    f(client, dsa_aes192_ctr,          ciphercmd("aes192-ctr"),    setup_dsa,        teardown) \
+    f(client, ecdsa_256_aes192_cbc,    ciphercmd("aes192-cbc"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_256_aes192_ctr,    ciphercmd("aes192-ctr"),    setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_aes192_cbc,    ciphercmd("aes192-cbc"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_384_aes192_ctr,    ciphercmd("aes192-ctr"),    setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_aes192_cbc,    ciphercmd("aes192-cbc"),    setup_ecdsa_521,  teardown) \
+    f(client, ecdsa_521_aes192_ctr,    ciphercmd("aes192-ctr"),    setup_ecdsa_521,  teardown)
+
+#define PKDTESTS_MAC(f, client, maccmd) \
+    /* MACs. */ \
+    f(client, rsa_hmac_sha1,            maccmd("hmac-sha1"),      setup_rsa,        teardown) \
+    f(client, dsa_hmac_sha1,            maccmd("hmac-sha1"),      setup_dsa,        teardown) \
+    f(client, ecdsa_256_hmac_sha1,      maccmd("hmac-sha1"),      setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_hmac_sha1,      maccmd("hmac-sha1"),      setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_hmac_sha1,      maccmd("hmac-sha1"),      setup_ecdsa_521,  teardown) \
+    f(client, rsa_hmac_sha2_256,        maccmd("hmac-sha2-256"),  setup_rsa,        teardown) \
+    f(client, dsa_hmac_sha2_256,        maccmd("hmac-sha2-256"),  setup_dsa,        teardown) \
+    f(client, ecdsa_256_hmac_sha2_256,  maccmd("hmac-sha2-256"),  setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_hmac_sha2_256,  maccmd("hmac-sha2-256"),  setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_hmac_sha2_256,  maccmd("hmac-sha2-256"),  setup_ecdsa_521,  teardown) \
+    f(client, rsa_hmac_sha2_512,        maccmd("hmac-sha2-512"),  setup_rsa,        teardown) \
+    f(client, dsa_hmac_sha2_512,        maccmd("hmac-sha2-512"),  setup_dsa,        teardown) \
+    f(client, ecdsa_256_hmac_sha2_512,  maccmd("hmac-sha2-512"),  setup_ecdsa_256,  teardown) \
+    f(client, ecdsa_384_hmac_sha2_512,  maccmd("hmac-sha2-512"),  setup_ecdsa_384,  teardown) \
+    f(client, ecdsa_521_hmac_sha2_512,  maccmd("hmac-sha2-512"),  setup_ecdsa_521,  teardown)
+
+static void torture_pkd_client_noop(void **state) {
+    struct pkd_state *pstate = (struct pkd_state *) (*state);
+    (void) pstate;
+    return;
+}
+
+static void torture_pkd_runtest(const char *testname,
+                                const char *testcmd)
+{
+    int i, rc;
+    char logfile[1024] = { 0 };
+    int iterations =
+        (pkd_dargs.opts.iterations != 0) ? pkd_dargs.opts.iterations
+                                         : DEFAULT_ITERATIONS;
+
+    for (i = 0; i < iterations; i++) {
+        rc = system_checked(testcmd);
+        assert_int_equal(rc, 0);
+    }
+
+    /* Asserts did not trip: cleanup logs. */
+    snprintf(&logfile[0], sizeof(logfile), "%s.out", testname);
+    unlink(logfile);
+    snprintf(&logfile[0], sizeof(logfile), "%s.err", testname);
+    unlink(logfile);
+}
+
+/*
+ * Though each keytest function body is the same, separate functions are
+ * defined here to result in distinct output when running the tests.
+ */
+
+#define emit_keytest(client, testname, sshcmd, setup, teardown) \
+    static void torture_pkd_## client ## _ ## testname(void **state) { \
+        const char *tname = "torture_pkd_" #client "_" #testname;      \
+        char testcmd[1024] = { 0 };                                    \
+        (void) state;                                                  \
+        snprintf(&testcmd[0], sizeof(testcmd), sshcmd, tname, tname);  \
+        torture_pkd_runtest(tname, testcmd);                           \
+    }
+
+/*
+ * Actual test functions are emitted here.
+ */
+
+#define CLIENT_ID_FILE OPENSSH_DSA_TESTKEY
+PKDTESTS_DEFAULT(emit_keytest, openssh_dsa, OPENSSH_CMD)
+PKDTESTS_KEX(emit_keytest, openssh_dsa, OPENSSH_KEX_CMD)
+PKDTESTS_CIPHER(emit_keytest, openssh_dsa, OPENSSH_CIPHER_CMD)
+PKDTESTS_CIPHER_AES192(emit_keytest, openssh_dsa, OPENSSH_CIPHER_CMD)
+PKDTESTS_MAC(emit_keytest, openssh_dsa, OPENSSH_MAC_CMD)
+#undef CLIENT_ID_FILE
+
+#define CLIENT_ID_FILE OPENSSH_RSA_TESTKEY
+PKDTESTS_DEFAULT(emit_keytest, openssh_rsa, OPENSSH_CMD)
+PKDTESTS_KEX(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD)
+PKDTESTS_CIPHER(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD)
+PKDTESTS_CIPHER_AES192(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD)
+PKDTESTS_MAC(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD)
+#undef CLIENT_ID_FILE
+
+#define CLIENT_ID_FILE OPENSSH_ECDSA256_TESTKEY
+PKDTESTS_DEFAULT(emit_keytest, openssh_e256, OPENSSH_CMD)
+PKDTESTS_KEX(emit_keytest, openssh_e256, OPENSSH_KEX_CMD)
+PKDTESTS_CIPHER(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD)
+PKDTESTS_CIPHER_AES192(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD)
+PKDTESTS_MAC(emit_keytest, openssh_e256, OPENSSH_MAC_CMD)
+#undef CLIENT_ID_FILE
+
+/* Could add these passes, too: */
+//#define CLIENT_ID_FILE OPENSSH_ECDSA384_TESTKEY
+//#define CLIENT_ID_FILE OPENSSH_ECDSA521_TESTKEY
+
+#define CLIENT_ID_FILE OPENSSH_ED25519_TESTKEY
+PKDTESTS_DEFAULT(emit_keytest, openssh_ed, OPENSSH_CMD)
+PKDTESTS_KEX(emit_keytest, openssh_ed, OPENSSH_KEX_CMD)
+PKDTESTS_CIPHER(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD)
+PKDTESTS_CIPHER_AES192(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD)
+PKDTESTS_MAC(emit_keytest, openssh_ed, OPENSSH_MAC_CMD)
+#undef CLIENT_ID_FILE
+
+#define CLIENT_ID_FILE DROPBEAR_RSA_TESTKEY
+PKDTESTS_DEFAULT(emit_keytest, dropbear, DROPBEAR_CMD)
+PKDTESTS_CIPHER(emit_keytest, dropbear, DROPBEAR_CIPHER_CMD)
+PKDTESTS_MAC(emit_keytest, dropbear, DROPBEAR_MAC_CMD)
+#undef CLIENT_ID_FILE
+
+/*
+ * Define an array of testname strings mapped to their associated
+ * test function.  Enables running tests individually by name from
+ * the command line.
+ */
+
+#define emit_testmap(client, testname, sshcmd, setup, teardown) \
+    { "torture_pkd_" #client "_" #testname,                     \
+      { emit_unit_test(client, testname, sshcmd, setup, teardown) } },
+
+#define emit_unit_test(client, testname, sshcmd, setup, teardown) \
+    unit_test_setup_teardown(torture_pkd_ ## client ## _ ## testname, \
+                             torture_pkd_ ## setup, \
+                             torture_pkd_ ## teardown)
+
+#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown) \
+    emit_unit_test(client, testname, sshcmd, setup, teardown),
+
+struct {
+    const char *testname;
+    const UnitTest test[3]; /* requires setup + test + teardown */
+} testmap[] = {
+    /* OpenSSH */
+    PKDTESTS_DEFAULT(emit_testmap, openssh_dsa, OPENSSH_CMD)
+    PKDTESTS_KEX(emit_testmap, openssh_dsa, OPENSSH_KEX_CMD)
+    PKDTESTS_CIPHER(emit_testmap, openssh_dsa, OPENSSH_CIPHER_CMD)
+    PKDTESTS_CIPHER_AES192(emit_testmap, openssh_dsa, OPENSSH_CIPHER_CMD)
+    PKDTESTS_MAC(emit_testmap, openssh_dsa, OPENSSH_MAC_CMD)
+
+    PKDTESTS_DEFAULT(emit_testmap, openssh_rsa, OPENSSH_CMD)
+    PKDTESTS_KEX(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD)
+    PKDTESTS_CIPHER(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD)
+    PKDTESTS_CIPHER_AES192(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD)
+    PKDTESTS_MAC(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD)
+
+    PKDTESTS_DEFAULT(emit_testmap, openssh_e256, OPENSSH_CMD)
+    PKDTESTS_KEX(emit_testmap, openssh_e256, OPENSSH_KEX_CMD)
+    PKDTESTS_CIPHER(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD)
+    PKDTESTS_CIPHER_AES192(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD)
+    PKDTESTS_MAC(emit_testmap, openssh_e256, OPENSSH_MAC_CMD)
+
+    PKDTESTS_DEFAULT(emit_testmap, openssh_ed, OPENSSH_CMD)
+    PKDTESTS_KEX(emit_testmap, openssh_ed, OPENSSH_KEX_CMD)
+    PKDTESTS_CIPHER(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD)
+    PKDTESTS_CIPHER_AES192(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD)
+    PKDTESTS_MAC(emit_testmap, openssh_ed, OPENSSH_MAC_CMD)
+
+    /* Dropbear */
+    PKDTESTS_DEFAULT(emit_testmap, dropbear, DROPBEAR_CMD)
+    PKDTESTS_CIPHER(emit_testmap, dropbear, DROPBEAR_CIPHER_CMD)
+    PKDTESTS_MAC(emit_testmap, dropbear, DROPBEAR_MAC_CMD)
+
+    /* Noop */
+    emit_testmap(client, noop, "", setup_noop, teardown)
+
+    /* NULL tail entry */
+    { NULL, { { NULL, NULL, 0 }, { NULL, NULL, 0 }, { NULL, NULL, 0 } } }
+};
+
+static int pkd_run_tests(void) {
+    int rc = -1;
+    int tindex = 0;
+
+    const UnitTest openssh_tests[] = {
+        PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_dsa, OPENSSH_CMD)
+        PKDTESTS_KEX(emit_unit_test_comma, openssh_dsa, OPENSSH_KEX_CMD)
+        PKDTESTS_CIPHER(emit_unit_test_comma, openssh_dsa, OPENSSH_CIPHER_CMD)
+        PKDTESTS_CIPHER_AES192(emit_unit_test_comma, openssh_dsa, OPENSSH_CIPHER_CMD)
+        PKDTESTS_MAC(emit_unit_test_comma, openssh_dsa, OPENSSH_MAC_CMD)
+
+        PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD)
+        PKDTESTS_KEX(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD)
+        PKDTESTS_CIPHER(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD)
+        PKDTESTS_CIPHER_AES192(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD)
+        PKDTESTS_MAC(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD)
+
+        PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_e256, OPENSSH_CMD)
+        PKDTESTS_KEX(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD)
+        PKDTESTS_CIPHER(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD)
+        PKDTESTS_CIPHER_AES192(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD)
+        PKDTESTS_MAC(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD)
+
+        PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed, OPENSSH_CMD)
+        PKDTESTS_KEX(emit_unit_test_comma, openssh_ed, OPENSSH_KEX_CMD)
+        PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD)
+        PKDTESTS_CIPHER_AES192(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD)
+        PKDTESTS_MAC(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD)
+    };
+
+    const UnitTest dropbear_tests[] = {
+        PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear, DROPBEAR_CMD)
+        PKDTESTS_CIPHER(emit_unit_test_comma, dropbear, DROPBEAR_CIPHER_CMD)
+        PKDTESTS_MAC(emit_unit_test_comma, dropbear, DROPBEAR_MAC_CMD)
+    };
+
+    const UnitTest noop_tests[] = {
+        emit_unit_test(client, noop, "", setup_noop, teardown)
+    };
+
+    /* Test list is populated depending on which clients are enabled. */
+    UnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) +
+                       (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) +
+                       (sizeof(noop_tests) / sizeof(noop_tests[0]))];
+    memset(&all_tests[0], 0x0, sizeof(all_tests));
+
+    /* Generate client keys and populate test list for each enabled client. */
+    if (is_openssh_client_enabled()) {
+        setup_openssh_client_keys();
+        memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests));
+        tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0]));
+    }
+
+    if (is_dropbear_client_enabled()) {
+        setup_dropbear_client_rsa_key();
+        memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests));
+        tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0]));
+    }
+
+    memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests));
+    tindex += (sizeof(noop_tests) / sizeof(noop_tests[0]));
+
+    if (pkd_dargs.opts.testname == NULL) {
+        rc = _run_tests(all_tests, tindex);
+    } else {
+        int i = 0;
+        const UnitTest *found = NULL;
+        const char *testname = pkd_dargs.opts.testname;
+
+        while (testmap[i].testname != NULL) {
+            if (strcmp(testmap[i].testname, testname) == 0) {
+                found = &testmap[i].test[0];
+                break;
+            }
+            i += 1;
+        }
+
+        if (found != NULL) {
+            rc = _run_tests(found, 3);
+        } else {
+            fprintf(stderr, "Did not find test '%s'\n", testname);
+        }
+    }
+
+    /* Clean up client keys for each enabled client. */
+    if (is_dropbear_client_enabled()) {
+        cleanup_dropbear_client_rsa_key();
+    }
+
+    if (is_openssh_client_enabled()) {
+        cleanup_openssh_client_keys();
+    }
+
+    /* Clean up any server keys that were generated. */
+    cleanup_rsa_key();
+    cleanup_dsa_key();
+    cleanup_ecdsa_keys();
+
+    return rc;
+}
+
+int main(int argc, char **argv) {
+    int i = 0;
+    int rc = 0;
+
+    unsetenv("SSH_AUTH_SOCK");
+
+    rc = ssh_init();
+    if (rc != 0) {
+        rc = SSH_ERROR;
+        goto out;
+    }
+
+#ifdef HAVE_ARGP_H
+    argp_parse(&parser, argc, argv, 0, 0, NULL);
+#else /* HAVE_ARGP_H */
+    (void) argc;  (void) argv;
+#endif /* HAVE_ARGP_H */
+
+    if (pkd_dargs.opts.list != 0) {
+        while (testmap[i].testname != NULL) {
+            printf("%s\n", testmap[i++].testname);
+        }
+    } else {
+        rc = pkd_run_tests();
+    }
+
+    rc = ssh_finalize();
+    if (rc != 0) {
+        fprintf(stderr, "ssh_finalize: %d\n", rc);
+    }
+out:
+    return rc;
+}
diff --git a/tests/pkd/pkd_keyutil.c b/tests/pkd/pkd_keyutil.c
new file mode 100644
index 0000000..e1e1ecb
--- /dev/null
+++ b/tests/pkd/pkd_keyutil.c
@@ -0,0 +1,138 @@
+/*
+ * pkd_keyutil.c -- pkd test key utilities
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#include <setjmp.h> // for cmocka
+#include <stdarg.h> // for cmocka
+#include <unistd.h> // for cmocka
+#include <cmocka.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pkd_client.h"
+#include "pkd_keyutil.h"
+#include "pkd_util.h"
+
+void setup_rsa_key() {
+    int rc = 0;
+    if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f "
+                            LIBSSH_RSA_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+}
+
+void setup_dsa_key() {
+    int rc = 0;
+    if (access(LIBSSH_DSA_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t dsa -q -N \"\" -f "
+                            LIBSSH_DSA_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+}
+
+void setup_ecdsa_keys() {
+    int rc = 0;
+
+    if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f "
+                            LIBSSH_ECDSA_256_TESTKEY);
+        assert_int_equal(rc, 0);
+    }
+    if (access(LIBSSH_ECDSA_384_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f "
+                            LIBSSH_ECDSA_384_TESTKEY);
+        assert_int_equal(rc, 0);
+    }
+    if (access(LIBSSH_ECDSA_521_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f "
+                            LIBSSH_ECDSA_521_TESTKEY);
+        assert_int_equal(rc, 0);
+    }
+}
+
+static void cleanup_key(const char *privkey, const char *pubkey) {
+    unlink(privkey);
+    unlink(pubkey);
+}
+
+void cleanup_rsa_key() {
+    cleanup_key(LIBSSH_RSA_TESTKEY, LIBSSH_RSA_TESTKEY ".pub");
+}
+
+void cleanup_dsa_key() {
+    cleanup_key(LIBSSH_DSA_TESTKEY, LIBSSH_DSA_TESTKEY ".pub");
+}
+
+void cleanup_ecdsa_keys() {
+    cleanup_key(LIBSSH_ECDSA_256_TESTKEY, LIBSSH_ECDSA_256_TESTKEY ".pub");
+    cleanup_key(LIBSSH_ECDSA_384_TESTKEY, LIBSSH_ECDSA_384_TESTKEY ".pub");
+    cleanup_key(LIBSSH_ECDSA_521_TESTKEY, LIBSSH_ECDSA_521_TESTKEY ".pub");
+}
+
+void setup_openssh_client_keys() {
+    int rc = 0;
+
+    if (access(OPENSSH_DSA_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t dsa -q -N \"\" -f "
+                            OPENSSH_DSA_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+
+    if (access(OPENSSH_RSA_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f "
+                            OPENSSH_RSA_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+
+    if (access(OPENSSH_ECDSA256_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f "
+                            OPENSSH_ECDSA256_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+
+    if (access(OPENSSH_ECDSA384_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f "
+                            OPENSSH_ECDSA384_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+
+    if (access(OPENSSH_ECDSA521_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f "
+                            OPENSSH_ECDSA521_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+
+    if (access(OPENSSH_ED25519_TESTKEY, F_OK) != 0) {
+        rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f "
+                            OPENSSH_ED25519_TESTKEY);
+    }
+    assert_int_equal(rc, 0);
+}
+
+void cleanup_openssh_client_keys() {
+    cleanup_key(OPENSSH_DSA_TESTKEY, OPENSSH_DSA_TESTKEY ".pub");
+    cleanup_key(OPENSSH_RSA_TESTKEY, OPENSSH_RSA_TESTKEY ".pub");
+    cleanup_key(OPENSSH_ECDSA256_TESTKEY, OPENSSH_ECDSA256_TESTKEY ".pub");
+    cleanup_key(OPENSSH_ECDSA384_TESTKEY, OPENSSH_ECDSA384_TESTKEY ".pub");
+    cleanup_key(OPENSSH_ECDSA521_TESTKEY, OPENSSH_ECDSA521_TESTKEY ".pub");
+    cleanup_key(OPENSSH_ED25519_TESTKEY,  OPENSSH_ED25519_TESTKEY  ".pub");
+}
+
+void setup_dropbear_client_rsa_key() {
+    int rc = 0;
+    if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) {
+        rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f "
+                            DROPBEAR_RSA_TESTKEY " 1>/dev/null 2>/dev/null");
+    }
+    assert_int_equal(rc, 0);
+}
+
+void cleanup_dropbear_client_rsa_key() {
+    unlink(DROPBEAR_RSA_TESTKEY);
+}
diff --git a/tests/pkd/pkd_keyutil.h b/tests/pkd/pkd_keyutil.h
new file mode 100644
index 0000000..8e9de00
--- /dev/null
+++ b/tests/pkd/pkd_keyutil.h
@@ -0,0 +1,40 @@
+/*
+ * pkd_keyutil.h --
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#ifndef __PKD_KEYUTIL_H__
+#define __PKD_KEYUTIL_H__
+
+/* Server keys. */
+#define LIBSSH_DSA_TESTKEY        "libssh_testkey.id_dsa"
+#define LIBSSH_RSA_TESTKEY        "libssh_testkey.id_rsa"
+#define LIBSSH_ECDSA_256_TESTKEY  "libssh_testkey.id_ecdsa256"
+#define LIBSSH_ECDSA_384_TESTKEY  "libssh_testkey.id_ecdsa384"
+#define LIBSSH_ECDSA_521_TESTKEY  "libssh_testkey.id_ecdsa521"
+
+void setup_dsa_key(void);
+void setup_rsa_key(void);
+void setup_ecdsa_keys(void);
+void cleanup_dsa_key(void);
+void cleanup_rsa_key(void);
+void cleanup_ecdsa_keys(void);
+
+/* Client keys. */
+#define OPENSSH_DSA_TESTKEY       "openssh_testkey.id_dsa"
+#define OPENSSH_RSA_TESTKEY       "openssh_testkey.id_rsa"
+#define OPENSSH_ECDSA256_TESTKEY  "openssh_testkey.id_ecdsa256"
+#define OPENSSH_ECDSA384_TESTKEY  "openssh_testkey.id_ecdsa384"
+#define OPENSSH_ECDSA521_TESTKEY  "openssh_testkey.id_ecdsa521"
+#define OPENSSH_ED25519_TESTKEY   "openssh_testkey.id_ed25519"
+
+#define DROPBEAR_RSA_TESTKEY      "dropbear_testkey.id_rsa"
+
+void setup_openssh_client_keys(void);
+void cleanup_openssh_client_keys(void);
+
+void setup_dropbear_client_rsa_key(void);
+void cleanup_dropbear_client_rsa_key(void);
+
+#endif /* __PKD_KEYUTIL_H__ */
diff --git a/tests/pkd/pkd_util.c b/tests/pkd/pkd_util.c
new file mode 100644
index 0000000..95d3be6
--- /dev/null
+++ b/tests/pkd/pkd_util.c
@@ -0,0 +1,45 @@
+/*
+ * pkd_util.c -- pkd utilities
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "pkd_client.h"
+#include "pkd_util.h"
+
+/**
+ * @brief runs system(3); exits if that is interrupted with SIGINT/QUIT
+ * @returns 0 upon success, non-zero otherwise
+ */
+int system_checked(const char *cmd) {
+    int rc = system(cmd);
+
+    if (WIFSIGNALED(rc) &&
+        ((WTERMSIG(rc) == SIGINT) || (WTERMSIG(rc) == SIGQUIT))) {
+        exit(1);
+    }
+
+    if (rc == -1) {
+        return -1;
+    }
+
+    return WEXITSTATUS(rc);
+}
+
+static int bin_exists(const char *binary) {
+    char bin[1024] = { 0 };
+    snprintf(&bin[0], sizeof(bin), "type %s 1>/dev/null 2>/dev/null", binary);
+    return (system_checked(bin) == 0);
+}
+
+int is_openssh_client_enabled(void) {
+    return (bin_exists(OPENSSH_BINARY) && bin_exists(OPENSSH_KEYGEN));
+}
+
+int is_dropbear_client_enabled(void) {
+    return (bin_exists(DROPBEAR_BINARY) && bin_exists(DROPBEAR_KEYGEN));
+}
diff --git a/tests/pkd/pkd_util.h b/tests/pkd/pkd_util.h
new file mode 100644
index 0000000..aedbbe9
--- /dev/null
+++ b/tests/pkd/pkd_util.h
@@ -0,0 +1,16 @@
+/*
+ * pkd_keyutil.h --
+ *
+ * (c) 2014 Jon Simons
+ */
+
+#ifndef __PKD_UTIL_H__
+#define __PKD_UTIL_H__
+
+int system_checked(const char *cmd);
+
+/* Is client 'X' enabled? */
+int is_openssh_client_enabled(void);
+int is_dropbear_client_enabled(void);
+
+#endif /* __PKD_UTIL_H__ */
-- 
2.1.1.602.g02f248d


Follow-Ups:
Re: [PATCH] tests: introduce pkd_hello (resend: rebased)Andreas Schneider <asn@xxxxxxxxxxxxxx>
Archive administrator: postmaster@lists.cynapses.org