diff options
author | Daiki Ueno <dueno@src.gnome.org> | 2018-02-23 10:28:38 +0100 |
---|---|---|
committer | Daiki Ueno <dueno@src.gnome.org> | 2018-03-04 10:25:00 +0100 |
commit | 5cbfcd6b51b32d5d9ecbed20e5dff46e46fd39cc (patch) | |
tree | 8da885ca146b5561e0383919b68b6bfdce39ed93 | |
parent | 9ec7de589a32e9f8409c5e2a04efb75a11cc1620 (diff) | |
download | gnome-keyring-5cbfcd6b51b32d5d9ecbed20e5dff46e46fd39cc.tar.gz |
ssh-agent: Add GkdSshAgentInteraction API
This could be used by the new implementation of ssh-agent, through the
GcrSshAskpass and GkdLoginInteraction.
https://bugzilla.gnome.org/show_bug.cgi?id=775981
-rw-r--r-- | daemon/ssh-agent/Makefile.am | 9 | ||||
-rw-r--r-- | daemon/ssh-agent/gkd-ssh-agent-interaction.c | 204 | ||||
-rw-r--r-- | daemon/ssh-agent/gkd-ssh-agent-interaction.h | 37 | ||||
-rw-r--r-- | daemon/ssh-agent/test-gkd-ssh-agent-interaction.c | 196 | ||||
-rw-r--r-- | po/POTFILES.in | 1 |
5 files changed, 446 insertions, 1 deletions
diff --git a/daemon/ssh-agent/Makefile.am b/daemon/ssh-agent/Makefile.am index 4fb718ea..8ed821bd 100644 --- a/daemon/ssh-agent/Makefile.am +++ b/daemon/ssh-agent/Makefile.am @@ -8,6 +8,8 @@ noinst_LTLIBRARIES += \ libgkd_ssh_agent_la_SOURCES = \ daemon/ssh-agent/gkd-ssh-agent.c \ daemon/ssh-agent/gkd-ssh-agent.h \ + daemon/ssh-agent/gkd-ssh-agent-interaction.c \ + daemon/ssh-agent/gkd-ssh-agent-interaction.h \ daemon/ssh-agent/gkd-ssh-agent-private.h \ daemon/ssh-agent/gkd-ssh-agent-ops.c \ daemon/ssh-agent/gkd-ssh-agent-proto.c @@ -47,7 +49,8 @@ ssh_agent_LIBS = \ ssh_agent_TESTS = \ test-communication \ - test-keytypes + test-keytypes \ + test-gkd-ssh-agent-interaction test_keytypes_SOURCES = daemon/ssh-agent/test-keytypes.c test_keytypes_CFLAGS = $(ssh_agent_CFLAGS) @@ -57,5 +60,9 @@ test_communication_SOURCES = daemon/ssh-agent/test-communication.c test_communication_CFLAGS = $(ssh_agent_CFLAGS) test_communication_LDADD = $(ssh_agent_LIBS) +test_gkd_ssh_agent_interaction_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-interaction.c +test_gkd_ssh_agent_interaction_CFLAGS = $(ssh_agent_CFLAGS) +test_gkd_ssh_agent_interaction_LDADD = $(ssh_agent_LIBS) libgkd-login.la libgkm-wrap-layer.la + check_PROGRAMS += $(ssh_agent_TESTS) TESTS += $(ssh_agent_TESTS) diff --git a/daemon/ssh-agent/gkd-ssh-agent-interaction.c b/daemon/ssh-agent/gkd-ssh-agent-interaction.c new file mode 100644 index 00000000..b3b7660a --- /dev/null +++ b/daemon/ssh-agent/gkd-ssh-agent-interaction.c @@ -0,0 +1,204 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daiki Ueno + */ + +#include "config.h" + +#include "gkd-ssh-agent-interaction.h" +#include "gkd-ssh-agent-private.h" +#include "daemon/login/gkd-login-password.h" + +#include <gcr/gcr-base.h> +#include <glib/gi18n-lib.h> + +enum { + PROP_0, + PROP_PROMPTER_NAME +}; + +struct _GkdSshAgentInteraction { + GTlsInteraction interaction; + gchar *prompter_name; +}; + +G_DEFINE_TYPE (GkdSshAgentInteraction, gkd_ssh_agent_interaction, G_TYPE_TLS_INTERACTION); + +static void +gkd_ssh_agent_interaction_init (GkdSshAgentInteraction *self) +{ +} + +static void +on_prompt_password (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GTlsPassword *password = g_task_get_task_data (task); + GkdLoginPassword *login_password = GKD_LOGIN_PASSWORD (password); + GcrPrompt *prompt = GCR_PROMPT (source_object); + GError *error = NULL; + const gchar *value; + + value = gcr_prompt_password_finish (prompt, result, &error); + if (!value) { + g_object_unref (prompt); + if (error) + g_task_return_error (task, error); + else + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "cancelled"); + g_object_unref (task); + return; + } + g_tls_password_set_value (password, (const guchar *)value, strlen (value)); + gkd_login_password_set_store_password (login_password, + gcr_prompt_get_choice_chosen (prompt)); + g_object_unref (prompt); + + g_task_return_int (task, G_TLS_INTERACTION_HANDLED); + g_object_unref (task); +} + +static void +on_prompt_open (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GTlsPassword *password = g_task_get_task_data (task); + GkdLoginPassword *login_password = GKD_LOGIN_PASSWORD (password); + GError *error = NULL; + GcrPrompt *prompt; + const gchar *choice; + gchar *text; + + prompt = gcr_system_prompt_open_finish (result, &error); + if (!prompt) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + gcr_prompt_set_title (prompt, _("Unlock private key")); + gcr_prompt_set_message (prompt, _("Enter password to unlock the private key")); + + /* TRANSLATORS: The private key is locked */ + text = g_strdup_printf (_("An application wants access to the private key “%s”, but it is locked"), + g_tls_password_get_description (password)); + gcr_prompt_set_description (prompt, text); + g_free (text); + + choice = NULL; + if (gkd_login_password_get_login_available (login_password)) + choice = _("Automatically unlock this key whenever I’m logged in"); + gcr_prompt_set_choice_label (prompt, choice); + gcr_prompt_set_continue_label (prompt, _("Unlock")); + + if (g_tls_password_get_flags (password) & G_TLS_PASSWORD_RETRY) + gcr_prompt_set_warning (prompt, _("The unlock password was incorrect")); + + gcr_prompt_password_async (prompt, g_task_get_cancellable (task), on_prompt_password, task); +} + +static void +gkd_ssh_agent_interaction_ask_password_async (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GkdSshAgentInteraction *self = GKD_SSH_AGENT_INTERACTION (interaction); + GTask *task; + + task = g_task_new (interaction, cancellable, callback, user_data); + g_task_set_task_data (task, g_object_ref (password), g_object_unref); + + gcr_system_prompt_open_for_prompter_async (self->prompter_name, 60, + cancellable, + on_prompt_open, + task); +} + +static GTlsInteractionResult +gkd_ssh_agent_interaction_ask_password_finish (GTlsInteraction *interaction, + GAsyncResult *res, + GError **error) +{ + GTask *task = G_TASK (res); + GTlsInteractionResult result; + + result = g_task_propagate_int (task, error); + if (result == -1) + return G_TLS_INTERACTION_FAILED; + return result; +} + +static void +gkd_ssh_agent_interaction_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GkdSshAgentInteraction *self = GKD_SSH_AGENT_INTERACTION (object); + + switch (prop_id) { + case PROP_PROMPTER_NAME: + self->prompter_name = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gkd_ssh_agent_interaction_finalize (GObject *object) +{ + GkdSshAgentInteraction *self = GKD_SSH_AGENT_INTERACTION (object); + + g_free (self->prompter_name); + + G_OBJECT_CLASS (gkd_ssh_agent_interaction_parent_class)->finalize (object); +} + +static void +gkd_ssh_agent_interaction_class_init (GkdSshAgentInteractionClass *klass) +{ + GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + interaction_class->ask_password_async = gkd_ssh_agent_interaction_ask_password_async; + interaction_class->ask_password_finish = gkd_ssh_agent_interaction_ask_password_finish; + + gobject_class->set_property = gkd_ssh_agent_interaction_set_property; + gobject_class->finalize = gkd_ssh_agent_interaction_finalize; + + g_object_class_install_property (gobject_class, PROP_PROMPTER_NAME, + g_param_spec_string ("prompter-name", "Prompter-name", "Prompter-name", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + +GTlsInteraction * +gkd_ssh_agent_interaction_new (const gchar *prompter_name) +{ + return g_object_new (GKD_TYPE_SSH_AGENT_INTERACTION, "prompter-name", prompter_name, NULL); +} diff --git a/daemon/ssh-agent/gkd-ssh-agent-interaction.h b/daemon/ssh-agent/gkd-ssh-agent-interaction.h new file mode 100644 index 00000000..20f6647b --- /dev/null +++ b/daemon/ssh-agent/gkd-ssh-agent-interaction.h @@ -0,0 +1,37 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daiki Ueno + */ + +#ifndef _GKD_SSH_AGENT_INTERACTION_H__ +#define __GKD_SSH_AGENT_INTERACTION_H__ + +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define GKD_TYPE_SSH_AGENT_INTERACTION gkd_ssh_agent_interaction_get_type () +G_DECLARE_FINAL_TYPE (GkdSshAgentInteraction, gkd_ssh_agent_interaction, GKD, SSH_AGENT_INTERACTION, GTlsInteraction) + +GTlsInteraction *gkd_ssh_agent_interaction_new (const gchar *prompter_name); + +G_END_DECLS + +#endif /* __GKD_SSH_INTERACTION_H__ */ diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-interaction.c b/daemon/ssh-agent/test-gkd-ssh-agent-interaction.c new file mode 100644 index 00000000..0f53aee9 --- /dev/null +++ b/daemon/ssh-agent/test-gkd-ssh-agent-interaction.c @@ -0,0 +1,196 @@ +/* + * 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 + * <http://www.gnu.org/licenses/>. + * + * Author: Daiki Ueno + */ + +#include "config.h" + +#include "gkd-ssh-agent-private.h" +#include "gkd-ssh-agent-interaction.h" +#include "daemon/login/gkd-login-password.h" +#include "egg/egg-testing.h" + +#include <glib.h> +#include <glib/gstdio.h> +#include <gcr/gcr-base.h> + +typedef struct { + const gchar *prompter_name; + GTlsPassword *password; +} Test; + +static void +setup (Test *test, gboolean login_available) +{ + GTlsPassword *password; + + password = g_tls_password_new (G_TLS_PASSWORD_NONE, ""); + test->password = g_object_new (GKD_TYPE_LOGIN_PASSWORD, + "base", password, + "login-available", login_available, + "description", "ssh-key", + NULL); + g_object_unref (password); + + test->prompter_name = gcr_mock_prompter_start (); +} + +static void +setup_no_login (Test *test, gconstpointer unused) +{ + setup (test, FALSE); +} + +static void +setup_login (Test *test, gconstpointer unused) +{ + setup (test, TRUE); +} + +static void +teardown (Test *test, gconstpointer unused) +{ + gcr_mock_prompter_stop (); + + g_object_unref (test->password); +} + +static void +on_async_result (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **ret = user_data; + *ret = g_object_ref (result); + egg_test_wait_stop (); +} + +static void +test_ask_password_no_login (Test *test, gconstpointer unused) +{ + GTlsInteraction *interaction; + GAsyncResult *result = NULL; + GError *error = NULL; + const guchar *value; + gsize length; + GTlsInteractionResult ret; + + interaction = gkd_ssh_agent_interaction_new (test->prompter_name); + gcr_mock_prompter_expect_password_ok ("password", NULL); + g_tls_interaction_ask_password_async (interaction, + test->password, + NULL, + on_async_result, + &result); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (result != NULL); + + ret = g_tls_interaction_ask_password_finish (interaction, result, &error); + g_assert_cmpint (ret, ==, G_TLS_INTERACTION_HANDLED); + g_assert_no_error (error); + + value = g_tls_password_get_value (test->password, &length); + g_assert_cmpmem ("password", 8, value, length); + g_assert_false (gkd_login_password_get_store_password (GKD_LOGIN_PASSWORD (test->password))); + g_object_unref (interaction); + g_object_unref (result); +} + +static void +test_ask_password_login (Test *test, gconstpointer unused) +{ + GTlsInteraction *interaction; + GAsyncResult *result = NULL; + GError *error = NULL; + const guchar *value; + gsize length; + GTlsInteractionResult ret; + + interaction = gkd_ssh_agent_interaction_new (test->prompter_name); + gcr_mock_prompter_expect_password_ok ("password", "choice-chosen", TRUE, NULL); + g_tls_interaction_ask_password_async (interaction, + test->password, + NULL, + on_async_result, + &result); + g_assert (result == NULL); + + egg_test_wait (); + + ret = g_tls_interaction_ask_password_finish (interaction, result, &error); + g_assert_cmpint (ret, ==, G_TLS_INTERACTION_HANDLED); + g_assert_no_error (error); + + value = g_tls_password_get_value (test->password, &length); + g_assert_cmpmem ("password", 8, value, length); + g_assert_true (gkd_login_password_get_store_password (GKD_LOGIN_PASSWORD (test->password))); + g_object_unref (interaction); + g_object_unref (result); +} + +static void +test_ask_password_cancel (Test *test, gconstpointer unused) +{ + GTlsInteraction *interaction; + GAsyncResult *result = NULL; + GError *error = NULL; + const guchar *value; + gsize length; + GTlsInteractionResult ret; + + interaction = gkd_ssh_agent_interaction_new (test->prompter_name); + gcr_mock_prompter_expect_password_cancel (); + g_tls_interaction_ask_password_async (interaction, + test->password, + NULL, + on_async_result, + &result); + g_assert (result == NULL); + + egg_test_wait (); + + g_assert (result != NULL); + + + ret = g_tls_interaction_ask_password_finish (interaction, result, &error); + g_assert_cmpint (ret, ==, G_TLS_INTERACTION_FAILED); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + + value = g_tls_password_get_value (test->password, &length); + g_assert_cmpmem ("", 0, value, length); + g_assert_false (gkd_login_password_get_store_password (GKD_LOGIN_PASSWORD (test->password))); + g_object_unref (interaction); + g_object_unref (result); +} + +int +main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add ("/ssh-agent/interaction/ask_password_no_login", Test, NULL, setup_no_login, test_ask_password_no_login, teardown); + g_test_add ("/ssh-agent/interaction/ask_password_login", Test, NULL, setup_login, test_ask_password_login, teardown); + g_test_add ("/ssh-agent/interaction/ask_password_cancel", Test, NULL, setup_no_login, test_ask_password_cancel, teardown); + + return egg_tests_run_with_loop (); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 3ed5e3dc..8f82ce23 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -7,6 +7,7 @@ daemon/gnome-keyring-pkcs11.desktop.in.in daemon/gnome-keyring-secrets.desktop.in.in daemon/gnome-keyring-ssh.desktop.in.in daemon/login/gkd-login.c +daemon/ssh-agent/gkd-ssh-agent-interaction.c egg/dotlock.c egg/egg-oid.c pkcs11/gkm/gkm-certificate.c |