/*
* gnome-keyring
*
* Copyright (C) 2018 Red Hat, Inc.
*
* This program 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 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see
* .
*
* Author: Daiki Ueno
*/
#include "config.h"
#include "gcr-ssh-agent-service.h"
#include "gcr-ssh-agent-private.h"
#include "gcr-ssh-agent-util.h"
#include "gcr-ssh-agent-test.h"
#include "egg/egg-testing.h"
#include "egg/mock-interaction.h"
#include
#include
#include
typedef struct {
gchar *directory;
EggBuffer req;
EggBuffer resp;
GcrSshAgentService *service;
GMainContext *server_thread_context;
volatile gint server_thread_stop;
GSocketConnection *connection;
GThread *thread;
GMutex lock;
GCond cond;
} Test;
static gpointer
server_thread (gpointer data)
{
Test *test = data;
gboolean ret;
g_main_context_push_thread_default (test->server_thread_context);
ret = gcr_ssh_agent_service_start (test->service);
g_assert_true (ret);
g_mutex_lock (&test->lock);
g_cond_signal (&test->cond);
g_mutex_unlock (&test->lock);
while (g_atomic_int_get (&test->server_thread_stop) == 0)
g_main_context_iteration (test->server_thread_context, TRUE);
g_main_context_pop_thread_default (test->server_thread_context);
return NULL;
}
static void
connect_to_server (Test *test)
{
const gchar *envvar;
GSocketClient *client;
GSocketAddress *address;
GError *error;
envvar = g_getenv ("SSH_AUTH_SOCK");
g_assert_nonnull (envvar);
address = g_unix_socket_address_new (envvar);
client = g_socket_client_new ();
error = NULL;
test->connection = g_socket_client_connect (client,
G_SOCKET_CONNECTABLE (address),
NULL,
&error);
g_assert_nonnull (test->connection);
g_assert_no_error (error);
g_object_unref (address);
g_object_unref (client);
}
static void
setup (Test *test, gconstpointer unused)
{
GTlsInteraction *interaction;
GcrSshAgentPreload *preload;
gchar *sockets_path;
gchar *preload_path;
gchar *path;
test->directory = egg_tests_create_scratch_directory (NULL, NULL);
sockets_path = g_build_filename (test->directory, "sockets", NULL);
g_mkdir (sockets_path, 0700);
preload_path = g_build_filename (test->directory, "preload", NULL);
g_mkdir (preload_path, 0700);
egg_tests_copy_scratch_file (preload_path, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain");
egg_tests_copy_scratch_file (preload_path, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub");
path = g_build_filename (preload_path, "id_rsa_plain", NULL);
g_chmod (path, 0600);
g_free (path);
egg_buffer_init_full (&test->req, 128, (EggBufferAllocator)g_realloc);
egg_buffer_init_full (&test->resp, 128, (EggBufferAllocator)g_realloc);
preload = gcr_ssh_agent_preload_new (preload_path);
g_free (preload_path);
test->service = gcr_ssh_agent_service_new (sockets_path, preload);
g_free (sockets_path);
interaction = mock_interaction_new ("password");
g_object_set (test->service, "interaction", interaction, NULL);
g_object_unref (interaction);
g_object_unref (preload);
g_mutex_init (&test->lock);
g_cond_init (&test->cond);
test->server_thread_context = g_main_context_new ();
test->thread = g_thread_new ("ssh-agent", server_thread, test);
/* Wait until the server is up */
g_mutex_lock (&test->lock);
g_cond_wait (&test->cond, &test->lock);
g_mutex_unlock (&test->lock);
}
static void
teardown (Test *test, gconstpointer unused)
{
g_atomic_int_set (&test->server_thread_stop, 1);
g_main_context_wakeup (test->server_thread_context);
g_thread_join (test->thread);
g_main_context_unref (test->server_thread_context);
g_clear_object (&test->connection);
gcr_ssh_agent_service_stop (test->service);
g_object_unref (test->service);
egg_buffer_uninit (&test->req);
egg_buffer_uninit (&test->resp);
egg_tests_remove_scratch_directory (test->directory);
g_free (test->directory);
g_cond_clear (&test->cond);
g_mutex_clear (&test->lock);
}
static void
call (Test *test)
{
GError *error;
gboolean ret;
error = NULL;
ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
error = NULL;
ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
}
static void
call_error_or_failure (Test *test, gint dom, gint code)
{
GError *error;
gboolean ret;
error = NULL;
ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
error = NULL;
ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
if (ret)
check_failure (&test->resp);
else {
g_assert_false (ret);
g_assert_error (error, dom, code);
}
}
DEFINE_CALL_FUNCS(Test, call)
static void
call_unparseable_add (Test *test)
{
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
prepare_add_identity (&test->req);
egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
}
static void
call_unparseable_remove (Test *test)
{
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
prepare_remove_identity (&test->req);
egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
}
static void
call_unparseable_sign (Test *test)
{
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
prepare_sign_request (&test->req);
egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
}
static void
prepare_sign_request_unknown (EggBuffer *req)
{
GBytes *public_key;
gchar *comment;
gsize length;
const guchar *blob;
gboolean ret;
public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub", &comment);
g_free (comment);
blob = g_bytes_get_data (public_key, &length);
egg_buffer_reset (req);
ret = egg_buffer_add_uint32 (req, 0);
g_assert_true (ret);
ret = egg_buffer_add_byte (req, GCR_SSH_OP_SIGN_REQUEST);
g_assert_true (ret);
ret = egg_buffer_add_byte_array (req, blob, length);
g_assert_true (ret);
ret = egg_buffer_add_string (req, "data");
g_assert_true (ret);
ret = egg_buffer_add_uint32 (req, 0);
g_assert_true (ret);
ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
g_assert_true (ret);
g_bytes_unref (public_key);
}
static void
call_sign_unknown (Test *test)
{
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
prepare_sign_request_unknown (&test->req);
call (test);
check_failure (&test->resp);
}
static void
call_empty (Test *test)
{
GError *error;
gboolean ret;
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
ret = egg_buffer_add_uint32 (&test->req, 0);
g_assert_true (ret);
error = NULL;
ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
error = NULL;
ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
g_assert_false (ret);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
g_clear_error (&error);
}
static void
call_unknown (Test *test)
{
GError *error;
gboolean ret;
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
ret = egg_buffer_add_uint32 (&test->req, 0);
g_assert_true (ret);
ret = egg_buffer_add_byte (&test->req, 255);
g_assert_true (ret);
error = NULL;
ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
error = NULL;
ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
g_assert_true (ret);
g_assert_no_error (error);
check_failure (&test->resp);
}
static void
call_lock (Test *test)
{
gboolean ret;
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
ret = egg_buffer_add_uint32 (&test->req, 0);
g_assert_true (ret);
ret = egg_buffer_add_byte (&test->req, GCR_SSH_OP_LOCK);
g_assert_true (ret);
ret = egg_buffer_add_string (&test->req, "password");
g_assert_true (ret);
ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
g_assert_true (ret);
call (test);
check_success (&test->resp);
}
static void
call_unlock (Test *test)
{
gboolean ret;
egg_buffer_reset (&test->req);
egg_buffer_reset (&test->resp);
ret = egg_buffer_add_uint32 (&test->req, 0);
g_assert_true (ret);
ret = egg_buffer_add_byte (&test->req, GCR_SSH_OP_UNLOCK);
g_assert_true (ret);
ret = egg_buffer_add_string (&test->req, "password");
g_assert_true (ret);
ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
g_assert_true (ret);
call (test);
check_success (&test->resp);
}
static void
test_startup_shutdown (Test *test, gconstpointer unused)
{
}
static void
test_list (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_request_identities (test, 1);
}
static void
test_add (Test *test, gconstpointer unused)
{
connect_to_server (test);
/* Adding an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_add_identity (test);
call_request_identities (test, 1);
}
static void
test_unparseable_add (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_unparseable_add (test);
}
static void
test_unparseable_remove (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_unparseable_remove (test); /* This closes the connection */
}
static void
test_unparseable_sign (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_unparseable_sign (test); /* This closes the connection */
}
static void
test_remove (Test *test, gconstpointer unused)
{
connect_to_server (test);
/* Adding an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_add_identity (test);
call_request_identities (test, 1);
/* Removing an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_remove_identity (test);
call_request_identities (test, 1);
}
static void
test_remove_all (Test *test, gconstpointer unused)
{
connect_to_server (test);
/* Adding an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_add_identity (test);
call_request_identities (test, 1);
/* Removing an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_remove_all_identities (test);
call_request_identities (test, 1);
}
static void
test_sign_loaded (Test *test, gconstpointer unused)
{
connect_to_server (test);
/* Adding an identity from the preloaded location doesn't
* change the total number of keys returned from
* GCR_SSH_OP_REQUEST_IDENTITIES */
call_add_identity (test);
call_request_identities (test, 1);
call_sign (test);
}
static void
test_sign (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_sign (test);
}
static void
test_sign_unknown (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_sign_unknown (test);
}
static gpointer
kill_thread (gpointer data)
{
Test *test = data;
GcrSshAgentProcess *process;
GPid pid;
process = gcr_ssh_agent_service_get_process (test->service);
pid = gcr_ssh_agent_process_get_pid (process);
g_assert_cmpint (-1, !=, pid);
kill (pid, SIGTERM);
return NULL;
}
static void
on_closed (GcrSshAgentProcess *self, gpointer data)
{
GMainLoop *loop = data;
g_main_loop_quit (loop);
g_main_loop_unref (loop);
}
static void
test_restart (Test *test, gconstpointer unused)
{
GcrSshAgentProcess *process;
GThread *thread;
GMainLoop *loop;
GBytes *public_key;
gchar *comment;
connect_to_server (test);
public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub", &comment);
g_free (comment);
call_add_identity (test);
call_request_identities (test, 1);
g_assert_true (gcr_ssh_agent_service_lookup_key (test->service, public_key));
thread = g_thread_new ("kill", kill_thread, test);
loop = g_main_loop_new (NULL, FALSE);
process = gcr_ssh_agent_service_get_process (test->service);
g_signal_connect (process, "closed", G_CALLBACK (on_closed), loop);
g_main_loop_run (loop);
g_thread_join (thread);
g_assert_false (gcr_ssh_agent_service_lookup_key (test->service, public_key));
call_add_identity (test);
call_request_identities (test, 1);
g_assert_true (gcr_ssh_agent_service_lookup_key (test->service, public_key));
g_bytes_unref (public_key);
}
static void
test_empty (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_empty (test);
}
static void
test_unknown (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_unknown (test);
}
static void
test_lock (Test *test, gconstpointer unused)
{
connect_to_server (test);
call_lock (test);
call_unlock (test);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add ("/ssh-agent/service/startup_shutdown", Test, NULL, setup, test_startup_shutdown, teardown);
g_test_add ("/ssh-agent/service/list", Test, NULL, setup, test_list, teardown);
g_test_add ("/ssh-agent/service/add", Test, NULL, setup, test_add, teardown);
g_test_add ("/ssh-agent/service/remove", Test, NULL, setup, test_remove, teardown);
g_test_add ("/ssh-agent/service/remove_all", Test, NULL, setup, test_remove_all, teardown);
g_test_add ("/ssh-agent/service/sign_loaded", Test, NULL, setup, test_sign_loaded, teardown);
g_test_add ("/ssh-agent/service/sign", Test, NULL, setup, test_sign, teardown);
g_test_add ("/ssh-agent/service/sign_unknown", Test, NULL, setup, test_sign_unknown, teardown);
g_test_add ("/ssh-agent/service/empty", Test, NULL, setup, test_empty, teardown);
g_test_add ("/ssh-agent/service/unknown", Test, NULL, setup, test_unknown, teardown);
g_test_add ("/ssh-agent/service/unparseable_add", Test, NULL, setup, test_unparseable_add, teardown);
g_test_add ("/ssh-agent/service/unparseable_remove", Test, NULL, setup, test_unparseable_remove, teardown);
g_test_add ("/ssh-agent/service/unparseable_sign", Test, NULL, setup, test_unparseable_sign, teardown);
g_test_add ("/ssh-agent/service/restart", Test, NULL, setup, test_restart, teardown);
g_test_add ("/ssh-agent/service/lock", Test, NULL, setup, test_lock, teardown);
return g_test_run ();
}