/*
* 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-interaction.h"
#include "gcr-ssh-agent-private.h"
#include "gcr-prompt.h"
#include "gcr-system-prompt.h"
#include "egg/egg-secure-memory.h"
#include
#include
static const SecretSchema schema = {
"org.freedesktop.Secret.Generic", SECRET_SCHEMA_NONE,
{
{ "unique", SECRET_SCHEMA_ATTRIBUTE_STRING },
{ NULL, 0 }
}
};
enum {
PROP_0,
PROP_PROMPTER_NAME,
PROP_LABEL,
PROP_FIELDS
};
struct _GcrSshAgentInteraction {
GTlsInteraction interaction;
gchar *prompter_name;
gchar *label;
GHashTable *fields;
};
G_DEFINE_TYPE (GcrSshAgentInteraction, gcr_ssh_agent_interaction, G_TYPE_TLS_INTERACTION);
EGG_SECURE_DECLARE (gcr_ssh_agent_interaction);
static void
gcr_ssh_agent_interaction_init (GcrSshAgentInteraction *self)
{
}
static void
on_store_password_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GError *error = NULL;
if (secret_password_store_finish (res, &error)) {
g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
} else {
g_task_return_error (task, error);
}
g_object_unref (task);
}
static void
on_prompt_password (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GcrSshAgentInteraction *self = g_task_get_source_object (task);
GTlsPassword *password = G_TLS_PASSWORD (g_task_get_task_data (task));
GcrPrompt *prompt = GCR_PROMPT (source_object);
GError *error = NULL;
const gchar *value;
gchar *secure_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;
}
secure_value = egg_secure_strndup (value, strlen (value));
g_tls_password_set_value_full (password,
(guchar *) secure_value,
strlen (secure_value),
(GDestroyNotify) egg_secure_free);
if (gcr_prompt_get_choice_chosen (prompt)) {
gchar *label;
label = g_strdup_printf (_("Unlock password for: %s"), self->label);
secret_password_storev (&schema,
self->fields,
NULL,
label,
secure_value,
g_task_get_cancellable (task),
on_store_password_ready,
task);
g_free (label);
} else {
g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
g_object_unref (task);
}
g_object_unref (prompt);
}
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);
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 = _("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
on_lookup_password_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
GcrSshAgentInteraction *self = g_task_get_source_object (task);
GTlsPassword *password = G_TLS_PASSWORD (g_task_get_task_data (task));
GError *error = NULL;
gchar *value = secret_password_lookup_finish (res, &error);
if (value) {
/* If password is found in the login keyring, return it */
g_tls_password_set_value_full (password,
(guchar *)value, strlen (value),
(GDestroyNotify)egg_secure_free);
g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
g_object_unref (task);
} else {
/* Otherwise, prompt a password */
gcr_system_prompt_open_for_prompter_async (self->prompter_name, 60,
g_task_get_cancellable (task),
on_prompt_open,
task);
}
}
static void
gcr_ssh_agent_interaction_ask_password_async (GTlsInteraction *interaction,
GTlsPassword *password,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GcrSshAgentInteraction *self = GCR_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);
secret_password_lookupv (&schema,
self->fields,
cancellable,
on_lookup_password_ready,
task);
}
static GTlsInteractionResult
gcr_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
gcr_ssh_agent_interaction_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GcrSshAgentInteraction *self = GCR_SSH_AGENT_INTERACTION (object);
switch (prop_id) {
case PROP_PROMPTER_NAME:
self->prompter_name = g_value_dup_string (value);
break;
case PROP_LABEL:
self->label = g_value_dup_string (value);
break;
case PROP_FIELDS:
self->fields = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gcr_ssh_agent_interaction_finalize (GObject *object)
{
GcrSshAgentInteraction *self = GCR_SSH_AGENT_INTERACTION (object);
g_free (self->prompter_name);
g_free (self->label);
g_hash_table_unref (self->fields);
G_OBJECT_CLASS (gcr_ssh_agent_interaction_parent_class)->finalize (object);
}
static void
gcr_ssh_agent_interaction_class_init (GcrSshAgentInteractionClass *klass)
{
GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
interaction_class->ask_password_async = gcr_ssh_agent_interaction_ask_password_async;
interaction_class->ask_password_finish = gcr_ssh_agent_interaction_ask_password_finish;
gobject_class->set_property = gcr_ssh_agent_interaction_set_property;
gobject_class->finalize = gcr_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));
g_object_class_install_property (gobject_class, PROP_LABEL,
g_param_spec_string ("label", "Label", "Label",
"",
G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
g_object_class_install_property (gobject_class, PROP_FIELDS,
g_param_spec_boxed ("fields", "Fields", "Fields",
G_TYPE_HASH_TABLE,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
}
GTlsInteraction *
gcr_ssh_agent_interaction_new (const gchar *prompter_name,
const gchar *label,
GHashTable *fields)
{
return g_object_new (GCR_TYPE_SSH_AGENT_INTERACTION,
"prompter-name", prompter_name,
"label", label,
"fields", fields,
NULL);
}