[hotssh] Initial host key fingerprint checking
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hotssh] Initial host key fingerprint checking
- Date: Fri, 11 Oct 2013 02:00:43 +0000 (UTC)
commit 5d80427764420d149cbbae0ad5f5680d06b96bcb
Author: Colin Walters <walters verbum org>
Date: Thu Oct 10 21:59:33 2013 -0400
Initial host key fingerprint checking
Ok, we prompt for the key every time. But that's better than not
prompting...
Need to read the OpenSSH known hosts at least, or consider having our
own database.
libgssh/gssh-connection-private.h | 12 +--
libgssh/gssh-connection.c | 147 ++++++++++++++++++++++---------------
libgssh/gssh-connection.h | 5 +
src/hotssh-win.c | 64 +++++++++++++++-
src/window.ui | 124 ++++++++++++++++++++++++++++---
5 files changed, 271 insertions(+), 81 deletions(-)
---
diff --git a/libgssh/gssh-connection-private.h b/libgssh/gssh-connection-private.h
index 92abe63..0e7bf22 100644
--- a/libgssh/gssh-connection-private.h
+++ b/libgssh/gssh-connection-private.h
@@ -23,21 +23,16 @@
#include "gssh-connection.h"
#include <libssh2.h>
-typedef enum {
- GSSH_CONNECTION_HANDSHAKE_STATE_STARTING,
- GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST
-} GSshConnectionHandshakeState;
-
struct _GSshConnection
{
GObject parent;
GSshConnectionState state;
- GSshConnectionHandshakeState handshake_state;
guint select_inbound : 1;
guint select_outbound : 1;
- guint unused : 30;
+ guint preauth_continue : 1;
+ guint unused : 29;
char *username;
@@ -47,6 +42,7 @@ struct _GSshConnection
GHashTable *channels;
GError *cached_error;
+ GBytes *remote_hostkey_sha1;
GMainContext *maincontext;
GSocketClient *socket_client;
GSocket *socket;
@@ -56,7 +52,7 @@ struct _GSshConnection
GCancellable *cancellable;
char *password;
- GTask *connection_task;
+ GTask *handshake_task;
GTask *auth_task;
GHashTable *open_channel_exec_tasks;
GHashTable *channel_tasks;
diff --git a/libgssh/gssh-connection.c b/libgssh/gssh-connection.c
index 3f887a1..09997c6 100644
--- a/libgssh/gssh-connection.c
+++ b/libgssh/gssh-connection.c
@@ -66,7 +66,7 @@ reset_state (GSshConnection *self)
g_assert (self->state == GSSH_CONNECTION_STATE_DISCONNECTED ||
self->state == GSSH_CONNECTION_STATE_ERROR);
- g_clear_object (&self->connection_task);
+ g_clear_object (&self->handshake_task);
g_clear_pointer (&self->open_channel_exec_tasks, g_hash_table_unref);
self->open_channel_exec_tasks = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
g_clear_pointer (&self->channel_tasks, g_hash_table_unref);
@@ -85,13 +85,13 @@ reset_state (GSshConnection *self)
/* This hash doesn't hold a ref */
self->channels = g_hash_table_new_full (NULL, NULL, NULL, NULL);
+ g_clear_pointer (&self->remote_hostkey_sha1, g_bytes_unref);
g_clear_error (&self->cached_error);
g_clear_object (&self->socket);
if (self->socket_source)
g_source_destroy (self->socket_source);
g_clear_pointer (&self->socket_source, g_source_unref);
g_clear_pointer (&self->authschemes, g_strfreev);
- self->handshake_state = GSSH_CONNECTION_HANDSHAKE_STATE_STARTING;
}
static void
@@ -117,10 +117,10 @@ state_transition_take_error (GSshConnection *self,
{
g_debug ("caught error: %s", error->message);
- if (self->connection_task)
+ if (self->handshake_task)
{
- g_task_return_error (self->connection_task, error);
- g_clear_object (&self->connection_task);
+ g_task_return_error (self->handshake_task, error);
+ g_clear_object (&self->handshake_task);
}
else
{
@@ -332,60 +332,60 @@ gssh_connection_iteration (GSshConnection *self,
case GSSH_CONNECTION_STATE_HANDSHAKING:
{
int socket_fd = g_socket_get_fd (self->socket);
- switch (self->handshake_state)
+ if ((rc = libssh2_session_handshake (self->session, socket_fd)) == LIBSSH2_ERROR_EAGAIN)
{
- case GSSH_CONNECTION_HANDSHAKE_STATE_STARTING:
- {
- g_debug ("handshake on socket fd=%d", socket_fd);
- if ((rc = libssh2_session_handshake (self->session, socket_fd)) == LIBSSH2_ERROR_EAGAIN)
- {
- recalculate_socket_state (self);
- return;
- }
- if (rc)
- {
- _gssh_set_error_from_libssh2 (error, "Failed to handshake SSH2 session", self->session);
- state_transition_take_error (self, local_error);
- return;
- }
- self->handshake_state = GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST;
- goto repeat;
- }
- case GSSH_CONNECTION_HANDSHAKE_STATE_AUTHLIST:
- {
- const char *authschemes;
-
- authschemes = libssh2_userauth_list (self->session,
- self->username,
- strlen (self->username));
- if (authschemes == NULL)
- {
- if (libssh2_userauth_authenticated (self->session))
- {
- /* Unusual according to the docs, but it's
- * possible for the remote server to be
- * configured without auth.
- */
- state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
- goto repeat;
- }
- else if (libssh2_session_last_error (self->session, NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN)
- {
- recalculate_socket_state (self);
- return;
- }
- else
- {
- _gssh_set_error_from_libssh2 (error, "Failed to list auth mechanisms", self->session);
- state_transition_take_error (self, local_error);
- return;
- }
- }
- self->authschemes = g_strsplit (authschemes, ",", 0);
- state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
- goto repeat;
- }
+ recalculate_socket_state (self);
+ return;
}
+ if (rc)
+ {
+ _gssh_set_error_from_libssh2 (error, "Failed to handshake SSH2 session", self->session);
+ g_task_return_error (self->handshake_task, local_error);
+ g_clear_object (&self->handshake_task);
+ return;
+ }
+ self->remote_hostkey_sha1 = g_bytes_new (libssh2_hostkey_hash (self->session,
LIBSSH2_HOSTKEY_HASH_SHA1), 20);
+ g_task_return_boolean (self->handshake_task, TRUE);
+ g_clear_object (&self->handshake_task);
+ state_transition (self, GSSH_CONNECTION_STATE_PREAUTH);
+ goto repeat;
+ }
+ case GSSH_CONNECTION_STATE_PREAUTH:
+ {
+ const char *authschemes;
+
+ if (!self->preauth_continue)
+ break;
+
+ authschemes = libssh2_userauth_list (self->session,
+ self->username,
+ strlen (self->username));
+ if (authschemes == NULL)
+ {
+ if (libssh2_userauth_authenticated (self->session))
+ {
+ /* Unusual according to the docs, but it's
+ * possible for the remote server to be
+ * configured without auth.
+ */
+ state_transition (self, GSSH_CONNECTION_STATE_CONNECTED);
+ goto repeat;
+ }
+ else if (libssh2_session_last_error (self->session, NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN)
+ {
+ recalculate_socket_state (self);
+ return;
+ }
+ else
+ {
+ _gssh_set_error_from_libssh2 (error, "Failed to list auth mechanisms", self->session);
+ state_transition_take_error (self, local_error);
+ return;
+ }
+ }
+ self->authschemes = g_strsplit (authschemes, ",", 0);
+ state_transition (self, GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED);
+ goto repeat;
}
break;
case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
@@ -468,7 +468,7 @@ on_socket_ready (GSocket *socket,
return FALSE;
}
- g_debug ("socket ready: state %d handshake_state: %d", self->state, self->handshake_state);
+ g_debug ("socket ready: state %d", self->state);
gssh_connection_iteration (self, condition);
@@ -524,6 +524,34 @@ gssh_connection_get_state (GSshConnection *self)
return self->state;
}
+/**
+ * gssh_connection_preauth_get_fingerprint_sha1:
+ * @self: Self
+ *
+ * Returns: (transfer none): 20 bytes for the remote host's SHA1 fingerprint
+ */
+GBytes *
+gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self)
+{
+ return self->remote_hostkey_sha1;
+}
+
+/**
+ * gssh_connection_preauth_continue:
+ * @self: Self
+ *
+ * Call this function after having verified the host key fingerprint
+ * to continue the connection.
+ */
+void
+gssh_connection_preauth_continue (GSshConnection *self)
+{
+ g_return_if_fail (self->state == GSSH_CONNECTION_STATE_PREAUTH);
+ g_return_if_fail (!self->preauth_continue);
+ self->preauth_continue = TRUE;
+ gssh_connection_iteration_default (self);
+}
+
const char*const*
gssh_connection_get_authentication_mechanisms (GSshConnection *self)
{
@@ -580,8 +608,7 @@ gssh_connection_handshake_async (GSshConnection *self,
state_transition (self, GSSH_CONNECTION_STATE_CONNECTING);
- self->connection_task = g_task_new (self, cancellable, callback, user_data);
-
+ self->handshake_task = g_task_new (self, cancellable, callback, user_data);
g_socket_client_connect_async (self->socket_client, self->address, cancellable,
on_socket_client_connected, self);
}
diff --git a/libgssh/gssh-connection.h b/libgssh/gssh-connection.h
index 85540e1..e4bed0e 100644
--- a/libgssh/gssh-connection.h
+++ b/libgssh/gssh-connection.h
@@ -33,6 +33,7 @@ typedef enum /*< prefix=GSSH_CONNECTION_STATE >*/
GSSH_CONNECTION_STATE_DISCONNECTED,
GSSH_CONNECTION_STATE_CONNECTING,
GSSH_CONNECTION_STATE_HANDSHAKING,
+ GSSH_CONNECTION_STATE_PREAUTH,
GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED,
GSSH_CONNECTION_STATE_CONNECTED,
GSSH_CONNECTION_STATE_ERROR
@@ -58,6 +59,10 @@ gboolean gssh_connection_handshake_finish (GSshConnection *sel
GAsyncResult *result,
GError **error);
+GBytes * gssh_connection_preauth_get_fingerprint_sha1 (GSshConnection *self);
+
+void gssh_connection_preauth_continue (GSshConnection *self);
+
const char*const* gssh_connection_get_authentication_mechanisms (GSshConnection *self);
void gssh_connection_auth_password_async (GSshConnection *self,
diff --git a/src/hotssh-win.c b/src/hotssh-win.c
index c8a33c3..433d602 100644
--- a/src/hotssh-win.c
+++ b/src/hotssh-win.c
@@ -23,6 +23,7 @@ typedef struct _HotSshWindowPrivate HotSshWindowPrivate;
typedef enum {
HOTSSH_WINDOW_PAGE_NEW_CONNECTION,
HOTSSH_WINDOW_PAGE_INTERSTITAL,
+ HOTSSH_WINDOW_PAGE_AUTH,
HOTSSH_WINDOW_PAGE_TERMINAL
} HotSshWindowPage;
@@ -42,6 +43,10 @@ struct _HotSshWindowPrivate
GtkWidget *password_entry;
GtkWidget *password_submit;
GtkWidget *connect_cancel_button;
+ GtkWidget *auth_cancel_button;
+ GtkWidget *hostkey_container;
+ GtkWidget *hostkey_fingerprint_label;
+ GtkWidget *approve_hostkey_button;
GtkWidget *disconnect_button;
GtkWidget *terminal_box;
@@ -92,8 +97,9 @@ state_reset_for_new_connection (HotSshWindow *self)
vte_terminal_reset ((VteTerminal*)priv->terminal, TRUE, TRUE);
gtk_entry_set_text ((GtkEntry*)priv->password_entry, "");
reset_focus_state (self);
- gtk_widget_hide (priv->password_container);
+ gtk_label_set_text ((GtkLabel*)priv->connection_text, "");
gtk_widget_show (priv->connection_text_container);
+ gtk_widget_hide (priv->hostkey_container);
gtk_widget_set_sensitive (priv->password_container, TRUE);
g_debug ("reset state done");
}
@@ -188,6 +194,9 @@ on_open_shell_complete (GObject *src,
}
static void
+iterate_authentication_modes (HotSshWindow *self);
+
+static void
on_connection_state_notify (GSshConnection *conn,
GParamSpec *pspec,
gpointer user_data)
@@ -205,8 +214,16 @@ on_connection_state_notify (GSshConnection *conn,
break;
case GSSH_CONNECTION_STATE_CONNECTING:
case GSSH_CONNECTION_STATE_HANDSHAKING:
+ case GSSH_CONNECTION_STATE_PREAUTH:
+ page_transition (self, HOTSSH_WINDOW_PAGE_INTERSTITAL);
+ break;
case GSSH_CONNECTION_STATE_AUTHENTICATION_REQUIRED:
+ page_transition (self, HOTSSH_WINDOW_PAGE_AUTH);
+ iterate_authentication_modes (self);
+ break;
case GSSH_CONNECTION_STATE_ERROR:
+ gtk_widget_hide (priv->hostkey_container);
+ gtk_widget_show (priv->connection_text_container);
page_transition (self, HOTSSH_WINDOW_PAGE_INTERSTITAL);
break;
case GSSH_CONNECTION_STATE_CONNECTED:
@@ -277,13 +294,37 @@ on_connection_handshake (GObject *object,
gpointer user_data)
{
HotSshWindow *self = HOTSSH_WINDOW (user_data);
+ HotSshWindowPrivate *priv = hotssh_window_get_instance_private (self);
GError *local_error = NULL;
GError **error = &local_error;
+ GBytes *hostkey_sha1_binary;
+ GString *buf;
+ guint i;
+ const guint8 *binbuf;
+ gsize len;
+ gs_free char *hostkey_sha1_text = NULL;
if (!gssh_connection_handshake_finish ((GSshConnection*)object, result, error))
goto out;
- iterate_authentication_modes (self);
+ g_debug ("handshake complete");
+
+ hostkey_sha1_binary = gssh_connection_preauth_get_fingerprint_sha1 (priv->connection);
+ binbuf = g_bytes_get_data (hostkey_sha1_binary, &len);
+ buf = g_string_new ("");
+ for (i = 0; i < len; i++)
+ {
+ g_string_append_printf (buf, "%02X", binbuf[i]);
+ if (i < len - 1)
+ g_string_append_c (buf, ':');
+ }
+ hostkey_sha1_text = g_string_free (buf, FALSE);
+
+ gtk_label_set_text ((GtkLabel*)priv->hostkey_fingerprint_label,
+ hostkey_sha1_text);
+ gtk_widget_hide (priv->connection_text_container);
+ gtk_widget_show (priv->hostkey_container);
+ page_transition (self, HOTSSH_WINDOW_PAGE_INTERSTITAL);
out:
if (local_error)
@@ -322,8 +363,9 @@ on_connect (GtkButton *button,
g_signal_connect_object (priv->connection, "notify::state",
G_CALLBACK (on_connection_state_notify),
self, 0);
+ g_debug ("connected, beginning handshake");
gssh_connection_handshake_async (priv->connection, priv->cancellable,
- on_connection_handshake, self);
+ on_connection_handshake, self);
}
static void
@@ -426,6 +468,16 @@ on_connect_cancel (GtkButton *button,
}
static void
+on_approve_hostkey_clicked (GtkButton *button,
+ gpointer user_data)
+{
+ HotSshWindow *self = user_data;
+ HotSshWindowPrivate *priv = hotssh_window_get_instance_private (self);
+
+ gssh_connection_preauth_continue (priv->connection);
+}
+
+static void
send_pty_size_request (HotSshWindow *self);
static void
@@ -490,6 +542,8 @@ hotssh_window_init (HotSshWindow *self)
g_signal_connect (priv->connect_button, "clicked", G_CALLBACK (on_connect), self);
g_signal_connect (priv->connect_cancel_button, "clicked", G_CALLBACK (on_connect_cancel), self);
+ g_signal_connect (priv->auth_cancel_button, "clicked", G_CALLBACK (on_connect_cancel), self);
+ g_signal_connect (priv->approve_hostkey_button, "clicked", G_CALLBACK (on_approve_hostkey_clicked), self);
g_signal_connect (priv->disconnect_button, "clicked", G_CALLBACK (on_disconnect), self);
g_signal_connect_swapped (priv->password_entry, "activate", G_CALLBACK (submit_password), self);
g_signal_connect_swapped (priv->password_submit, "clicked", G_CALLBACK (submit_password), self);
@@ -534,6 +588,10 @@ hotssh_window_class_init (HotSshWindowClass *class)
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, password_entry);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, password_submit);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow,
connect_cancel_button);
+ gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, auth_cancel_button);
+ gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, hostkey_container);
+ gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow,
hostkey_fingerprint_label);
+ gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow,
approve_hostkey_button);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, disconnect_button);
gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (class), HotSshWindow, terminal_box);
}
diff --git a/src/window.ui b/src/window.ui
index 2567569..ed67e71 100644
--- a/src/window.ui
+++ b/src/window.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.15.4 on Thu Sep 19 13:48:41 2013 -->
+<!-- Generated with glade 3.15.4 on Thu Oct 10 21:41:56 2013 -->
<interface>
<!-- interface-requires gtk+ 3.10 -->
<template class="HotSshWindow" parent="GtkApplicationWindow">
@@ -117,15 +117,14 @@
</packing>
</child>
<child>
- <object class="GtkBox" id="box1">
+ <object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
- <object class="GtkBox" id="box3">
+ <object class="GtkBox" id="interstital_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="orientation">vertical</property>
<child>
<object class="GtkAlignment" id="connection_text_container">
<property name="visible">True</property>
@@ -135,16 +134,121 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Connecting...</property>
+ <property name="justify">center</property>
</object>
</child>
</object>
<packing>
- <property name="expand">True</property>
+ <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
+ <object class="GtkBox" id="hostkey_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="hostkey_label_text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Approval of newly seen host key required;
RSA key fingerprint is:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="hostkey_fingerprint_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label"
translatable="yes">00:--:--:--:--:--:--:--:--:--:--:--:--:--:--:00</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="approve_hostkey_button">
+ <property name="label" translatable="yes">Approve and Connect</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="xalign">0.55000001192092896</property>
+ <property name="yalign">0.57999998331069946</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="connect_cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">end</property>
+ <property name="use_stock">True</property>
+ <property name="image_position">right</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">page 2</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="baseline_position">top</property>
+ <child>
<object class="GtkBox" id="password_container">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -207,7 +311,7 @@
</packing>
</child>
<child>
- <object class="GtkButton" id="connect_cancel_button">
+ <object class="GtkButton" id="auth_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
@@ -224,7 +328,7 @@
</child>
</object>
<packing>
- <property name="position">1</property>
+ <property name="position">2</property>
</packing>
</child>
<child type="tab">
@@ -234,7 +338,7 @@
<property name="label" translatable="yes">page 2</property>
</object>
<packing>
- <property name="position">1</property>
+ <property name="position">2</property>
<property name="tab_fill">False</property>
</packing>
</child>
@@ -303,7 +407,7 @@
</child>
</object>
<packing>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
<child type="tab">
@@ -313,7 +417,7 @@
<property name="label" translatable="yes">page 3</property>
</object>
<packing>
- <property name="position">2</property>
+ <property name="position">3</property>
<property name="tab_fill">False</property>
</packing>
</child>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]