[seahorse] Ported the SSH module to Vala.



commit 8fe1c6d99462e296a8ddedd2cf7dd658f2f26a9f
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Fri Jan 13 20:51:57 2017 +0100

    Ported the SSH module to Vala.
    
    * Added separate file for the type of algorithm: `algorithm.vala`
      This should facilitate adding other encryption types.
    * Added SSH-specific errors: `errors.vala`. Just a stump right now, but
      we should be able to expand that when the time comes.
    * The dialogs no longer use Seahorse.ObjectWidget/Seahorse.Widget. (In
      the long term, we'll probably deprecate these)
    * An Operation is an object, which provides us a convenient closure.
    * Added a lot more comments.
    * Some types of base classes should've been nullable, but weren't.

 common/actions.vala                |    2 +-
 common/backend.vala                |    4 +-
 common/config.vapi                 |   39 ++
 gkr/gkr-backend.vala               |    4 +-
 po/POTFILES.in                     |   24 +-
 ssh/Makefile.am                    |   70 ++-
 ssh/actions.vala                   |   78 +++
 ssh/algorithm.vala                 |   87 +++
 ssh/backend.vala                   |   84 +++
 ssh/deleter.vala                   |  123 ++++
 ssh/errors.vala                    |   21 +
 ssh/exporter.vala                  |  130 ++++
 ssh/generate.vala                  |  155 +++++
 ssh/key-data.vala                  |  255 ++++++++
 ssh/key-properties.vala            |  198 +++++++
 ssh/key.vala                       |  340 +++++++++++
 ssh/operation.vala                 |  397 +++++++++++++
 ssh/seahorse-ssh-actions.c         |  133 -----
 ssh/seahorse-ssh-actions.h         |   29 -
 ssh/seahorse-ssh-askpass.c         |    1 +
 ssh/seahorse-ssh-backend.c         |  273 ---------
 ssh/seahorse-ssh-backend.h         |   47 --
 ssh/seahorse-ssh-deleter.c         |  204 -------
 ssh/seahorse-ssh-deleter.h         |   41 --
 ssh/seahorse-ssh-dialogs.h         |   20 -
 ssh/seahorse-ssh-exporter.c        |  331 -----------
 ssh/seahorse-ssh-exporter.h        |   40 --
 ssh/seahorse-ssh-generate.c        |  221 -------
 ssh/seahorse-ssh-generate.ui       |  509 ++++++++---------
 ssh/seahorse-ssh-key-data.c        |  453 --------------
 ssh/seahorse-ssh-key-data.h        |  103 ----
 ssh/seahorse-ssh-key-properties.c  |  382 ------------
 ssh/seahorse-ssh-key-properties.ui |  806 ++++++++++++--------------
 ssh/seahorse-ssh-key.c             |  459 ---------------
 ssh/seahorse-ssh-key.h             |   92 ---
 ssh/seahorse-ssh-operation.c       | 1140 ------------------------------------
 ssh/seahorse-ssh-operation.h       |  116 ----
 ssh/seahorse-ssh-source.c          | 1008 -------------------------------
 ssh/seahorse-ssh-source.h          |   96 ---
 ssh/seahorse-ssh-upload.c          |  185 ------
 ssh/seahorse-ssh-upload.ui         |  271 ++++------
 ssh/seahorse-ssh.h                 |   39 --
 ssh/source.vala                    |  462 +++++++++++++++
 ssh/{seahorse-ssh.c => ssh.vala}   |   25 +-
 ssh/upload.vala                    |  144 +++++
 45 files changed, 3300 insertions(+), 6341 deletions(-)
---
diff --git a/common/actions.vala b/common/actions.vala
index 1e4637c..08cd7bb 100644
--- a/common/actions.vala
+++ b/common/actions.vala
@@ -65,7 +65,7 @@ public class Actions : Gtk.ActionGroup {
        private unowned string? _definition;
        private WeakRef _catalog;
 
-       Actions(string name) {
+       public Actions(string name) {
                GLib.Object(
                        name: name
                );
diff --git a/common/backend.vala b/common/backend.vala
index 257cdc8..9f245ec 100644
--- a/common/backend.vala
+++ b/common/backend.vala
@@ -22,10 +22,10 @@ public interface Backend : Gcr.Collection {
        public abstract string name { get; }
        public abstract string label { get; }
        public abstract string description { get; }
-       public abstract Gtk.ActionGroup actions { owned get; }
+       public abstract Gtk.ActionGroup? actions { owned get; }
        public abstract bool loaded { get; }
 
-       public abstract Place lookup_place(string uri);
+       public abstract Place? lookup_place(string uri);
 
        public void register() {
                Registry.register_object(this, "backend");
diff --git a/common/config.vapi b/common/config.vapi
index 32f6d1a..7ce46ba 100644
--- a/common/config.vapi
+++ b/common/config.vapi
@@ -3,9 +3,15 @@ namespace Config
 {
        public const string PKGDATADIR;
 
+       public const string EXECDIR;
+
        public const string VERSION;
        public const string PACKAGE;
        public const string GETTEXT_PACKAGE;
+
+       public const bool WITH_SSH;
+       public const string SSH_PATH;
+       public const string SSH_KEYGEN_PATH;
 }
 
 /*
@@ -39,4 +45,37 @@ public class Interaction : GLib.TlsInteraction {
        public Gtk.Window? parent;
 }
 
+[CCode (cheader_filename = "libseahorse/seahorse-object.h")]
+public class Object : GLib.Object {
+       public Place place { get; }
+       public Actions actions { get; }
+       public string label { get; }
+       public string markup { get; }
+       public string nickname { get; }
+       public GLib.Icon icon { get; }
+       public string identifier { get; }
+       public Usage usage { get; }
+       public Seahorse.Flags flags { get; }
+
+       public Object();
+}
+
+[CCode (cheader_filename = "libseahorse/seahorse-validity.h", cname = "SeahorseValidity", cprefix = 
"SEAHORSE_VALIDITY_", has_type_id = false)]
+public enum Validity {
+       REVOKED,
+       DISABLED,
+       NEVER,
+       UNKNOWN,
+       MARGINAL,
+       FULL,
+       ULTIMATE;
+
+       public string? get_string();
+}
+
+[CCode (cheader_filename = "libseahorse/seahorse-progress.h")]
+namespace Progress {
+       public void show(GLib.Cancellable? cancellable, string title, bool delayed);
+}
+
 }
diff --git a/gkr/gkr-backend.vala b/gkr/gkr-backend.vala
index b88d35e..9003c5a 100644
--- a/gkr/gkr-backend.vala
+++ b/gkr/gkr-backend.vala
@@ -43,7 +43,7 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
                get { return _("Stored personal passwords, credentials and secrets"); }
        }
 
-       public Gtk.ActionGroup actions {
+       public Gtk.ActionGroup? actions {
                owned get { return this._actions; }
        }
 
@@ -151,7 +151,7 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
                return false;
        }
 
-       public Place lookup_place(string uri) {
+       public Place? lookup_place(string uri) {
                return this._keyrings.lookup(uri);
        }
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 36341f9..c084bf0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -93,17 +93,21 @@ src/seahorse-key-manager.c
 src/seahorse-key-manager.ui
 src/seahorse-main.c
 src/seahorse-sidebar.c
-ssh/seahorse-ssh-actions.c
+ssh/actions.vala
+ssh/algorithm.vala
+ssh/backend.vala
+ssh/deleter.vala
+ssh/errors.vala
+ssh/exporter.vala
+ssh/generate.vala
+ssh/key-data.vala
+ssh/key-properties.vala
+ssh/key.vala
+ssh/operation.vala
 ssh/seahorse-ssh-askpass.c
-ssh/seahorse-ssh-backend.c
-ssh/seahorse-ssh-deleter.c
-ssh/seahorse-ssh-exporter.c
-ssh/seahorse-ssh-generate.c
 ssh/seahorse-ssh-generate.ui
-ssh/seahorse-ssh-key-properties.c
 ssh/seahorse-ssh-key-properties.ui
-ssh/seahorse-ssh-key.c
-ssh/seahorse-ssh-operation.c
-ssh/seahorse-ssh-source.c
-ssh/seahorse-ssh-upload.c
 ssh/seahorse-ssh-upload.ui
+ssh/source.vala
+ssh/ssh.vala
+ssh/upload.vala
diff --git a/ssh/Makefile.am b/ssh/Makefile.am
index 18069a1..412a7a8 100644
--- a/ssh/Makefile.am
+++ b/ssh/Makefile.am
@@ -1,21 +1,60 @@
 
 noinst_LIBRARIES += libseahorse-ssh.a
 
+ssh_VALA = \
+       ssh/actions.vala \
+       ssh/algorithm.vala \
+       ssh/backend.vala \
+       ssh/deleter.vala \
+       ssh/errors.vala \
+       ssh/exporter.vala \
+       ssh/generate.vala \
+       ssh/key-data.vala \
+       ssh/key-properties.vala \
+       ssh/key.vala \
+       ssh/operation.vala \
+       ssh/source.vala \
+       ssh/ssh.vala \
+       ssh/upload.vala \
+       $(NULL)
+
+ssh_HEADER = ssh/seahorse-ssh.h
+
+ssh_C = $(ssh_VALA:.vala=.c)
+
 libseahorse_ssh_a_SOURCES = \
-       ssh/seahorse-ssh.h ssh/seahorse-ssh.c \
-       ssh/seahorse-ssh-actions.c ssh/seahorse-ssh-actions.h \
-       ssh/seahorse-ssh-backend.c ssh/seahorse-ssh-backend.h \
-       ssh/seahorse-ssh-deleter.c ssh/seahorse-ssh-deleter.h \
-       ssh/seahorse-ssh-dialogs.h \
-       ssh/seahorse-ssh-exporter.c ssh/seahorse-ssh-exporter.h \
-       ssh/seahorse-ssh-generate.c \
-       ssh/seahorse-ssh-key-data.c ssh/seahorse-ssh-key-data.h \
-       ssh/seahorse-ssh-key.c ssh/seahorse-ssh-key.h  \
-       ssh/seahorse-ssh-key-properties.c \
-       ssh/seahorse-ssh-source.c ssh/seahorse-ssh-source.h \
-       ssh/seahorse-ssh-operation.c ssh/seahorse-ssh-operation.h \
-       ssh/seahorse-ssh-upload.c
+       $(ssh_C) $(ssh_HEADER)
+
+libseahorse_ssh_a_CFLAGS = \
+       -include config.h -w \
+       -DSECRET_API_SUBJECT_TO_CHANGE
+
+if WITH_VALAC
+$(ssh_HEADER): $(ssh_VALA) $(common_VAPI)
+       $(V_VALAC) $(VALAC) $(VALA_FLAGS) -C --use-header --header=$(ssh_HEADER) \
+               --pkg gtk+-3.0 --pkg gcr-3 --pkg gcr-ui-3 --pkg libsecret-1 --pkg posix \
+               --directory=$(builddir)/ssh --basedir=$(dir $<) $^
+endif
 
+$(ssh_C): $(ssh_HEADER)
+
+ssh_BUILT = \
+       $(ssh_C) \
+       $(ssh_HEADER)
+
+BUILT_SOURCES += $(ssh_BUILT)
+
+EXTRA_DIST += \
+       $(ssh_BUILT) \
+       $(ssh_VALA) \
+       $(NULL)
+
+ui_files += \
+       ssh/seahorse-ssh-key-properties.ui \
+       ssh/seahorse-ssh-generate.ui \
+       ssh/seahorse-ssh-upload.ui
+
+# Askpass binary
 seahorselibexecbin_PROGRAMS += seahorse-ssh-askpass
 
 seahorse_ssh_askpass_SOURCES = \
@@ -24,9 +63,4 @@ seahorse_ssh_askpass_SOURCES = \
 seahorse_ssh_askpass_LDADD = \
        $(SEAHORSE_LIBS)
 
-ui_files += \
-       ssh/seahorse-ssh-key-properties.ui \
-       ssh/seahorse-ssh-generate.ui \
-       ssh/seahorse-ssh-upload.ui
-
 -include $(top_srcdir)/git.mk
diff --git a/ssh/actions.vala b/ssh/actions.vala
new file mode 100644
index 0000000..71770d6
--- /dev/null
+++ b/ssh/actions.vala
@@ -0,0 +1,78 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * 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/>.
+ */
+
+public class Seahorse.Ssh.Actions : Seahorse.Actions {
+
+    private const Gtk.ActionEntry KEYS_ACTIONS[] = {
+        { "remote-ssh-upload", null, N_("Configure Key for _Secure Shell…"), null,
+            N_("Send public Secure Shell key to another machine, and enable logins using that key."),
+            on_ssh_upload }
+    };
+
+    public const string UI_DEFINITION = """
+    <ui>
+      <menubar>
+        <placeholder name="RemoteMenu">
+          <menu name="Remote" action="remote-menu">
+            <menuitem action="remote-ssh-upload"/>
+          </menu>
+        </placeholder>
+      </menubar>
+      <popup name="ObjectPopup">
+        <menuitem action="remote-ssh-upload"/>
+      </popup>
+    </ui>""";
+
+    construct {
+        set_translation_domain(Config.GETTEXT_PACKAGE);
+        add_actions(KEYS_ACTIONS, this);
+        register_definition(UI_DEFINITION);
+    }
+
+    private Actions(string name) {
+        base(name);
+    }
+
+    private static Actions _instance = null;
+
+    public static unowned Actions instance() {
+        if (_instance == null) {
+            _instance = new Actions("SshKey");
+        }
+
+        return _instance;
+    }
+
+    private void on_ssh_upload (Gtk.Action action) {
+        List<Key> keys = new List<Key>();
+
+        if (this.catalog != null) {
+            foreach (GLib.Object el in this.catalog.get_selected_objects()) {
+                Key key = el as Key;
+                if (key != null)
+                    keys.prepend(key);
+            }
+        }
+
+        Upload.prompt(keys, Seahorse.Action.get_window(action));
+    }
+}
diff --git a/ssh/algorithm.vala b/ssh/algorithm.vala
new file mode 100644
index 0000000..c300d91
--- /dev/null
+++ b/ssh/algorithm.vala
@@ -0,0 +1,87 @@
+/*
+ * Seahorse
+ *
+ * Copyright (c) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Enumerates the possible algorithms in which an SSH key can be encrypted.
+ * This is also known as the 'type' of a key.
+ */
+public enum Seahorse.Ssh.Algorithm {
+    UNKNOWN,
+    RSA,
+    DSA;
+
+    /**
+     * Returns a (non-localized) string representation.
+     *
+     * @return A string representation, or null if UNKNOWN.
+     */
+    public string? to_string() {
+        switch(this) {
+            case UNKNOWN:
+                return "";
+            case RSA:
+                return "RSA";
+            case DSA:
+                return "DSA";
+            default:
+                assert_not_reached ();
+        };
+    }
+
+    /**
+     * Converts a string representation into a Algorithm.
+     *
+     * @return The algorithm (can be UNKNOWN).
+     */
+    public static Algorithm from_string(string? type) {
+        if (type == null || type == "")
+            return UNKNOWN;
+
+        string _type = type.strip().down();
+        switch (_type) {
+            case "rsa":
+                return RSA;
+            case "dsa":
+            case "dss":
+                return DSA;
+            default:
+                return UNKNOWN;
+        }
+    }
+
+    /**
+     * Tries to make an educated guess for the algorithm type.
+     * Basically it just checks if the given string contains the type.
+     *
+     * @return The guessed algorithm (can be UNKNOWN)
+     */
+    public static Algorithm guess_from_string(string? str) {
+        if (str == null || str == "")
+            return UNKNOWN;
+
+        string str_down = str.down();
+        if ("rsa" in str_down)
+            return RSA;
+
+        if (("dsa" in str_down) || ("dss" in str_down))
+            return DSA;
+
+        return UNKNOWN;
+    }
+}
diff --git a/ssh/backend.vala b/ssh/backend.vala
new file mode 100644
index 0000000..9380f0c
--- /dev/null
+++ b/ssh/backend.vala
@@ -0,0 +1,84 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * 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/>.
+ */
+
+public class Seahorse.Ssh.Backend : GLib.Object, Gcr.Collection, Seahorse.Backend {
+
+    private Source dot_ssh;
+
+    public string name { get { return SEAHORSE_SSH_NAME; } }
+    public string label { get { return _("Secure Shell"); } }
+    public string description { get { return _("Keys used to connect securely to other computers"); } }
+    public Gtk.ActionGroup? actions { owned get { return null; } }
+
+    private bool _loaded;
+    public bool loaded { get { return _loaded; } }
+
+    construct {
+        this.dot_ssh = new Source();
+        this.dot_ssh.load.begin(null, (obj, res) => {
+            try {
+                this._loaded = dot_ssh.load.end(res);
+            } catch (GLib.Error e) {
+                warning("Failed to initialize SSH backend: %s", e.message);
+            }
+        });
+    }
+
+    public Backend() {
+        register();
+    }
+
+    public static Backend? instance { get; internal set; default = null; }
+
+    public uint get_length() {
+        return 1;
+    }
+
+    public List<weak GLib.Object> get_objects() {
+        List<GLib.Object> list = new List<GLib.Object>();
+        list.append(this.dot_ssh);
+        return list;
+    }
+
+    public bool contains(GLib.Object object) {
+        Source? src = object as Source;
+
+        return (src != null) && (this.dot_ssh == src);
+    }
+
+    public Seahorse.Place? lookup_place(string uri) {
+        if (this.dot_ssh != null && this.dot_ssh.uri != null && this.dot_ssh.uri == uri)
+            return this.dot_ssh;
+
+        return null;
+    }
+
+    public static void initialize() {
+        if (Config.WITH_SSH) {
+            instance = new Backend();
+            Generate.register();
+        }
+    }
+
+    public Source get_dot_ssh() {
+        return this.dot_ssh;
+    }
+}
diff --git a/ssh/deleter.vala b/ssh/deleter.vala
new file mode 100644
index 0000000..22c5e1b
--- /dev/null
+++ b/ssh/deleter.vala
@@ -0,0 +1,123 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * 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: Stef Walter <stefw collabora co uk>
+ */
+
+public class Seahorse.Ssh.Deleter : Seahorse.Deleter {
+
+    private bool have_private;
+    private List<Key> keys;
+
+    public Deleter(Key key) {
+        if (!add_object(key))
+            assert_not_reached ();
+    }
+
+    public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+        uint num = this.keys.length();
+        assert(num != 1);
+
+        string confirm, prompt;
+        if (this.have_private) {
+
+            prompt = _("Are you sure you want to delete the secure shell key “%s”?")
+                         .printf(this.keys.data.label);
+            confirm = _("I understand that this secret key will be permanently deleted.");
+
+        } else if (num == 1) {
+            prompt = _("Are you sure you want to delete the secure shell key “%s”?")
+                         .printf(this.keys.data.label);
+            confirm = null;
+
+        } else {
+            prompt = ngettext("Are you sure you want to delete %u secure shell key?",
+                              "Are you sure you want to delete %u secure shell keys?",
+                              num).printf(num);
+            confirm = null;
+        }
+
+        Seahorse.DeleteDialog dialog = new Seahorse.DeleteDialog(parent, "%s", prompt);
+
+        if (confirm != null) {
+            dialog.check_label = confirm;
+            dialog.check_require = true;
+        }
+
+        return dialog;
+    }
+
+    public override unowned List<weak GLib.Object> get_objects () {
+        return this.keys;
+    }
+
+    public override bool add_object(GLib.Object object) {
+        Key key = object as Key;
+        if (this.have_private || key == null)
+            return false;
+
+        if (key.usage == Seahorse.Usage.PRIVATE_KEY) {
+            if (this.keys != null)
+                return false;
+            this.have_private = true;
+        }
+
+        this.keys.append(key);
+        return true;
+    }
+
+    public override async bool delete(GLib.Cancellable? cancellable) throws GLib.Error {
+        foreach (Key key in this.keys)
+            delete_key(key);
+
+        return true;
+    }
+
+    /**
+     * Deletes a given key.
+     *
+     * @param key The key that should be deleted.
+     */
+    public void delete_key(Key key) throws GLib.Error {
+        KeyData? keydata = key.key_data;
+        if (keydata == null)
+            throw new Error.GENERAL("Can't delete key with empty KeyData.");
+
+        if (keydata.partial) { // Just part of a file for this key
+            if (keydata.pubfile != null) // Take just that line out of the file
+                KeyData.filter_file(keydata.pubfile, null, keydata);
+
+        } else { // A full file for this key
+            if (keydata.pubfile != null) {
+                if (FileUtils.unlink(keydata.pubfile) == -1)
+                    throw new Error.GENERAL("Couldn't delete public key file '%s'".printf(keydata.pubfile));
+            }
+
+            if (keydata.privfile != null) {
+                if (FileUtils.unlink(keydata.privfile) == -1) {
+                    throw new Error.GENERAL("Couldn't delete private key file 
'%s'".printf(keydata.privfile));
+                }
+            }
+        }
+
+        Source source = (Source) key.place;
+        source.remove_object(key);
+    }
+}
diff --git a/ssh/errors.vala b/ssh/errors.vala
new file mode 100644
index 0000000..5f3301c
--- /dev/null
+++ b/ssh/errors.vala
@@ -0,0 +1,21 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+public errordomain Seahorse.Ssh.Error {
+    GENERAL
+}
diff --git a/ssh/exporter.vala b/ssh/exporter.vala
new file mode 100644
index 0000000..f2d70b4
--- /dev/null
+++ b/ssh/exporter.vala
@@ -0,0 +1,130 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+public class Seahorse.Ssh.Exporter : GLib.Object, Seahorse.Exporter {
+
+    private Key? key;
+    private List<Key> objects;
+
+    private bool _secret;
+    public bool secret {
+        get { return this._secret; }
+        set {
+            this._secret = value;
+            notify_property("filename");
+            notify_property("file-filter");
+            notify_property("content-type");
+        }
+    }
+
+    public string filename { owned get { return get_filename(); } }
+
+    public string content_type {
+        get {
+            return this.secret ? "application/x-pem-key" : "application/x-ssh-key";
+        }
+    }
+
+    public Gtk.FileFilter file_filter {
+        owned get {
+            Gtk.FileFilter filter = new Gtk.FileFilter();
+
+            if (this.secret) {
+                filter.set_name(_("Secret SSH keys"));
+                filter.add_mime_type("application/x-pem-key");
+                filter.add_pattern("id_*");
+            } else {
+                filter.set_name(_("Public SSH keys"));
+                filter.add_mime_type("application/x-ssh-key");
+                filter.add_pattern("*.pub");
+            }
+
+            return filter;
+        }
+    }
+
+    public Exporter(Key key, bool secret) {
+        this.secret = secret;
+
+        if (!add_object(key))
+            assert_not_reached ();
+    }
+
+    private string? get_filename() {
+        if (this.key == null)
+            return null;
+
+        KeyData? data = this.key.key_data;
+        if (data != null && !data.partial) {
+            string? location = null;
+            if (this.secret && data.privfile != null)
+                location = data.privfile;
+            else if (!this.secret && data.pubfile != null)
+                location = data.pubfile;
+            if (location != null)
+                return Path.get_basename(location);
+        }
+
+        string basename = this.key.nickname ?? _("SSH Key");
+
+        if (this.secret) {
+            string filename = "id_%s".printf(basename).strip();
+            filename.delimit(BAD_FILENAME_CHARS, '_');
+            return filename;
+        } else {
+            string filename = "%s.pub".printf(basename).strip();
+            filename.delimit(BAD_FILENAME_CHARS, '_');
+            return filename;
+        }
+    }
+
+    public unowned GLib.List<weak GLib.Object> get_objects() {
+        return this.objects;
+    }
+
+    public bool add_object(GLib.Object object) {
+        Key key = object as Key;
+        if (key == null)
+            return false;
+
+        if (this.secret && key.usage != Seahorse.Usage.PRIVATE_KEY)
+            return false;
+
+        this.key = key;
+        this.objects.append(this.key);
+        notify_property("filename");
+        return true;
+    }
+
+    public async uint8[] export(GLib.Cancellable? cancellable) throws GLib.Error {
+        KeyData keydata = this.key.key_data;
+
+        if (this.secret)
+            return ((Source) this.key.place).export_private(this.key).data;
+
+        if (keydata.pubfile == null)
+            throw new Error.GENERAL(_("No public key file is available for this key."));
+
+        assert(keydata.rawdata != null);
+        return "%s\n".printf(keydata.rawdata).data;
+    }
+}
diff --git a/ssh/generate.vala b/ssh/generate.vala
new file mode 100644
index 0000000..6cb26e8
--- /dev/null
+++ b/ssh/generate.vala
@@ -0,0 +1,155 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.Ssh.Generate : Gtk.Dialog {
+    public const int DEFAULT_DSA_SIZE = 1024;
+    public const int DEFAULT_RSA_SIZE = 2048;
+
+    private const Gtk.ActionEntry ACTION_ENTRIES[] = {
+        { "ssh-generate-key", Gcr.ICON_KEY_PAIR, N_ ("Secure Shell Key"), "",
+          N_("Used to access other computers (eg: via a terminal)"), on_ssh_generate_key }
+    };
+
+    private Source source;
+
+    private Gtk.SpinButton bits_spin_button;
+    private Gtk.Entry email_entry;
+    private Gtk.ComboBoxText algorithm_combo_box;
+    private Gtk.Button create_with_setup_button;
+    private Gtk.Button create_no_setup_button;
+
+    public Generate(Source src, Gtk.Window parent) {
+        GLib.Object(border_width: 5,
+                    title: _("New Secure Shell Key"),
+                    resizable: false,
+                    modal: true,
+                    transient_for: parent);
+        this.source = src;
+
+        load_ui();
+
+        // on_change() gets called, bits entry is setup
+        algorithm_combo_box.set_active(0);
+    }
+
+    private static void on_ssh_generate_key(Gtk.Action action) {
+        Generate generate_dialog = new Generate(Backend.instance.get_dot_ssh(),
+                                                Action.get_window(action));
+        generate_dialog.show();
+    }
+
+    public static void register() {
+        Gtk.ActionGroup actions = new Gtk.ActionGroup("ssh-generate");
+
+        actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+        actions.add_actions(ACTION_ENTRIES, null);
+
+        // Register this as a generator
+        Seahorse.Registry.register_object(actions, "generator");
+    }
+
+    // FIXME: normally we would do this using GtkTemplate, but this is quite hard with the current build 
setup
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/seahorse-ssh-generate.ui";
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("ssh-generate");
+        ((Gtk.Container)this.get_content_area()).add(content);
+        Gtk.Widget actions = (Gtk.Widget) builder.get_object("action_area");
+        ((Gtk.Container)this.get_action_area()).add(actions);
+
+        this.bits_spin_button = (Gtk.SpinButton) builder.get_object("bits-spin-button");
+        this.email_entry = (Gtk.Entry) builder.get_object("email-entry");
+        this.algorithm_combo_box = (Gtk.ComboBoxText) builder.get_object("algorithm-combo-box");
+        this.create_no_setup_button = (Gtk.Button) builder.get_object("create-no-setup");
+        this.create_with_setup_button = (Gtk.Button) builder.get_object("create-with-setup");
+
+        // Signals
+        this.algorithm_combo_box.changed.connect(on_change);
+        this.create_no_setup_button.clicked.connect((b) => create_key(false));
+        this.create_with_setup_button.clicked.connect((b) => create_key(true));
+    }
+
+    private void on_change(Gtk.ComboBox combo) {
+        string t = algorithm_combo_box.get_active_text();
+        if (Algorithm.from_string(t) == Algorithm.DSA) {
+           this.bits_spin_button.set_value(DEFAULT_DSA_SIZE);
+           this.bits_spin_button.sensitive = false;
+        } else {
+           this.bits_spin_button.set_value(DEFAULT_RSA_SIZE);
+           this.bits_spin_button.sensitive = true;
+        }
+    }
+
+    private void create_key(bool upload) {
+        // The email address
+        string email = this.email_entry.text;
+
+        // The algorithm
+        string t = this.algorithm_combo_box.get_active_text();
+        Algorithm type = Algorithm.from_string(t);
+        assert(type != Algorithm.UNKNOWN);
+
+        // The number of bits
+        uint bits = this.bits_spin_button.get_value_as_int();
+        if (bits < 512 || bits > 8192) {
+            message("Invalid key size: %s defaulting to 2048", t);
+            bits = 2048;
+        }
+
+        // The filename
+        string filename = this.source.new_filename_for_algorithm(type);
+
+        // We start creation
+        Cancellable cancellable = new Cancellable();
+        GenerateOperation op = new GenerateOperation();
+        op.generate_async.begin(filename, email, type, bits, cancellable, (obj, res) => {
+            try {
+                op.generate_async.end(res);
+
+                // The result of the operation is the key we generated
+                source.add_key_from_filename.begin(filename, (obj, res) => {
+                    try {
+                        Key key = source.add_key_from_filename.end(res);
+
+                        if (upload && key != null) {
+                            List<Key> keys = new List<Key>();
+                            keys.append(key);
+                            Upload.prompt(keys, null);
+                        }
+                    } catch (GLib.Error e) {
+                        Seahorse.Util.show_error(null, _("Couldn’t load newly generated Secure Shell key"), 
e.message);
+                    }
+                });
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(null, _("Couldn’t generate Secure Shell key"), e.message);
+            }
+        });
+        Seahorse.Progress.show(cancellable, _("Creating Secure Shell Key"), false);
+
+        destroy();
+    }
+
+}
diff --git a/ssh/key-data.vala b/ssh/key-data.vala
new file mode 100644
index 0000000..d26621b
--- /dev/null
+++ b/ssh/key-data.vala
@@ -0,0 +1,255 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.Ssh.KeyData : GLib.Object {
+
+    /* Used by callers */
+    public string privfile { get; set; }        /* The secret key file */
+    public string pubfile { get; set; }         /* The public key file */
+    public bool partial { get; set; }       /* Only part of the public key file */
+    public bool authorized { get; set; default = false; }    /* Is in authorized_keys */
+
+    // These props are filled in by the parser
+    public string rawdata { get; internal set; }         /* The raw data of the public key */
+    public string? comment { get; internal set; }         /* The comment for the public key */
+    public string? fingerprint { get; internal set; }     /* The full fingerprint hash */
+    public uint length { get; internal set; }           /* Number of bits */
+    public Algorithm algo { get; internal set; }             /* Key algorithm */
+
+    public bool is_valid() {
+        return this.fingerprint != null;
+    }
+
+    /**
+     * Checks whether this key and another (in the form of a string) match.
+     */
+    public bool match(string line) {
+        if (!is_valid())
+            return false;
+
+        try {
+            KeyData other = parse_line(line);
+            return (other.fingerprint != null) && (this.fingerprint == other.fingerprint);
+        } catch (GLib.Error e) {
+            warning(e.message);
+        }
+
+        return false;
+    }
+
+    public static KeyData parse_line(string? line) throws GLib.Error {
+        if (line == null || line.strip() == "")
+            throw new Error.GENERAL("Can't parse key from empty line.");
+
+        string no_leading = line.chug();
+        KeyData result = new KeyData();
+        result.rawdata = no_leading;
+
+        // Get the type
+        string[] type_rest = no_leading.split(" ", 2);
+        if (type_rest.length != 2)
+            throw new Error.GENERAL("Can't distinguish type from data (space missing).");
+
+        string type = type_rest[0];
+        if (type == "")
+            throw new Error.GENERAL("Key doesn't have a type.");
+
+        result.algo = Algorithm.guess_from_string(type);
+        if (result.algo == Algorithm.UNKNOWN)
+            throw new Error.GENERAL("Key doesn't have a valid type.");
+
+        // Prepare for decoding
+        string rest = type_rest[1];
+        if (rest == "")
+            throw new Error.GENERAL("Key doesn't have any data.");
+        string[] data_comment = rest.split(" ", 2);
+
+        // Decode it, and parse binary stuff
+        uchar[] bytes = Base64.decode(data_comment[0].strip());
+        result.fingerprint = parse_key_blob(bytes);
+
+        // The number of bits
+        result.length = calc_bits(result.algo, bytes.length);
+
+        // And the rest is the comment
+        if (data_comment.length == 2) {
+            string comment = data_comment[1];
+
+            if (!comment.validate()) // If not utf8-valid, assume latin1
+                result.comment = convert(comment, comment.length, "UTF-8", "ISO-8859-1");
+            else
+                result.comment = comment;
+        }
+
+        return result;
+    }
+
+    internal static string parse_key_blob (uchar[] bytes) throws GLib.Error {
+        string digest = Checksum.compute_for_data(ChecksumType.MD5, bytes);
+        if (digest == null)
+            throw new Error.GENERAL("Can't calculate fingerprint from key.");
+
+        StringBuilder fingerprint = new StringBuilder.sized((digest.length * 3) / 2);
+        for (size_t i = 0; i < digest.length; i += 2) {
+            if (i > 0)
+                fingerprint.append_c(':');
+            fingerprint.append(digest.substring((long) i, 2));
+        }
+
+        return fingerprint.str;
+    }
+
+    internal static uint calc_bits (Algorithm algo, uint len) {
+        // To keep us from having to parse a BIGNUM and link to openssl, these
+        // are from the hip guesses at the bits of a key based on the size of
+        // the binary blob in the public key.
+        switch (algo) {
+            case Algorithm.RSA:
+                // Seems accurate to nearest 8 bits
+                return ((len - 21) * 8);
+
+            case Algorithm.DSA:
+                // DSA keys seem to only work at 'bits % 64 == 0' boundaries
+                uint n = ((len - 50) * 8) / 3;
+                return ((n / 64) + (((n % 64) > 32) ? 1 : 0)) * 64; // round to 64
+
+            default:
+                return 0;
+        }
+    }
+
+    // Adds and/or removes a keydata to a file (if added is already there, it is added at the back of the 
file).
+    public static void filter_file(string filename, KeyData? add, KeyData? remove = null) throws GLib.Error {
+        // By default filter out the one we're adding
+        if (remove == null)
+            remove = add;
+
+        string contents;
+        FileUtils.get_contents(filename, out contents);
+
+        StringBuilder results = new StringBuilder();
+
+        // Load each line
+        bool first = true;
+        string[] lines = (contents ?? "").split("\n");
+        foreach (string line in lines) {
+            if (remove != null && remove.match(line))
+                continue;
+
+            if (!first)
+                results.append_c('\n');
+            first = false;
+            results.append(line);
+        }
+
+        // Add any that need adding
+        if (add != null) {
+            if (!first)
+                results.append_c('\n');
+            results.append(add.rawdata);
+        }
+
+        FileUtils.set_contents(filename, results.str);
+    }
+
+    public string? get_location() {
+        return this.privfile != null ? this.privfile : this.pubfile;
+    }
+}
+
+/**
+ * Represents the data in a private key.
+ */
+public class Seahorse.Ssh.SecData : GLib.Object {
+    public const string SSH_KEY_SECRET_SIG = "# SSH PRIVATE KEY: ";
+    public const string SSH_PRIVATE_BEGIN = "-----BEGIN ";
+    public const string SSH_PRIVATE_END = "-----END ";
+
+    /**
+     * Everything excluding the comment
+     */
+    public string rawdata { get; internal set; }
+
+    public string? comment { get; internal set; }
+
+    public Algorithm algo { get; internal set; }
+
+    public static bool contains_private_key(string data) {
+        return (SSH_KEY_SECRET_SIG in data) || (SSH_PRIVATE_BEGIN in data);
+    }
+
+    /**
+     * Finds the first occurence of a private key in the given string and parses it if found.
+     * NOTE: after parsing, it will *remove* the data with the private key from the string.
+     *
+     * @param data The data that contains a private key.
+     */
+    public static SecData parse_data(StringBuilder data) throws GLib.Error {
+        SecData secdata = new SecData();
+
+        // Get the comment
+        if (data.str.has_prefix(SSH_KEY_SECRET_SIG)) {
+            string comment = data.str.split("\n", 2)[0];
+            secdata.comment = comment.substring(SSH_KEY_SECRET_SIG.length).strip();
+        }
+
+        // First get our raw data (if there is none, don't bother)
+        string rawdata = parse_lines_block(data, SSH_PRIVATE_BEGIN, SSH_PRIVATE_END);
+        if (rawdata == null || rawdata == "")
+            throw new Error.GENERAL("Private key contains no data.");
+
+        secdata.rawdata = rawdata;
+
+        // Guess at the algorithm type
+        secdata.algo = Algorithm.guess_from_string(rawdata);
+
+        return secdata;
+    }
+
+    /**
+     * Takes everything between the start and end pattern and returns it.
+     * NOTE: The string (if found will) be removed from the argument.
+     */
+    private static string parse_lines_block(StringBuilder data, string start, string end) {
+        StringBuilder result = new StringBuilder();
+
+        bool start_found = false;
+        string[] lines = data.str.split("\n");
+        foreach (string line in lines) {
+            // Look for the beginning
+            if (!start_found) {
+                if (start in line) {
+                    result.append_printf("%s\n", line);
+                    result.erase(0, line.length + 1);
+                    start_found = true;
+                    continue;
+                }
+            } else {
+                // Look for the end
+                result.append_printf("%s\n", line);
+                result.erase(0, line.length + 1);
+                if (end in line)
+                    break;
+            }
+        }
+
+        return result.str;
+    }
+}
diff --git a/ssh/key-properties.vala b/ssh/key-properties.vala
new file mode 100644
index 0000000..4632cf2
--- /dev/null
+++ b/ssh/key-properties.vala
@@ -0,0 +1,198 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
+
+    private Key key;
+
+    // Used to make sure we don't start calling command unnecessarily
+    private bool updating_ui = false;
+
+    private Gtk.Image key_image;
+    private Gtk.Entry comment_entry;
+    private Gtk.Label id_label;
+    private Gtk.Label trust_message;
+    private Gtk.ToggleButton trust_check;
+
+    private Gtk.Button passphrase_button;
+    private Gtk.Button export_button;
+
+    private Gtk.Label fingerprint_label;
+    private Gtk.Label algo_label;
+    private Gtk.Label location_label;
+    private Gtk.Label strength_label;
+
+    public KeyProperties(Key key, Gtk.Window parent) {
+        GLib.Object(border_width: 5,
+                    title: _("Key Properties"),
+                    resizable: false,
+                    transient_for: parent);
+        this.key = key;
+
+        load_ui();
+        update_ui();
+
+        // A public key only
+        if (key.usage != Seahorse.Usage.PRIVATE_KEY) {
+            passphrase_button.visible = false;
+            export_button.visible = false;
+        }
+
+        this.key.notify.connect(() => update_ui());
+    }
+
+    // FIXME: normally we would do this using GtkTemplate, but this is quite hard with the current build 
setup
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/seahorse-ssh-key-properties.ui";
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("ssh-key-properties");
+        ((Gtk.Container)this.get_content_area()).add(content);
+
+        this.key_image = (Gtk.Image) builder.get_object("key-image");
+        this.comment_entry = (Gtk.Entry) builder.get_object("comment-entry");
+        this.id_label = (Gtk.Label) builder.get_object("id-label");
+        this.trust_message = (Gtk.Label) builder.get_object("trust-message");
+        this.trust_check = (Gtk.ToggleButton) builder.get_object("trust-check");
+        this.passphrase_button = (Gtk.Button) builder.get_object("passphrase-button");
+        this.export_button = (Gtk.Button) builder.get_object("export-button");
+        this.fingerprint_label = (Gtk.Label) builder.get_object("fingerprint-label");
+        this.algo_label = (Gtk.Label) builder.get_object("algo-label");
+        this.location_label = (Gtk.Label) builder.get_object("location-label");
+        this.strength_label = (Gtk.Label) builder.get_object("strength-label");
+
+        // Signals
+        this.comment_entry.activate.connect(on_ssh_comment_activate);
+        this.comment_entry.focus_out_event.connect(on_ssh_comment_focus_out);
+        this.trust_check.toggled.connect(on_ssh_trust_toggled);
+        this.passphrase_button.clicked.connect(on_ssh_passphrase_button_clicked);
+        this.export_button.clicked.connect(on_ssh_export_button_clicked);
+    }
+
+    private void update_ui() {
+        this.updating_ui = true;
+
+        // Image
+        this.key_image.set_from_icon_name(Seahorse.ICON_KEY_SSH, Gtk.IconSize.DIALOG);
+        // Name and title
+        this.comment_entry.text = this.key.label;
+        this.title = this.key.label;
+        // Key id
+        this.id_label.label = this.key.identifier;
+        // Put in message
+        string template = this.trust_message.label;
+        this.trust_message.set_markup(template.printf(Environment.get_user_name()));
+
+        // Setup the check
+        this.trust_check.active = (this.key.trust >= Seahorse.Validity.FULL);
+
+        this.fingerprint_label.label = this.key.fingerprint;
+        this.algo_label.label = this.key.get_algo().to_string() ?? _("Unknown type");
+        this.location_label.label = this.key.get_location();
+        this.strength_label.label = "%u".printf(this.key.get_strength());
+
+        this.updating_ui = false;
+    }
+
+    public override void response(int response) {
+        destroy();
+    }
+
+    public void on_ssh_comment_activate(Gtk.Entry entry) {
+        // Make sure not the same
+        if (key.key_data.comment != null && entry.text == key.key_data.comment)
+            return;
+
+        entry.sensitive = false;
+
+        RenameOperation op = new RenameOperation();
+        op.rename_async.begin(key, entry.text, this, (obj, res) => {
+            try {
+                op.rename_async.end(res);
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(this, _("Couldn’t rename key."), e.message);
+                entry.text = key.key_data.comment ?? "";
+            }
+
+            entry.sensitive = true;
+        });
+    }
+
+    private bool on_ssh_comment_focus_out(Gtk.Widget widget, Gdk.EventFocus event) {
+        on_ssh_comment_activate((Gtk.Entry) widget);
+        return false;
+    }
+
+    private void on_ssh_trust_toggled(Gtk.ToggleButton button) {
+        if (updating_ui)
+            return;
+
+        button.sensitive = false;
+
+        Source source = (Source) key.place;
+        source.authorize_async.begin(key, button.active, (obj, res) => {
+            try {
+                source.authorize_async.end(res);
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(this, _("Couldn’t change authorization for key."), e.message);
+            }
+
+            button.sensitive = true;
+        });
+    }
+
+    private void on_ssh_passphrase_button_clicked (Gtk.Button button) {
+        button.sensitive = false;
+
+        ChangePassphraseOperation op = new ChangePassphraseOperation();
+        op.change_passphrase_async.begin(key, null, (obj, res) => {
+            try {
+                op.change_passphrase_async.end(res);
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(this, _("Couldn't change passphrase for key."), e.message);
+            }
+
+            button.sensitive = true;
+        });
+    }
+
+    private void on_ssh_export_button_clicked (Gtk.Widget widget) {
+        List<Exporter> exporters = new List<Exporter>();
+        exporters.append(new Exporter(key, true));
+
+        Seahorse.Exporter exporter;
+        string directory = null;
+        File file;
+        if (Seahorse.Exportable.prompt(exporters, this, ref directory, out file, out exporter)) {
+            exporter.export_to_file.begin(file, true, null, (obj, res) => {
+                try {
+                    exporter.export_to_file.end(res);
+                } catch (GLib.Error e) {
+                    Seahorse.Util.show_error(this, _("Couldn’t export key"), e.message);
+                }
+            });
+        }
+    }
+}
diff --git a/ssh/key.vala b/ssh/key.vala
new file mode 100644
index 0000000..2a49854
--- /dev/null
+++ b/ssh/key.vala
@@ -0,0 +1,340 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Represents an SSH key, consisting of a public/private pair.
+ */
+public class Seahorse.Ssh.Key : Seahorse.Object, Seahorse.Exportable, Seahorse.Deletable, Seahorse.Viewable {
+    public const int SSH_IDENTIFIER_SIZE = 8;
+
+    private KeyData? _keydata;
+    public KeyData? key_data {
+        get { return _keydata; }
+        set { this._keydata = value; changed_key(); }
+    }
+
+    /**
+     * Unique fingerprint for this key
+     */
+    public string? fingerprint {
+        get { return (this.key_data != null) ? this.key_data.fingerprint : null; }
+    }
+
+    /**
+     * Description
+     */
+    public string description {
+        get { return this.usage == Seahorse.Usage.PRIVATE_KEY ? _("Personal SSH key") : _("SSH key"); }
+    }
+
+    /**
+     * Validity of this key
+     */
+    public Seahorse.Validity validity {
+        get {
+            if (this.key_data != null && this.key_data.privfile != null)
+                return Seahorse.Validity.ULTIMATE;
+            return 0;
+        }
+    }
+
+    /**
+     * Trust in this key
+     */
+    public Seahorse.Validity trust {
+        get {
+            if (this.key_data != null && this.key_data.authorized)
+                return Seahorse.Validity.FULL;
+            return 0;
+        }
+    }
+
+    /**
+     * Date this key expires on (0 if never)
+     */
+    public ulong expires {
+        get { return 0; }
+    }
+
+    /**
+     * The length of this key
+     */
+    public uint length {
+        get { return this.key_data != null ? this.key_data.length : 0; }
+    }
+
+    public bool deletable {
+        get { return Seahorse.Flags.DELETABLE in this.flags; }
+    }
+
+    public bool exportable {
+        get { return Seahorse.Flags.EXPORTABLE in this.flags; }
+    }
+
+    public Key(Source source, KeyData key_data) {
+        GLib.Object(place: source, key_data: key_data);
+    }
+
+    private string? parse_first_word(string line) {
+        string PARSE_CHARS = "\t \n@;,.\\?()[]{}+/";
+
+        string[] words = line.split_set(PARSE_CHARS, 2);
+        return (words.length == 2)? words[0] : null;
+    }
+
+    private void changed_key() {
+        string display = null;
+        string simple = null;
+
+        // TODO - this is a stupid hack until we can set the properties normally
+        Value labelValue = Value(typeof(string));
+        Value actionsValue = Value(typeof(Gtk.ActionGroup));
+        Value iconValue = Value(typeof(Icon?));
+        Value usageValue = Value(typeof(Seahorse.Usage));
+        Value nicknameValue = Value(typeof(string));
+        Value flagsValue = Value(typeof(uint));
+        Value markupValue = Value(typeof(string));
+        Value identifierValue = Value(typeof(string));
+
+        if (this.key_data != null) {
+            // Try to make display and simple names
+            if (this.key_data.comment != null) {
+                display = this.key_data.comment;
+                simple = parse_first_word(this.key_data.comment);
+
+            // No names when not even the fingerpint loaded
+            } else if (this.key_data.fingerprint == null) {
+                display = _("(Unreadable Secure Shell Key)");
+            // No comment, but loaded
+            } else {
+                display = _("Secure Shell Key");
+            }
+
+            if (simple == null)
+                simple = _("Secure Shell Key");
+        }
+
+        if (this.key_data == null || this.key_data.fingerprint == null) {
+            labelValue.set_string("");
+            set_property("label", labelValue);
+            iconValue.set_object(null);
+            set_property("icon", iconValue);
+            usageValue.set_enum(Seahorse.Usage.NONE);
+            set_property("usage", usageValue);
+            labelValue.set_string("");
+            set_property("nickname", nicknameValue);
+            usageValue.set_uint((uint) Seahorse.Flags.DISABLED);
+            set_property("object-flags", flagsValue);
+            return;
+        }
+
+        Seahorse.Usage usage;
+        Seahorse.Flags flags = Seahorse.Flags.EXPORTABLE | Seahorse.Flags.DELETABLE;
+        Icon icon;
+        if (this.key_data.privfile != null) {
+            usage = Seahorse.Usage.PRIVATE_KEY;
+            flags |= Seahorse.Flags.PERSONAL | Seahorse.Flags.TRUSTED;
+            icon = new ThemedIcon(Gcr.ICON_KEY_PAIR);
+        } else {
+            flags = 0;
+            usage = Seahorse.Usage.PUBLIC_KEY;
+            icon = new ThemedIcon(Gcr.ICON_KEY);
+        }
+
+        string filename = Path.get_basename(this.key_data.privfile ?? this.key_data.pubfile);
+
+        string markup = Markup.printf_escaped("%s<span size='small' rise='0' 
foreground='#555555'>\n%s</span>",
+                                              display, filename);
+
+        string identifier = calc_identifier(this.key_data.fingerprint);
+        Actions actions = Actions.instance();
+
+        if (this.key_data.authorized)
+            flags |= Seahorse.Flags.TRUSTED;
+
+        actionsValue.set_object(actions);
+        set_property("actions", actionsValue);
+        markupValue.set_string(markup);
+        set_property("markup", markupValue);
+        labelValue.set_string(display);
+        set_property("label", labelValue);
+        iconValue.set_object(icon);
+        set_property("icon", iconValue);
+        usageValue.set_enum(usage);
+        set_property("usage", usageValue);
+        nicknameValue.set_string(simple);
+        set_property("nickname", nicknameValue);
+        identifierValue.set_string(identifier);
+        set_property("identifier", identifierValue);
+        flagsValue.set_uint((uint) flags);
+        set_property("object-flags", flagsValue);
+    }
+
+    public void refresh() {
+        // TODO: Need to work on key refreshing
+    }
+
+    public GLib.List<Seahorse.Exporter> create_exporters(ExporterType type) {
+        List<Seahorse.Exporter> exporters = new List<Seahorse.Exporter>();
+        exporters.append(new Exporter(this, false));
+        return exporters;
+    }
+
+    public Seahorse.Deleter create_deleter() {
+        return new Deleter(this);
+    }
+
+    public Gtk.Window? create_viewer(Gtk.Window? parent) {
+        KeyProperties properties_dialog = new KeyProperties(this, parent);
+        properties_dialog.show();
+        return properties_dialog;
+    }
+
+    public Algorithm get_algo() {
+        return this.key_data.algo;
+    }
+
+    public string? get_location() {
+        if (this.key_data == null)
+            return null;
+        return this.key_data.get_location();
+    }
+
+    public uint get_strength() {
+        return (this.key_data != null)? this.key_data.length : 0;
+    }
+
+    /**
+     * Creates a valid identifier for an SSH key from a given string.
+     *
+     * @return A valid identifier, or null if the result is too short.
+     */
+    public static string? calc_identifier(string id) {
+        // Strip out all non-alphanumeric chars and limit length to SSH_ID_SIZE
+        try {
+            Regex regex = new Regex("[^a-zA-Z0-9]");
+            string result = regex.replace(id, id.length, 0, "");
+
+            if (result.length >= SSH_IDENTIFIER_SIZE)
+                return result.substring(0, result.length);
+        } catch (RegexError e) {
+            warning("Couldn't create regex for calc_identifier. Message: %s".printf(e.message));
+        }
+
+        return null;
+    }
+
+    /**
+     * Sometimes keys loaded later on have more information (e.g. keys loaded
+     * from authorized_keys), so propagate that up to the previously loaded key.
+     */
+    public void merge_keydata(KeyData keydata) {
+        if (!this.key_data.authorized && keydata.authorized) {
+            this.key_data.authorized = true;
+
+            // Let the key know something's changed
+            this.key_data = this.key_data;
+        }
+    }
+
+    /**
+     * Parses a string into public/private keys.
+     *
+     * @param data The string that needs to be parsed.
+     * @param pub_handler Will be called anytime a public key has been parsed.
+     *                    If null, nothing will be done if a public key is parsed.
+     * @param priv_handler Will be called anytime a private key has been parsed.
+     *                     If null, nothing will be done if a private key is parsed.
+     * @param cancellable Can be used to cancel the parsing.
+     */
+    public static async void parse(string data,
+                                   PubParsedHandler? pub_handler,
+                                   PrivParsedHandler? priv_handler = null,
+                                   Cancellable? cancellable = null) throws GLib.Error {
+        if (data == null || data == "")
+            return;
+
+        StringBuilder toParse = new StringBuilder(data.chug());
+        while (toParse.str.length > 0) {
+            // First of all, check for a private key, as it can span several lines
+            if (SecData.contains_private_key(toParse.str)) {
+                try {
+                    SecData secdata = SecData.parse_data(toParse);
+                    if (priv_handler != null)
+                        priv_handler(secdata);
+                    continue;
+                } catch (GLib.Error e) {
+                    warning(e.message);
+                }
+            }
+
+            // We're sure we'll have at least 1 element
+            string[] lines = toParse.str.split("\n", 2);
+            string line = lines[0];
+            toParse.erase(0, line.length);
+            if (lines.length == 2) // There was a \n, so don't forget to erase it as well
+                toParse.erase(0, 1);
+
+            // Comments and empty lines, not a parse error, but no data
+            if (line.strip() == "" || line.has_prefix("#"))
+                continue;
+
+            // See if we have a public key
+            try {
+                KeyData keydata = KeyData.parse_line(line);
+                if (pub_handler != null)
+                    pub_handler(keydata);
+            } catch (GLib.Error e) {
+                warning(e.message);
+            }
+        }
+    }
+
+    /**
+     * Parses the contents of the given file into public/private keys.
+     *
+     * @param data The file that will be parsed.
+     * @param pub_handler Will be called anytime a public key has been parsed.
+     *                    If null, nothing will be done if a public key is parsed.
+     * @param priv_handler Will be called anytime a private key has been parsed.
+     *                     If null, nothing will be done if a private key is parsed.
+     * @param cancellable Can be used to cancel the parsing.
+     */
+    public static async void parse_file(string filename,
+                                        PubParsedHandler? pub_handler,
+                                        PrivParsedHandler? priv_handler = null,
+                                        Cancellable? cancellable = null) throws GLib.Error {
+        string contents;
+        FileUtils.get_contents(filename, out contents);
+
+        yield parse(contents, pub_handler, priv_handler, cancellable);
+    }
+
+    /**
+     * Takes care of the public key that has been found in a string while parsing.
+     */
+    public delegate bool PubParsedHandler(KeyData data);
+
+    /**
+     * Takes care of the private key that has been found in a string while parsing.
+     */
+    public delegate bool PrivParsedHandler(SecData data);
+}
diff --git a/ssh/operation.vala b/ssh/operation.vala
new file mode 100644
index 0000000..174e91d
--- /dev/null
+++ b/ssh/operation.vala
@@ -0,0 +1,397 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+namespace Seahorse.Ssh {
+
+/**
+ * Wraps a command-line executable in its own async method.
+ *
+ * The basic idea is that we don't want to link to the OpenSSL/LibreSSL libraries.
+ * So, create a subclass instead and do whatever you want with the output.
+ */
+public abstract class Operation : GLib.Object {
+
+    private string command_name;
+
+    private StringBuilder _std_out = new StringBuilder();
+    protected StringBuilder std_out { get { return _std_out; } }
+
+    private StringBuilder _std_err = new StringBuilder();
+    protected StringBuilder std_err { get { return _std_err; } }
+
+    private Pid pid;
+
+    // These are all fields that will be used in case the user will be prompted.
+    protected string? prompt_title;
+    protected string? prompt_message;
+    protected string? prompt_argument;
+    protected ulong prompt_transient_for;
+
+    /**
+     * Calls a command and returns the output.
+     *
+     * @param command The command that should be launched.
+     * @param input The standard input for the command, or null if none expected.
+     * @param cancellable Can be used if you want to cancel. The process will be killed.
+     * @return The output of the command.
+     */
+    protected async void operation_async(string command,
+                                         string? input,
+                                         Cancellable? cancellable) throws GLib.Error {
+        if (command == null || command == "")
+            return;
+
+        string[] args;
+        try {
+            Shell.parse_argv(command, out args);
+            this.command_name = args[0];
+        } catch (GLib.Error e) {
+            critical("Couldn't parse SSH command line %s.", command);
+            throw e;
+        }
+
+        debug("SSHOP: Executing SSH command: %s", command);
+
+        // And off we go to run the program
+        int? fin, fout, ferr;
+        if (input != null)
+            fin = null;
+        Process.spawn_async_with_pipes(null, args, null,
+                                       SpawnFlags.DO_NOT_REAP_CHILD | SpawnFlags.LEAVE_DESCRIPTORS_OPEN,
+                                       on_spawn_setup_child, out this.pid, out fin, out fout, out ferr);
+
+        ulong cancelled_sig = 0;
+        if (cancellable != null)
+            cancelled_sig = cancellable.connect(() =>  { Posix.kill(this.pid, Posix.SIGTERM); });
+
+        // Copy the input for later writing
+        if (input != null) {
+            StringBuilder std_in = new StringBuilder(input);
+            debug("Sending input to '%s': '%s'", this.command_name, std_in.str);
+            create_io_channel(std_in, fin, IOCondition.OUT,
+                              (io, cond) => on_io_ssh_write(io, cond, std_in));
+        }
+
+        // Make all the proper IO Channels for the output/error
+        create_io_channel(this.std_out, fout, IOCondition.IN,
+                          (io, cond) => on_io_ssh_read(io, cond, this.std_out));
+        create_io_channel(this.std_err, ferr, IOCondition.IN,
+                          (io, cond) => on_io_ssh_read(io, cond, this.std_err));
+
+        // Process watch
+        watch_ssh_process(cancellable, cancelled_sig);
+    }
+
+    private void create_io_channel(StringBuilder data,
+                                   int handle,
+                                   IOCondition io_cond,
+                                   IOFunc io_func) throws GLib.Error {
+        Posix.fcntl(handle, Posix.F_SETFL, Posix.O_NONBLOCK | Posix.fcntl(handle, Posix.F_GETFL));
+        IOChannel io_channel = new IOChannel.unix_new(handle);
+        io_channel.set_encoding(null);
+        io_channel.set_close_on_unref(true);
+        io_channel.add_watch(io_cond, io_func);
+    }
+
+    private void watch_ssh_process(Cancellable? cancellable, ulong cancelled_sig) throws GLib.Error {
+        ChildWatch.add_full(Priority.DEFAULT, this.pid, (pid, status) => {
+            debug("Process '%s' done.", this.command_name);
+
+            // FIXME This is the wrong place to catch that error really
+            try {
+                Process.check_exit_status(status);
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(null, this.prompt_title, this.std_err.str);
+            }
+            cancellable.disconnect(cancelled_sig);
+            Process.close_pid(pid);
+        });
+    }
+
+    /**
+     * Escapes a command-line argument so it can be safely passed on.
+     *
+     * @param arg The argument that should be escaped.
+     * @return The escaped argument.
+     */
+    protected string escape_shell_arg (string arg) {
+        string escaped = arg.replace("'", "\'");
+        return "\'%s\'".printf(escaped);
+    }
+
+    private bool on_io_ssh_read(IOChannel source, IOCondition condition, StringBuilder data) {
+        try {
+            IOStatus status = IOStatus.NORMAL;
+            while (status != IOStatus.EOF) {
+                string buf;
+                status = source.read_line(out buf, null, null);
+
+                if (status == IOStatus.NORMAL && buf != null) {
+                    data.append(buf);
+                    debug("%s", data.str.substring(buf.length));
+                }
+            }
+        } catch (GLib.Error e) {
+            critical("Couldn't read output of SSH command. Error: %s", e.message);
+            Posix.kill(this.pid, Posix.SIGTERM);
+            return false;
+        }
+
+        return true;
+    }
+
+    private bool on_io_ssh_write(IOChannel source, IOCondition condition, StringBuilder input) {
+        debug("SSHOP: SSH ready for input");
+        try {
+            size_t written = 0;
+            IOStatus status = source.write_chars(input.str.to_utf8(), out written);
+            if (status != IOStatus.AGAIN) {
+                debug("SSHOP: Wrote %u bytes to SSH", (uint) written);
+                input.erase(0, (ssize_t) written);
+            }
+        } catch (GLib.Error e) {
+            critical("Couldn't write to STDIN of SSH command. Error: %s", e.message);
+            Posix.kill(this.pid, Posix.SIGTERM);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void on_spawn_setup_child() {
+        // No terminal for this process
+        Posix.setsid();
+
+        Environment.set_variable("SSH_ASKPASS", "%sseahorse-ssh-askpass".printf(Config.EXECDIR), false);
+
+        // We do screen scraping so we need locale C
+        if (Environment.get_variable("LC_ALL") != null)
+            Environment.set_variable("LC_ALL", "C", true);
+        Environment.set_variable("LANG", "C", true);
+
+        if (this.prompt_title != null)
+            Environment.set_variable("SEAHORSE_SSH_ASKPASS_TITLE", prompt_title, true);
+        if (this.prompt_message != null)
+            Environment.set_variable("SEAHORSE_SSH_ASKPASS_MESSAGE", prompt_message, true);
+        if (this.prompt_transient_for != 0) {
+            string parent = "%lu".printf(prompt_transient_for);
+            Environment.set_variable("SEAHORSE_SSH_ASKPASS_PARENT", parent, true);
+        }
+    }
+}
+
+public class UploadOperation : Operation {
+    /**
+     * Uploads a set of keys to a given host.
+     *
+     * @param keys The keys that should be uploaded.
+     * @param username The username to use on the server.
+     * @param hostname The URL of the host.
+     * @param port The port of the host. If none is specified, the default port is used.
+     * @param cancellable Used if you want to cancel the operation.
+     */
+    public async void upload_async(List<Key> keys,
+                                   string username, string hostname, string? port,
+                                   Cancellable? cancellable) throws GLib.Error {
+        this.prompt_title = _("Remote Host Password");
+
+        if (keys == null
+                || username == null || username == ""
+                || hostname == null || hostname == "")
+            return;
+
+        if (port == null)
+            port = "";
+
+        StringBuilder data = new StringBuilder.sized(1024);
+        keys.foreach((key) => {
+            KeyData keydata = key.key_data;
+            if (keydata != null && keydata.pubfile != null) {
+                data.append(keydata.rawdata);
+                data.append_c('\n');
+            }
+        });
+
+        /*
+         * This command creates the .ssh directory if necessary (with appropriate permissions)
+         * and then appends all input data onto the end of .ssh/authorized_keys
+         */
+        // TODO: Important, we should handle the host checking properly
+        string cmd = "%s '%s@%s' %s %s -o StrictHostKeyChecking=no \"umask 077; test -d .ssh || mkdir .ssh ; 
cat >> .ssh/authorized_keys\""
+                               .printf(Config.SSH_PATH, username, hostname, port != "" ? "-p" : "", port);
+
+        yield operation_async(cmd, data.str, cancellable);
+    }
+}
+
+public class ChangePassphraseOperation : Operation {
+    /**
+     * Changes the passphrase of the given key.
+     *
+     * @param key The key of which to change the passphrase.
+     * @param cancellable Used if you want to cancel the operation.
+     */
+    public async void change_passphrase_async(Key key,
+                                              Cancellable? cancellable) throws GLib.Error {
+        if (key.key_data == null || key.key_data.privfile == null)
+            return;
+
+        this.prompt_title = _("Enter Key Passphrase");
+        this.prompt_argument = key.label;
+
+        string cmd = "%s -p -f '%s'".printf(Config.SSH_KEYGEN_PATH, key.key_data.privfile);
+        yield operation_async(cmd, null, cancellable);
+    }
+}
+
+public class GenerateOperation : Operation {
+    /**
+     * Generates an SSH key pair.
+     *
+     * @param filename The filename of the new key.
+     * @param email The e-mail address for which the key should be made.
+     * @param type The type of key, i.e. which algorithm should be used;
+     * @param bits The amount of bits that should be used.
+     * @param cancellable Used if you want to cancel the operation.
+     */
+    public async void generate_async(string filename,
+                                     string email,
+                                     Algorithm type,
+                                     uint bits,
+                                     Cancellable cancellable) throws GLib.Error {
+        if (type == Algorithm.UNKNOWN)
+            throw new Error.GENERAL("Can't generate key for an unknown algorithm");
+
+        this.prompt_title = _("Passphrase for New Secure Shell Key");
+
+        string comment = escape_shell_arg(email);
+        string algo = type.to_string().down();
+
+        // Default number of bits
+        if (bits == 0)
+            bits = 2048;
+
+        string cmd = "%s -b '%u' -t '%s' -C %s -f '%s'".printf(Config.SSH_KEYGEN_PATH, bits, algo, comment, 
filename);
+
+        yield operation_async(cmd, null, cancellable);
+    }
+}
+
+public class PrivateImportOperation : Operation {
+    /**
+     * Imports a private key into a file.
+     *
+     * @param source The source that will decide the location of the private key.
+     * @param data The public key that needs to be imported.
+     * @param filename The name of the file in which the key should be imported.
+     * @param cancellable Allows the operation to be cancelled.
+     */
+    public async string? import_private_async(Source source,
+                                              SecData data,
+                                              string? filename,
+                                              Cancellable cancellable) throws GLib.Error {
+        if (data == null || data.rawdata == null)
+            throw new Error.GENERAL("Trying to import private key that is empty.");
+
+        // No filename specified, make one up
+        string file = filename ?? source.new_filename_for_algorithm(data.algo);
+
+        // Add the comment to the output
+        string message = (data.comment != null) ?
+            _("Importing key: %s").printf(data.comment) : _("Importing key. Enter passphrase");
+
+        // The prompt
+        this.prompt_title = _("Import Key");
+        this.prompt_message = message;
+
+        // Write the private key into the file
+        FileUtils.set_contents(file, data.rawdata);
+
+        // Start command to generate public key
+        string cmd = "%s -y -f '%s'".printf(Config.SSH_KEYGEN_PATH, file);
+        yield operation_async(cmd, null, cancellable);
+
+        // Only use the first line of the output
+        int pos = int.max(this.std_out.str.index_of_char('\n'), std_out.str.index_of_char('\r'));
+        if (pos != -1)
+            std_out.erase(pos);
+
+        // Parse the data so we can get the fingerprint
+        KeyData? keydata = KeyData.parse_line(std_out.str);
+
+        // Add the comment to the output
+        if (data.comment != null) {
+            std_out.append_c(' ');
+            std_out.append(data.comment);
+        }
+
+        // The file to write to
+        string pubfile = "%s.pub".printf(file);
+        FileUtils.set_contents(pubfile, std_out.str);
+
+        if (keydata != null && keydata.is_valid())
+            return keydata.fingerprint;
+
+        throw new Error.GENERAL("Couldn't parse imported private key fingerprint");
+    }
+}
+
+public class RenameOperation : Operation {
+    /**
+     * Renames/Deletes the comment in a key.
+     *
+     * @param key The key that should have its comment renamed/deleted.
+     * @param new_comment The new comment of the key (or null if the previous
+     *                    comment should be deleted)
+     */
+    public async void rename_async(Key key, string? new_comment, Gtk.Window transient_for) throws GLib.Error 
{
+        KeyData keydata = key.key_data;
+
+        if (new_comment == null)
+            new_comment = "";
+
+        if (!change_raw_comment (keydata, new_comment))
+            return;
+
+        debug("Renaming key to: %s", new_comment);
+
+        assert (keydata.pubfile != null);
+        if (keydata.partial) // Just part of a file for this key
+            KeyData.filter_file (keydata.pubfile, keydata, keydata);
+        else // A full file for this key
+            FileUtils.set_contents(keydata.pubfile, keydata.rawdata);
+    }
+
+    private bool change_raw_comment(KeyData keydata, string new_comment) {
+        assert(keydata.rawdata != null);
+
+        string no_leading = keydata.rawdata.chug();
+        string[] parts = no_leading.split_set(" ", 3);
+        if (parts.length < 3)
+            return false;
+
+        keydata.rawdata = parts[0] + " " + parts[1] + " " + new_comment;
+
+        return true;
+    }
+}
+
+}
diff --git a/ssh/seahorse-ssh-askpass.c b/ssh/seahorse-ssh-askpass.c
index 4617d73..cccf59f 100644
--- a/ssh/seahorse-ssh-askpass.c
+++ b/ssh/seahorse-ssh-askpass.c
@@ -141,3 +141,4 @@ main (int argc, char* argv[])
        gtk_widget_destroy (GTK_WIDGET (dialog));
        return result;
 }
+
diff --git a/ssh/seahorse-ssh-generate.ui b/ssh/seahorse-ssh-generate.ui
index d918617..fc34a0b 100644
--- a/ssh/seahorse-ssh-generate.ui
+++ b/ssh/seahorse-ssh-generate.ui
@@ -8,177 +8,64 @@
     <property name="step_increment">256</property>
     <property name="page_increment">10</property>
   </object>
-  <object class="GtkImage" id="create-image">
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-ok</property>
-  </object>
-  <object class="GtkDialog" id="ssh-generate">
+  <object class="GtkBox" id="ssh-generate">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
-    <property name="border_width">5</property>
-    <property name="title" translatable="yes">New Secure Shell Key</property>
-    <property name="resizable">False</property>
-    <property name="modal">True</property>
-    <property name="window_position">center-on-parent</property>
-    <property name="type_hint">dialog</property>
-    <signal name="delete-event" handler="on_widget_delete_event" swapped="no"/>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
+    <property name="orientation">vertical</property>
+    <property name="spacing">2</property>
+    <child>
+      <object class="GtkBox" id="hbox4">
         <property name="visible">True</property>
+        <property name="orientation">horizontal</property>
         <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="dialog-action_area1">
+        <property name="border_width">5</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkAlignment" id="alignment9">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="helpbutton1">
-                <property name="label" translatable="yes">_Just Create Key</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="use_underline">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
             <child>
-              <object class="GtkButton" id="button1">
-                <property name="label">gtk-cancel</property>
+              <object class="GtkImage" id="ssh-image">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="use_stock">True</property>
+                <property name="can_focus">False</property>
+                <property name="yalign">0</property>
+                <property name="pixel_size">48</property>
+                <property name="icon_name">gcr-key-pair</property>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="button2">
-                <property name="label" translatable="yes">_Create and Set Up</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="has_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="image">create-image</property>
-                <property name="use_underline">True</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="expand">True</property>
             <property name="fill">True</property>
-            <property name="pack_type">end</property>
             <property name="position">0</property>
           </packing>
         </child>
         <child>
-          <object class="GtkHBox" id="hbox4">
+          <object class="GtkBox" id="vbox5">
             <property name="visible">True</property>
+            <property name="orientation">vertical</property>
             <property name="can_focus">False</property>
-            <property name="border_width">5</property>
             <property name="spacing">12</property>
             <child>
-              <object class="GtkAlignment" id="alignment9">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkImage" id="ssh-image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="yalign">0</property>
-                    <property name="pixel_size">48</property>
-                    <property name="icon_name">gcr-key-pair</property>
-                  </object>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkVBox" id="vbox5">
+              <object class="GtkBox" id="vbox3">
                 <property name="visible">True</property>
+                <property name="orientation">vertical</property>
                 <property name="can_focus">False</property>
                 <property name="spacing">12</property>
                 <child>
-                  <object class="GtkVBox" id="vbox3">
+                  <object class="GtkBox" id="hbox41">
                     <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
                     <property name="can_focus">False</property>
                     <property name="spacing">12</property>
                     <child>
-                      <object class="GtkHBox" id="hbox41">
+                      <object class="GtkLabel" id="label45">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel" id="label45">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="yalign">0</property>
-                            <property name="label" translatable="yes">A Secure Shell (SSH) key lets you 
connect securely to other computers.</property>
-                            <property name="wrap">True</property>
-                          </object>
-                          <packing>
-                            <property name="expand">True</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkAlignment" id="alignment8">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="yalign">0</property>
-                            <property name="xscale">0</property>
-                            <property name="yscale">0</property>
-                            <child>
-                              <object class="GtkButton" id="button3">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
-                                <property name="relief">half</property>
-                                <signal name="clicked" handler="on_widget_help" swapped="no"/>
-                                <child>
-                                  <object class="GtkImage" id="image3">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="stock">gtk-help</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">A Secure Shell (SSH) key lets you connect 
securely to other computers.</property>
+                        <property name="wrap">True</property>
                       </object>
                       <packing>
                         <property name="expand">True</property>
@@ -187,77 +74,34 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkAlignment" id="alignment7">
+                      <object class="GtkAlignment" id="alignment8">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="xscale">0</property>
+                        <property name="yscale">0</property>
                         <child>
-                          <object class="GtkGrid" id="grid1">
+                          <object class="GtkButton" id="button3">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row_spacing">6</property>
-                            <property name="column_spacing">12</property>
-                            <child>
-                              <object class="GtkLabel" id="label46">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="xalign">1</property>
-                                <property name="yalign">0</property>
-                                <property name="label" translatable="yes">_Description:</property>
-                                <property name="use_markup">True</property>
-                                <property name="use_underline">True</property>
-                                <property name="mnemonic_widget">email-entry</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">0</property>
-                                <property name="top_attach">0</property>
-                                <property name="width">1</property>
-                                <property name="height">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkEntry" id="email-entry">
-                                <property name="width_request">180</property>
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="activates_default">True</property>
-                                <property name="invisible_char_set">True</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">0</property>
-                                <property name="width">1</property>
-                                <property name="height">1</property>
-                              </packing>
-                            </child>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="use_action_appearance">False</property>
+                            <property name="relief">half</property>
+                            <signal name="clicked" handler="on_widget_help" swapped="no"/>
                             <child>
-                              <object class="GtkLabel" id="label53">
+                              <object class="GtkImage" id="image3">
                                 <property name="visible">True</property>
                                 <property name="can_focus">False</property>
-                                <property name="xalign">0</property>
-                                <property name="xpad">3</property>
-                                <property name="label" translatable="yes">Your email address, or a reminder 
of what this key is for.</property>
-                                <property name="wrap">True</property>
-                                <attributes>
-                                 <attribute name="style" value="italic"/>
-                                </attributes>
+                                <property name="stock">gtk-help</property>
                               </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="top_attach">1</property>
-                                <property name="width">1</property>
-                                <property name="height">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <placeholder/>
                             </child>
                           </object>
                         </child>
                       </object>
                       <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
                         <property name="position">1</property>
                       </packing>
                     </child>
@@ -269,24 +113,25 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkExpander" id="expander1">
+                  <object class="GtkAlignment" id="alignment7">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
+                    <property name="can_focus">False</property>
                     <child>
-                      <object class="GtkGrid" id="grid2">
+                      <object class="GtkGrid" id="grid1">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="margin_left">20</property>
                         <property name="row_spacing">6</property>
                         <property name="column_spacing">12</property>
                         <child>
-                          <object class="GtkLabel" id="label49">
+                          <object class="GtkLabel" id="label46">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Encryption _Type:</property>
+                            <property name="xalign">1</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">_Description:</property>
+                            <property name="use_markup">True</property>
                             <property name="use_underline">True</property>
-                            <property name="mnemonic_widget">algorithm-choice</property>
+                            <property name="mnemonic_widget">email-entry</property>
                           </object>
                           <packing>
                             <property name="left_attach">0</property>
@@ -296,40 +141,13 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label50">
+                          <object class="GtkEntry" id="email-entry">
+                            <property name="width_request">180</property>
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Key _Strength (bits):</property>
-                            <property name="use_underline">True</property>
-                            <property name="mnemonic_widget">bits-entry</property>
-                          </object>
-                          <packing>
-                            <property name="left_attach">0</property>
-                            <property name="top_attach">1</property>
-                            <property name="width">1</property>
-                            <property name="height">1</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkAlignment" id="alignment4">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="yalign">0</property>
-                            <property name="xscale">0</property>
-                            <child>
-                              <object class="GtkComboBoxText" id="algorithm-choice">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="entry_text_column">0</property>
-                                <property name="id_column">1</property>
-                                <items>
-                                  <item translatable="yes">RSA</item>
-                                  <item translatable="yes">DSA</item>
-                                </items>
-                              </object>
-                            </child>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="activates_default">True</property>
+                            <property name="invisible_char_set">True</property>
                           </object>
                           <packing>
                             <property name="left_attach">1</property>
@@ -339,24 +157,16 @@
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkAlignment" id="alignment5">
+                          <object class="GtkLabel" id="label53">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
                             <property name="xalign">0</property>
-                            <property name="yalign">0</property>
-                            <property name="xscale">0</property>
-                            <property name="yscale">0</property>
-                            <child>
-                              <object class="GtkSpinButton" id="bits-entry">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="invisible_char">●</property>
-                                <property name="invisible_char_set">True</property>
-                                <property name="adjustment">adjustment1</property>
-                                <property name="climb_rate">1</property>
-                                <property name="numeric">True</property>
-                              </object>
-                            </child>
+                            <property name="xpad">3</property>
+                            <property name="label" translatable="yes">Your email address, or a reminder of 
what this key is for.</property>
+                            <property name="wrap">True</property>
+                            <attributes>
+                             <attribute name="style" value="italic"/>
+                            </attributes>
                           </object>
                           <packing>
                             <property name="left_attach">1</property>
@@ -365,17 +175,9 @@
                             <property name="height">1</property>
                           </packing>
                         </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label48">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">_Advanced key options</property>
-                        <property name="use_underline">True</property>
-                        <attributes>
-                         <attribute name="weight" value="bold"/>
-                        </attributes>
+                        <child>
+                          <placeholder/>
+                        </child>
                       </object>
                     </child>
                   </object>
@@ -385,19 +187,122 @@
                     <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="GtkExpander" id="expander1">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
                 <child>
-                  <object class="GtkLabel" id="label54">
+                  <object class="GtkGrid" id="grid2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_left">20</property>
+                    <property name="row_spacing">6</property>
+                    <property name="column_spacing">12</property>
+                    <child>
+                      <object class="GtkLabel" id="label49">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Encryption _Type:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">algorithm-combo-box</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label50">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Key _Strength (bits):</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">bits-spin-button</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkAlignment" id="alignment4">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="xscale">0</property>
+                        <child>
+                          <object class="GtkComboBoxText" id="algorithm-combo-box">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="entry_text_column">0</property>
+                            <property name="id_column">1</property>
+                            <items>
+                              <item translatable="yes">RSA</item>
+                              <item translatable="yes">DSA</item>
+                            </items>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkAlignment" id="alignment5">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="xscale">0</property>
+                        <property name="yscale">0</property>
+                        <child>
+                          <object class="GtkSpinButton" id="bits-spin-button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="invisible_char">●</property>
+                            <property name="invisible_char_set">True</property>
+                            <property name="adjustment">adjustment1</property>
+                            <property name="climb_rate">1</property>
+                            <property name="numeric">True</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                        <property name="width">1</property>
+                        <property name="height">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label48">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">If there is a computer you want to use this 
key with, you can set up that computer to recognize your new key.</property>
-                    <property name="wrap">True</property>
+                    <property name="label" translatable="yes">_Advanced key options</property>
+                    <property name="use_underline">True</property>
+                    <attributes>
+                     <attribute name="weight" value="bold"/>
+                    </attributes>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">2</property>
-                  </packing>
                 </child>
               </object>
               <packing>
@@ -406,19 +311,63 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkLabel" id="label54">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">If there is a computer you want to use this key 
with, you can set up that computer to recognize your new key.</property>
+                <property name="wrap">True</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="expand">True</property>
             <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
         </child>
       </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkButtonBox" id="action_area">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkButton" id="create-no-setup">
+        <property name="label" translatable="yes">_Just Create Key</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="can_default">True</property>
+        <property name="receives_default">False</property>
+        <property name="use_underline">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="create-with-setup">
+        <property name="label" translatable="yes">_Create and Set Up</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="use_underline">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+      </packing>
     </child>
-    <action-widgets>
-      <action-widget response="-7">helpbutton1</action-widget>
-      <action-widget response="-6">button1</action-widget>
-      <action-widget response="-5">button2</action-widget>
-    </action-widgets>
   </object>
 </interface>
diff --git a/ssh/seahorse-ssh-key-properties.ui b/ssh/seahorse-ssh-key-properties.ui
index 24ef34d..933f181 100644
--- a/ssh/seahorse-ssh-key-properties.ui
+++ b/ssh/seahorse-ssh-key-properties.ui
@@ -8,568 +8,508 @@
   <object class="GtkImage" id="export-image">
     <property name="stock">gtk-save-as</property>
   </object>
-  <object class="GtkDialog" id="ssh-key-properties">
+  <object class="GtkBox" id="ssh-key-properties">
     <property name="visible">True</property>
-    <property name="border_width">5</property>
-    <property name="title" translatable="yes">Key Properties</property>
-    <property name="resizable">False</property>
-    <property name="type_hint">dialog</property>
-    <signal name="delete_event" handler="on_widget_delete_event"/>
-    <child internal-child="vbox">
-      <object class="GtkVBox" id="dialog-vbox1">
+    <property name="orientation">vertical</property>
+    <property name="spacing">2</property>
+    <child>
+      <object class="GtkNotebook" id="notebook">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="can_focus">True</property>
+        <property name="border_width">5</property>
         <child>
-          <object class="GtkNotebook" id="notebook">
+          <object class="GtkBox" id="vbox7">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="border_width">5</property>
+            <property name="orientation">vertical</property>
+            <property name="border_width">12</property>
+            <property name="spacing">12</property>
             <child>
-              <object class="GtkVBox" id="vbox7">
+              <object class="GtkHBox" id="hbox61">
                 <property name="visible">True</property>
-                <property name="border_width">12</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
                 <child>
-                  <object class="GtkHBox" id="hbox61">
+                  <object class="GtkImage" id="key-image">
                     <property name="visible">True</property>
+                    <property name="yalign">0</property>
+                    <property name="stock">gtk-missing-image</property>
+                    <property name="icon-size">6</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkTable" id="table9">
+                    <property name="visible">True</property>
+                    <property name="n_rows">4</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">12</property>
+                    <property name="row_spacing">3</property>
+                    <child>
+                      <object class="GtkLabel" id="label74">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="label" translatable="yes">Identifier:</property>
+                        <attributes>
+                         <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="id-label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label"></property>
+                        <property name="selectable">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label93">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">Type:</property>
+                        <attributes>
+                         <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="type-label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Secure Shell Key</property>
+                        <property name="selectable">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
                     <child>
-                      <object class="GtkImage" id="key-image">
+                      <object class="GtkLabel" id="label72">
                         <property name="visible">True</property>
+                        <property name="xalign">1</property>
                         <property name="yalign">0</property>
-                        <property name="stock">gtk-missing-image</property>
-                        <property name="icon-size">6</property>
+                        <property name="label" translatable="yes" context="name-of-ssh-key" comments="Name 
of key, often a persons name">Name:</property>
+                        <attributes>
+                         <attribute name="weight" value="bold"/>
+                        </attributes>
                       </object>
                       <packing>
-                        <property name="expand">False</property>
-                        <property name="position">0</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkTable" id="table9">
+                      <object class="GtkEntry" id="comment-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="activates_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label22227">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">1</property>
+                        <property name="label" translatable="yes">Used to connect to other 
computers.</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label22228">
+                        <property name="visible">True</property>
+                        <property name="xalign">1</property>
+                        <property name="yalign">1</property>
+                        <property name="label" translatable="yes" comments="To translators: This is the noun 
not the verb.">Use:</property>
+                        <attributes>
+                         <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkAlignment" id="alignment42">
+                <property name="visible">True</property>
+                <property name="xalign">1</property>
+                <property name="xscale">0</property>
+                <property name="yscale">0</property>
+                <child>
+                  <object class="GtkButton" id="passphrase-button">
+                    <property name="label" translatable="yes">Change _Passphrase</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="image">passphrase-image</property>
+                    <property name="use_underline">True</property>
+                    <signal name="clicked" handler="on_ssh_passphrase_button_clicked"/>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox28">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label22226">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Trust</property>
+                    <attributes>
+                     <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkAlignment" id="alignment46">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkBox" id="vbox29">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkCheckButton" id="trust-check">
+                            <property name="label" translatable="yes">The owner of this key is _authorized 
to connect to this computer</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                            <signal name="toggled" handler="on_ssh_trust_toggled"/>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="trust-message">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">This only applies to the 
&lt;i&gt;%s&lt;/i&gt; account.</property>
+                            <property name="use_markup">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+        <child type="tab">
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xpad">3</property>
+            <property name="label" translatable="yes">Key</property>
+          </object>
+          <packing>
+            <property name="tab_fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="vbox8">
+            <property name="visible">True</property>
+            <property name="border_width">12</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkFrame" id="frame4">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment29">
+                    <property name="visible">True</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkTable" id="table10">
                         <property name="visible">True</property>
                         <property name="n_rows">4</property>
                         <property name="n_columns">2</property>
                         <property name="column_spacing">12</property>
-                        <property name="row_spacing">3</property>
+                        <property name="row_spacing">6</property>
                         <child>
-                          <object class="GtkLabel" id="label74">
+                          <object class="GtkLabel" id="label109">
                             <property name="visible">True</property>
-                            <property name="xalign">1</property>
-                            <property name="label" translatable="yes">Identifier:</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">Algorithm:</property>
                             <attributes>
                              <attribute name="weight" value="bold"/>
                             </attributes>
                           </object>
                           <packing>
-                            <property name="top_attach">3</property>
-                            <property name="bottom_attach">4</property>
                             <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="id-label">
+                          <object class="GtkLabel" id="label25">
                             <property name="visible">True</property>
-                            <property name="can_focus">True</property>
                             <property name="xalign">0</property>
-                            <property name="label"></property>
-                            <property name="selectable">True</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">Strength:</property>
+                            <attributes>
+                             <attribute name="weight" value="bold"/>
+                            </attributes>
                           </object>
                           <packing>
-                            <property name="left_attach">1</property>
-                            <property name="right_attach">2</property>
-                            <property name="top_attach">3</property>
-                            <property name="bottom_attach">4</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
                             <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label93">
+                          <object class="GtkLabel" id="algo-label">
                             <property name="visible">True</property>
-                            <property name="xalign">1</property>
-                            <property name="yalign">0</property>
-                            <property name="label" translatable="yes">Type:</property>
-                            <attributes>
-                             <attribute name="weight" value="bold"/>
-                            </attributes>
+                            <property name="can_focus">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label"></property>
+                            <property name="selectable">True</property>
                           </object>
                           <packing>
-                            <property name="top_attach">2</property>
-                            <property name="bottom_attach">3</property>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
                             <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="type-label">
+                          <object class="GtkLabel" id="strength-label">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Secure Shell Key</property>
+                            <property name="label"></property>
                             <property name="selectable">True</property>
                           </object>
                           <packing>
                             <property name="left_attach">1</property>
                             <property name="right_attach">2</property>
-                            <property name="top_attach">2</property>
-                            <property name="bottom_attach">3</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
                             <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label72">
+                          <object class="GtkLabel" id="label113">
                             <property name="visible">True</property>
-                            <property name="xalign">1</property>
-                            <property name="yalign">0</property>
-                            <property name="label" translatable="yes" context="name-of-ssh-key" 
comments="Name of key, often a persons name">Name:</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Location:</property>
                             <attributes>
                              <attribute name="weight" value="bold"/>
                             </attributes>
                           </object>
                           <packing>
-                            <property name="top_attach">1</property>
-                            <property name="bottom_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
                             <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkEntry" id="comment-entry">
+                          <object class="GtkLabel" id="label22231">
                             <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="activates_default">True</property>
-                            <signal name="focus_out_event" handler="on_ssh_comment_focus_out"/>
-                            <signal name="activate" handler="on_ssh_comment_activate"/>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">Fingerprint:</property>
+                            <attributes>
+                             <attribute name="weight" value="bold"/>
+                            </attributes>
                           </object>
                           <packing>
-                            <property name="left_attach">1</property>
-                            <property name="right_attach">2</property>
-                            <property name="top_attach">1</property>
-                            <property name="bottom_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
+                            <property name="x_options">GTK_FILL</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label22227">
+                          <object class="GtkLabel" id="fingerprint-label">
                             <property name="visible">True</property>
+                            <property name="can_focus">True</property>
                             <property name="xalign">0</property>
-                            <property name="yalign">1</property>
-                            <property name="label" translatable="yes">Used to connect to other 
computers.</property>
-                            <property name="use_markup">True</property>
+                            <property name="yalign">0</property>
+                            <property name="label"></property>
+                            <property name="selectable">True</property>
                           </object>
                           <packing>
                             <property name="left_attach">1</property>
                             <property name="right_attach">2</property>
+                            <property name="top_attach">3</property>
+                            <property name="bottom_attach">4</property>
                             <property name="x_options">GTK_FILL</property>
-                            <property name="y_options">GTK_FILL</property>
+                            <property name="y_options"></property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label22228">
+                          <object class="GtkLabel" id="location-label">
                             <property name="visible">True</property>
-                            <property name="xalign">1</property>
-                            <property name="yalign">1</property>
-                            <property name="label" translatable="yes" comments="To translators: This is the 
noun not the verb.">Use:</property>
-                            <attributes>
-                             <attribute name="weight" value="bold"/>
-                            </attributes>
+                            <property name="can_focus">True</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="label"></property>
+                            <property name="use_markup">True</property>
+                            <property name="selectable">True</property>
+                            <property name="ellipsize">start</property>
                           </object>
                           <packing>
-                            <property name="x_options">GTK_FILL</property>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">2</property>
+                            <property name="bottom_attach">3</property>
                             <property name="y_options"></property>
                           </packing>
                         </child>
                       </object>
-                      <packing>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkAlignment" id="alignment42">
-                    <property name="visible">True</property>
-                    <property name="xalign">1</property>
-                    <property name="xscale">0</property>
-                    <property name="yscale">0</property>
-                    <child>
-                      <object class="GtkButton" id="passphrase-button">
-                        <property name="label" translatable="yes">Change _Passphrase</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="image">passphrase-image</property>
-                        <property name="use_underline">True</property>
-                        <signal name="clicked" handler="on_ssh_passphrase_button_clicked"/>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkVBox" id="vbox28">
+                <child type="label">
+                  <object class="GtkLabel" id="label108">
                     <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkLabel" id="label22226">
-                        <property name="visible">True</property>
-                        <property name="xalign">0</property>
-                        <property name="label" translatable="yes">Trust</property>
-                        <attributes>
-                         <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkAlignment" id="alignment46">
-                        <property name="visible">True</property>
-                        <property name="left_padding">12</property>
-                        <child>
-                          <object class="GtkVBox" id="vbox29">
-                            <property name="visible">True</property>
-                            <property name="orientation">vertical</property>
-                            <property name="spacing">6</property>
-                            <child>
-                              <object class="GtkCheckButton" id="trust-check">
-                                <property name="label" translatable="yes">The owner of this key is 
_authorized to connect to this computer</property>
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="use_underline">True</property>
-                                <property name="draw_indicator">True</property>
-                                <signal name="toggled" handler="on_ssh_trust_toggled"/>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="trust-message">
-                                <property name="visible">True</property>
-                                <property name="xalign">0</property>
-                                <property name="label" translatable="yes">This only applies to the 
&lt;i&gt;%s&lt;/i&gt; account.</property>
-                                <property name="use_markup">True</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
+                    <property name="label" translatable="yes">Technical Details:</property>
+                    <attributes>
+                     <attribute name="weight" value="bold"/>
+                    </attributes>
                   </object>
-                  <packing>
-                    <property name="position">2</property>
-                  </packing>
                 </child>
               </object>
               <packing>
-                <property name="tab_fill">False</property>
-              </packing>
-            </child>
-            <child type="tab">
-              <object class="GtkLabel" id="label1">
-                <property name="visible">True</property>
-                <property name="xpad">3</property>
-                <property name="label" translatable="yes">Key</property>
-              </object>
-              <packing>
-                <property name="tab_fill">False</property>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
               </packing>
             </child>
             <child>
-              <object class="GtkVBox" id="vbox8">
+              <object class="GtkAlignment" id="alignment44">
                 <property name="visible">True</property>
-                <property name="border_width">12</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
+                <property name="xalign">1</property>
+                <property name="xscale">0</property>
+                <property name="yscale">0</property>
                 <child>
-                  <object class="GtkFrame" id="frame4">
+                  <object class="GtkButton" id="export-button">
+                    <property name="label" translatable="yes">E_xport Complete Key</property>
                     <property name="visible">True</property>
-                    <property name="label_xalign">0</property>
-                    <property name="shadow_type">none</property>
-                    <child>
-                      <object class="GtkAlignment" id="alignment29">
-                        <property name="visible">True</property>
-                        <property name="top_padding">6</property>
-                        <property name="left_padding">12</property>
-                        <child>
-                          <object class="GtkTable" id="table10">
-                            <property name="visible">True</property>
-                            <property name="n_rows">4</property>
-                            <property name="n_columns">2</property>
-                            <property name="column_spacing">12</property>
-                            <property name="row_spacing">6</property>
-                            <child>
-                              <object class="GtkLabel" id="label109">
-                                <property name="visible">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label" translatable="yes">Algorithm:</property>
-                                <attributes>
-                                 <attribute name="weight" value="bold"/>
-                                </attributes>
-                              </object>
-                              <packing>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label25">
-                                <property name="visible">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label" translatable="yes">Strength:</property>
-                                <attributes>
-                                 <attribute name="weight" value="bold"/>
-                                </attributes>
-                              </object>
-                              <packing>
-                                <property name="top_attach">1</property>
-                                <property name="bottom_attach">2</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="algo-label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="label"></property>
-                                <property name="selectable">True</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="right_attach">2</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="strength-label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="label"></property>
-                                <property name="selectable">True</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="right_attach">2</property>
-                                <property name="top_attach">1</property>
-                                <property name="bottom_attach">2</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label113">
-                                <property name="visible">True</property>
-                                <property name="xalign">0</property>
-                                <property name="label" translatable="yes">Location:</property>
-                                <attributes>
-                                 <attribute name="weight" value="bold"/>
-                                </attributes>
-                              </object>
-                              <packing>
-                                <property name="top_attach">2</property>
-                                <property name="bottom_attach">3</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="label22231">
-                                <property name="visible">True</property>
-                                <property name="xalign">0</property>
-                                <property name="label" translatable="yes">Fingerprint:</property>
-                                <attributes>
-                                 <attribute name="weight" value="bold"/>
-                                </attributes>
-                              </object>
-                              <packing>
-                                <property name="top_attach">3</property>
-                                <property name="bottom_attach">4</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="fingerprint-label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label"></property>
-                                <property name="selectable">True</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="right_attach">2</property>
-                                <property name="top_attach">3</property>
-                                <property name="bottom_attach">4</property>
-                                <property name="x_options">GTK_FILL</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="location-label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label"></property>
-                                <property name="use_markup">True</property>
-                                <property name="selectable">True</property>
-                                <property name="ellipsize">start</property>
-                              </object>
-                              <packing>
-                                <property name="left_attach">1</property>
-                                <property name="right_attach">2</property>
-                                <property name="top_attach">2</property>
-                                <property name="bottom_attach">3</property>
-                                <property name="y_options"></property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                    <child type="label">
-                      <object class="GtkLabel" id="label108">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Technical Details:</property>
-                        <attributes>
-                         <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                    </child>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="image">export-image</property>
+                    <property name="use_underline">True</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkAlignment" id="alignment44">
-                    <property name="visible">True</property>
-                    <property name="xalign">1</property>
-                    <property name="xscale">0</property>
-                    <property name="yscale">0</property>
-                    <child>
-                      <object class="GtkButton" id="export-button">
-                        <property name="label" translatable="yes">E_xport Complete Key</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="image">export-image</property>
-                        <property name="use_underline">True</property>
-                        <signal name="clicked" handler="on_ssh_export_button_clicked"/>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
               <packing>
                 <property name="position">1</property>
               </packing>
             </child>
-            <child type="tab">
-              <object class="GtkLabel" id="label22222">
-                <property name="visible">True</property>
-                <property name="xpad">3</property>
-                <property name="label" translatable="yes">Details</property>
-              </object>
-              <packing>
-                <property name="position">1</property>
-                <property name="tab_fill">False</property>
-              </packing>
-            </child>
           </object>
           <packing>
             <property name="position">1</property>
           </packing>
         </child>
-        <child internal-child="action_area">
-          <object class="GtkHButtonBox" id="dialog-action_area1">
+        <child type="tab">
+          <object class="GtkLabel" id="label22222">
             <property name="visible">True</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="helpbutton1">
-                <property name="label">gtk-help</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_stock">True</property>
-                <signal name="clicked" handler="on_widget_help"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="closebutton1">
-                <property name="label">gtk-close</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_stock">True</property>
-                <signal name="clicked" handler="on_widget_closed"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
+            <property name="xpad">3</property>
+            <property name="label" translatable="yes">Details</property>
           </object>
           <packing>
-            <property name="expand">False</property>
-            <property name="pack_type">end</property>
-            <property name="position">0</property>
+            <property name="position">1</property>
+            <property name="tab_fill">False</property>
           </packing>
         </child>
       </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
     </child>
-    <action-widgets>
-      <action-widget response="-11">helpbutton1</action-widget>
-      <action-widget response="-7">closebutton1</action-widget>
-    </action-widgets>
   </object>
 </interface>
diff --git a/ssh/seahorse-ssh-upload.ui b/ssh/seahorse-ssh-upload.ui
index 92c086b..41e8442 100644
--- a/ssh/seahorse-ssh-upload.ui
+++ b/ssh/seahorse-ssh-upload.ui
@@ -2,209 +2,146 @@
 <interface>
   <requires lib="gtk+" version="2.16"/>
   <!-- interface-naming-policy toplevel-contextual -->
-  <object class="GtkImage" id="setup-image">
-    <property name="stock">gtk-go-up</property>
-  </object>
-  <object class="GtkDialog" id="ssh-upload">
-    <property name="border_width">5</property>
-    <property name="title" translatable="yes">Set Up Computer for SSH Connection</property>
-    <property name="resizable">False</property>
-    <property name="modal">True</property>
-    <property name="window_position">center-on-parent</property>
-    <property name="default_width">400</property>
-    <property name="type_hint">dialog</property>
-    <property name="skip_taskbar_hint">True</property>
-    <property name="skip_pager_hint">True</property>
-    <child internal-child="vbox">
-      <object class="GtkVBox" id="dialog-vbox1">
+  <object class="GtkBox" id="ssh-upload">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">2</property>
+    <child>
+      <object class="GtkBox" id="vbox1">
         <property name="visible">True</property>
-        <property name="spacing">2</property>
+        <property name="orientation">vertical</property>
+        <property name="border_width">5</property>
+        <property name="spacing">12</property>
         <child>
-          <object class="GtkVBox" id="vbox1">
+          <object class="GtkBox" id="vbox2">
             <property name="visible">True</property>
-            <property name="border_width">5</property>
+            <property name="orientation">vertical</property>
             <property name="spacing">12</property>
             <child>
-              <object class="GtkVBox" id="vbox2">
+              <object class="GtkLabel" id="label5">
+                <property name="width_request">380</property>
                 <property name="visible">True</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="label5">
-                    <property name="width_request">380</property>
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">To use your Secure Shell key with another 
computer that uses SSH, you must already have a login account on that computer.</property>
-                    <property name="wrap">True</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
+                <property name="label" translatable="yes">To use your Secure Shell key with another computer 
that uses SSH, you must already have a login account on that computer.</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkTable" id="table1">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">12</property>
+                <property name="row_spacing">6</property>
                 <child>
-                  <object class="GtkTable" id="table1">
+                  <object class="GtkBox" id="vbox3">
                     <property name="visible">True</property>
-                    <property name="n_rows">2</property>
-                    <property name="n_columns">2</property>
-                    <property name="column_spacing">12</property>
-                    <property name="row_spacing">6</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">3</property>
                     <child>
-                      <object class="GtkVBox" id="vbox3">
+                      <object class="GtkEntry" id="host-entry">
                         <property name="visible">True</property>
-                        <property name="spacing">3</property>
-                        <child>
-                          <object class="GtkEntry" id="host-label">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="has_focus">True</property>
-                            <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
-                            <property name="invisible_char">&#x25CF;</property>
-                            <property name="activates_default">True</property>
-                            <signal name="changed" handler="on_upload_input_changed"/>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label7">
-                            <property name="visible">True</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">eg: 
fileserver.example.com:port</property>
-                            <attributes>
-                             <attribute name="style" value="italic"/>
-                            </attributes>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="right_attach">2</property>
-                        <property name="x_options">GTK_FILL</property>
-                        <property name="y_options">GTK_FILL</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label3">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="yalign">0</property>
-                        <property name="ypad">4</property>
-                        <property name="label" translatable="yes">_Server address:</property>
-                        <property name="use_underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="x_options">GTK_FILL</property>
-                        <property name="y_options">GTK_FILL</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="label" translatable="yes">_Login name:</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">user-label</property>
+                        <property name="can_focus">True</property>
+                        <property name="has_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
+                        <property name="invisible_char">&#x25CF;</property>
+                        <property name="activates_default">True</property>
                       </object>
                       <packing>
-                        <property name="top_attach">1</property>
-                        <property name="bottom_attach">2</property>
-                        <property name="x_options">GTK_FILL</property>
-                        <property name="y_options"></property>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="user-label">
+                      <object class="GtkLabel" id="label7">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="has_focus">True</property>
-                        <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
-                        <property name="invisible_char">&#x25CF;</property>
-                        <property name="activates_default">True</property>
-                        <signal name="changed" handler="on_upload_input_changed"/>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">eg: fileserver.example.com:port</property>
+                        <attributes>
+                         <attribute name="style" value="italic"/>
+                        </attributes>
                       </object>
                       <packing>
-                        <property name="left_attach">1</property>
-                        <property name="right_attach">2</property>
-                        <property name="top_attach">1</property>
-                        <property name="bottom_attach">2</property>
-                        <property name="y_options"></property>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
                       </packing>
                     </child>
                   </object>
                   <packing>
-                    <property name="position">1</property>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="xalign">1</property>
+                    <property name="yalign">0</property>
+                    <property name="ypad">4</property>
+                    <property name="label" translatable="yes">_Server address:</property>
+                    <property name="use_underline">True</property>
+                  </object>
+                  <packing>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="xalign">1</property>
+                    <property name="label" translatable="yes">_Login name:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">user-entry</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"></property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="user-entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="has_focus">True</property>
+                    <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
+                    <property name="invisible_char">&#x25CF;</property>
+                    <property name="activates_default">True</property>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="y_options"></property>
                   </packing>
                 </child>
               </object>
               <packing>
-                <property name="expand">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child internal-child="action_area">
-          <object class="GtkHButtonBox" id="dialog-action_area1">
-            <property name="visible">True</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="cancel">
-                <property name="label">gtk-cancel</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="ok">
-                <property name="label" translatable="yes">_Set Up</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="has_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="has_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="image">setup-image</property>
-                <property name="use_underline">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
                 <property name="position">1</property>
               </packing>
             </child>
           </object>
           <packing>
             <property name="expand">False</property>
-            <property name="pack_type">end</property>
             <property name="position">0</property>
           </packing>
         </child>
       </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">1</property>
+      </packing>
     </child>
-    <action-widgets>
-      <action-widget response="-6">cancel</action-widget>
-      <action-widget response="-3">ok</action-widget>
-    </action-widgets>
   </object>
 </interface>
diff --git a/ssh/source.vala b/ssh/source.vala
new file mode 100644
index 0000000..f29df29
--- /dev/null
+++ b/ssh/source.vala
@@ -0,0 +1,462 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004-2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (c) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * The {@link Place} where SSH keys are stored. By default that is ~/.ssh.
+ * Basically, this becomes an interface to the SSH home directory.
+ */
+public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
+
+    public const string AUTHORIZED_KEYS_FILE = "authorized_keys";
+    public const string OTHER_KEYS_FILE = "other_keys.seahorse";
+
+    // The home directory for SSH keys.
+    private string ssh_homedir;
+    // Source for refresh timeout
+    private uint scheduled_refresh_source;
+    // For monitoring the .ssh directory
+    private FileMonitor monitor_handle;
+
+    // Maps filenames (of the public parts) to keys
+    private HashTable<string, Key> keys;
+
+    public string label {
+        owned get { return _("OpenSSH keys"); }
+        set { }
+    }
+
+    public string description {
+        owned get { return _("OpenSSH: %s").printf(this.ssh_homedir); }
+    }
+
+    public string uri {
+        owned get { return _("openssh://%s").printf(this.ssh_homedir); }
+    }
+
+    public Icon icon {
+        owned get { return new ThemedIcon(Gcr.ICON_HOME_DIRECTORY); }
+    }
+
+    public Gtk.ActionGroup? actions {
+        owned get { return null; }
+    }
+
+    /**
+     * The directory containing the SSH keys.
+     */
+    public string base_directory {
+        get { return ssh_homedir; }
+    }
+
+    construct {
+        this.keys = new HashTable<string, Key>(str_hash, str_equal);
+
+        this.scheduled_refresh_source = 0;
+        this.monitor_handle = null;
+
+        this.ssh_homedir = "%s/.ssh".printf(Environment.get_home_dir());
+
+        // Make the .ssh directory if it doesn't exist
+        if (!FileUtils.test(this.ssh_homedir, FileTest.EXISTS)) {
+            if (DirUtils.create(this.ssh_homedir, 0700) != 0) {
+                warning("couldn't create .ssh directory: %s", this.ssh_homedir);
+                return;
+            }
+        }
+
+        monitor_ssh_homedir();
+    }
+
+    private bool check_file_for_ssh(string filename) {
+        if(!FileUtils.test(filename, FileTest.IS_REGULAR))
+            return false;
+
+        try {
+            string contents;
+            FileUtils.get_contents(filename, out contents);
+
+            // Check for our signature
+            return " PRIVATE KEY-----" in contents;
+        } catch (FileError e) {
+            warning("Error reading file '%s' to check for SSH key. %s".printf(filename, e.message));
+        }
+
+        return false;
+    }
+
+    private void cancel_scheduled_refresh () {
+        if (this.scheduled_refresh_source != 0) {
+            debug("Cancelling scheduled refresh event");
+            GLib.Source.remove(this.scheduled_refresh_source);
+            this.scheduled_refresh_source = 0;
+        }
+    }
+
+    private bool scheduled_refresh() {
+        debug("Scheduled refresh event ocurring now");
+        cancel_scheduled_refresh();
+        load.begin(null);
+        return false; // don't run again
+    }
+
+    private bool scheduled_dummy() {
+        debug("Dummy refresh event occurring now");
+        this.scheduled_refresh_source = 0;
+        return false; // don't run again
+    }
+
+    private void monitor_ssh_homedir() {
+        File? dot_ssh_dir = File.new_for_path(this.ssh_homedir);
+        if (dot_ssh_dir == null)
+            return;
+
+        try {
+            this.monitor_handle = dot_ssh_dir.monitor_directory(FileMonitorFlags.NONE, null);
+            this.monitor_handle.changed.connect((file, other_file, event_type) => {
+                if (this.scheduled_refresh_source != 0 ||
+                    (event_type != FileMonitorEvent.CHANGED &&
+                     event_type != FileMonitorEvent.CHANGES_DONE_HINT &&
+                     event_type != FileMonitorEvent.DELETED &&
+                     event_type != FileMonitorEvent.CREATED))
+                    return;
+
+                string? path = file.get_path();
+                if (path == null)
+                    return;
+
+                // Filter out any noise
+                if (event_type != FileMonitorEvent.DELETED
+                        && !path.has_suffix(AUTHORIZED_KEYS_FILE)
+                        && !path.has_suffix(OTHER_KEYS_FILE)
+                        && !path.has_suffix(".pub")
+                        && !SecData.contains_private_key(path))
+                    return;
+
+                debug("Scheduling refresh event due to file changes");
+                this.scheduled_refresh_source = Timeout.add(500, scheduled_refresh);
+            });
+        } catch (GLib.Error e) {
+            warning("couldn't monitor ssh directory: %s: %s", this.ssh_homedir, e.message);
+        }
+    }
+
+    public uint get_length() {
+        return this.keys.size();
+    }
+
+    public List<weak GLib.Object> get_objects() {
+        return this.keys.get_values();
+    }
+
+    public bool contains(GLib.Object object) {
+        Key? key = (object as Key);
+        if (key == null)
+            return false;
+
+        string filename = key.get_location();
+        return this.keys.lookup(filename) == object;
+    }
+
+    public void remove_object(GLib.Object object) {
+        Key? key = object as Key;
+        if (key == null)
+            return;
+
+        string? filename = key.get_location();
+        if (filename == null || this.keys.lookup(filename) != key)
+            return;
+
+        this.keys.remove(filename);
+        removed(key);
+    }
+
+    public string file_for_public(bool authorized) {
+        return Path.build_filename(this.ssh_homedir,
+                                   authorized ? AUTHORIZED_KEYS_FILE : OTHER_KEYS_FILE);
+    }
+
+    public async Key? add_key_from_filename(string? privfile) throws GLib.Error {
+        if (privfile == null)
+            return null;
+
+        // Check if it was already loaded once. If not, load it now
+        Key? key = this.keys.lookup(privfile);
+        if (key != null)
+            return key;
+
+        return yield load_key_for_private_file(privfile);
+    }
+
+    // Loads the (public) key for a private key.
+    private async Key? load_key_for_private_file(string privfile) throws GLib.Error {
+        string pubfile = privfile + ".pub";
+        Source src = this;
+        Key? key = null;
+
+        // possibly an SSH key?
+        if (FileUtils.test(privfile, FileTest.EXISTS)
+                && FileUtils.test(pubfile, FileTest.EXISTS)
+                && check_file_for_ssh(privfile)) {
+            try {
+                yield Key.parse_file(pubfile, (keydata) => {
+                    key = Source.add_key_from_parsed_data(src, keydata, pubfile, false, false, privfile);
+                    return true;
+                });
+            } catch (GLib.Error e) {
+                throw new Error.GENERAL("Couldn't read SSH file: %s (%s)".printf(pubfile, e.message));
+            }
+        }
+
+        return key;
+    }
+
+    public string? export_private(Key key) throws GLib.Error {
+        KeyData? keydata = key.key_data;
+        if (keydata == null)
+            return null;
+
+        if (keydata.privfile == null)
+            throw new Error.GENERAL(_("No private key file is available for this key."));
+
+        // And then the data itself
+        string results;
+        if (!FileUtils.get_contents(keydata.privfile, out results))
+            return null;
+
+        return results;
+    }
+
+    /**
+     * Loads the keys from this Source's directory.
+     *
+     * @param cancellable Use this to cancel the operation.
+     */
+    public async bool load(GLib.Cancellable? cancellable) throws GLib.Error {
+        Source src = this;
+        // Since we can find duplicate keys, limit them with this hash
+        GenericSet<string> loaded = new GenericSet<string>(str_hash, str_equal);
+        // Keys that currently exist, so we can remove any that disappeared
+        GenericSet<string> checks = load_present_keys();
+
+        // Schedule a dummy refresh. This blocks all monitoring for a while
+        cancel_scheduled_refresh();
+        this.scheduled_refresh_source = Timeout.add(500, scheduled_dummy);
+        debug("scheduled a dummy refresh");
+
+        // List the .ssh directory for private keys
+        Dir dir = Dir.open(this.ssh_homedir);
+
+        // Load each key file in ~/.ssh
+        string? filename = null;
+        while ((filename = dir.read_name()) != null) {
+            string privfile = Path.build_filename(this.ssh_homedir, filename);
+            load_key_for_private_file.begin(privfile);
+        }
+
+        // Now load the authorized keys file
+        string pubfile = file_for_public(true);
+        Key.parse_file.begin(pubfile, (keydata) => {
+            Source.add_key_from_parsed_data(src, keydata, pubfile, true, true, null, checks, loaded);
+            return true;
+        });
+
+        // Load the other keys file
+        pubfile = file_for_public(false);
+        Key.parse_file.begin(pubfile, (keydata) => {
+            Source.add_key_from_parsed_data(src, keydata, pubfile, true, false, null, checks, loaded);
+            return true;
+        });
+
+        return true;
+    }
+
+    private GenericSet<string> load_present_keys() {
+        GenericSet<string> checks = new GenericSet<string>(str_hash, str_equal);
+        this.keys.foreach((filename, key) => checks.add(filename));
+        return checks;
+    }
+
+    public static Key? add_key_from_parsed_data(Source src,
+                                                KeyData? keydata,
+                                                string pubfile,
+                                                bool partial,
+                                                bool authorized,
+                                                string? privfile = null,
+                                                GenericSet<string>? checks = null,
+                                                GenericSet<string>? loaded = null) {
+        if (keydata == null || !keydata.is_valid())
+            return null;
+
+        keydata.pubfile = pubfile;
+        keydata.partial = partial;
+        keydata.authorized = authorized;
+        if (privfile != null)
+            keydata.privfile = privfile;
+
+        string? location = keydata.get_location();
+        if (location == null)
+            return null;
+
+        // Does src key exist in the context?
+        Key? prev = src.keys.lookup(location);
+
+        // Mark src key as seen
+        if (checks != null)
+            checks.remove(location);
+
+        // See if we've already gotten a key like src in this load batch
+        if (loaded != null) {
+            if (location in loaded) {
+                if (prev != null)
+                    prev.merge_keydata(keydata);
+                return null;
+            }
+
+            // Mark src key as loaded
+            loaded.add(location);
+        }
+
+        // If we already have src key then just transfer ownership of keydata
+        if (prev != null) {
+            prev.key_data = keydata;
+            return prev;
+        }
+
+        // Create a new key
+        Key key = new Key(src, keydata);
+        src.keys.insert(location, key);
+        src.added(key);
+
+        return key;
+    }
+
+
+    /**
+     * Parse an inputstream into a list of keys and import those keys.
+     */
+    public async List<Key>? import_async(InputStream input, Gtk.Window transient_for,
+                                         Cancellable? cancellable = null) throws GLib.Error {
+        uint8[] buffer = new uint8[4096];
+        size_t bytes_read;
+        input.read_all(buffer, out bytes_read, cancellable);
+
+        string fullpath = file_for_public(false);
+        Source src = this;
+
+        Key.parse.begin((string) buffer,
+                        (keydata) => {
+                            import_public_async.begin(keydata, fullpath, cancellable);
+                            return true;
+                        },
+                        (secdata) => {
+                            new PrivateImportOperation().import_private_async.begin(src, secdata, null, 
cancellable);
+                            return true;
+                        });
+
+        // TODO: The list of keys imported?
+        return null;
+    }
+
+    /**
+     * Import a public key into a file.
+     *
+     * @param data The public key that needs to be imported.
+     * @param filename The name of the file in which the key should be imported.
+     * @param cancellable Allows the operation to be cancelled.
+     */
+    public async string import_public_async(KeyData data,
+                                            string filename,
+                                            Cancellable? cancellable) throws GLib.Error {
+        if (data == null || !data.is_valid() || data.rawdata == null)
+            throw new Error.GENERAL("Trying to import an invalid public key.");
+
+        KeyData.filter_file(filename, data);
+
+        return data.fingerprint;
+    }
+
+    /**
+     * Returns a possible filename for a key.
+     *
+     * @param algo The type of the key
+     */
+    public string? new_filename_for_algorithm(Algorithm algo) {
+        string type = algo.to_string() ?? "unk";
+        string basename = "id_%s".printf(type.down());
+
+        string? filename = null;
+        for (int i = 0; i < int.MAX; i++) {
+            string t = (i == 0) ? basename : "%s.%d".printf(basename, i);
+            filename = Path.build_filename(this.base_directory, t);
+
+            if (!FileUtils.test(filename, FileTest.EXISTS))
+                break;
+
+            filename = null;
+        }
+
+        return filename;
+    }
+
+    /**
+     * Adds/Removes a public key to/from the authorized keys.
+     *
+     * @param key The key that needs to be authorized.
+     * @param authorize Whether the specified key needs to be (de-)authorized.
+     */
+    public async void authorize_async(Key key, bool authorize) throws GLib.Error {
+        SourceFunc callback = authorize_async.callback;
+        GLib.Error? err = null;
+
+        Thread<void*> thread = new Thread<void*>("authorize-async", () => {
+            try {
+                KeyData? keydata = key.key_data;
+                if (keydata == null)
+                    throw new Error.GENERAL("Can't authorize empty key.");
+
+                if (!authorize) {
+                    // Take it out of the file for authorized keys
+                    string from = file_for_public(true);
+                    KeyData.filter_file(from, null, keydata);
+                }
+
+                // Put it into the correct file
+                string to = file_for_public(authorize);
+                KeyData.filter_file(to, keydata, null);
+
+                // Just reload that one key
+                key.refresh();
+            } catch (GLib.Error e) {
+                err = e;
+            }
+
+            Idle.add((owned)callback);
+            return null;
+        });
+
+        yield;
+
+        thread.join();
+
+        if (err != null)
+            throw err;
+    }
+}
diff --git a/ssh/seahorse-ssh.c b/ssh/ssh.vala
similarity index 84%
rename from ssh/seahorse-ssh.c
rename to ssh/ssh.vala
index 5bb06ea..5dc3cab 100644
--- a/ssh/seahorse-ssh.c
+++ b/ssh/ssh.vala
@@ -1,33 +1,26 @@
-/* 
+/*
  * Seahorse
- * 
+ *
  * Copyright (C) 2008 Stefan Walter
- * 
+ * Copyright (C) 2016 Niels De Graef
+ *
  * 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/>.
  */
 
-#include "seahorse-ssh.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-
-
-
-
-
-
+namespace Seahorse.Ssh {
 
+public const string SEAHORSE_SSH_NAME = "openssh";
 
+}
diff --git a/ssh/upload.vala b/ssh/upload.vala
new file mode 100644
index 0000000..67e311a
--- /dev/null
+++ b/ssh/upload.vala
@@ -0,0 +1,144 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.Ssh.Upload : Gtk.Dialog {
+
+    private unowned List<Key> keys;
+
+    private Gtk.Entry user_entry;
+    private Gtk.Entry host_entry;
+    private Gtk.Button ok_button;
+
+    public Upload(List<Key> keys, Gtk.Window? parent) {
+        GLib.Object(border_width: 5,
+                    title: _("Set Up Computer for SSH Connection"),
+                    resizable: false,
+                    default_width: 400,
+                    skip_taskbar_hint: true,
+                    skip_pager_hint: true,
+                    window_position: Gtk.WindowPosition.CENTER_ON_PARENT,
+                    modal: true,
+                    transient_for: parent);
+
+        this.keys = keys;
+
+        load_ui();
+
+        // Default to the users current name
+        this.user_entry.text = Environment.get_user_name();
+        // Focus the host
+        this.host_entry.grab_focus();
+
+        on_upload_input_changed();
+    }
+
+    // FIXME: normally we would do this using GtkTemplate, but this is quite hard with the current build 
setup
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/seahorse-ssh-upload.ui";
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("ssh-upload");
+        ((Gtk.Container)this.get_content_area()).add(content);
+
+        this.host_entry = (Gtk.Entry) builder.get_object("host-entry");
+        this.host_entry.changed.connect(on_upload_input_changed);
+        this.user_entry = (Gtk.Entry) builder.get_object("user-entry");
+        this.user_entry.changed.connect(on_upload_input_changed);
+
+        this.ok_button = (Gtk.Button) add_button(_("Set up"), Gtk.ResponseType.OK);
+    }
+
+    private void upload_keys() {
+        string user = this.user_entry.text.strip();
+        string host_port = this.host_entry.text.strip();
+
+        if (!user.validate() || host_port == "" || !host_port.validate())
+            return;
+
+        // Port is anything past a colon
+        string[] host_port_split = host_port.split(":", 2);
+        string host = host_port_split[0];
+        string? port = (host_port_split.length == 2)? host_port_split[1] : null;
+
+        Cancellable cancellable = new Cancellable();
+
+        // Start the upload process
+        UploadOperation op = new UploadOperation();
+        op.upload_async.begin(keys, user, host, port, cancellable, (obj, res) => {
+            try {
+                op.upload_async.end(res);
+            } catch (GLib.Error e) {
+                Seahorse.Util.show_error(this, _("Couldn’t configure Secure Shell keys on remote 
computer."), e.message);
+            }
+        });
+
+        Seahorse.Progress.show(cancellable, _("Configuring Secure Shell Keys…"), false);
+    }
+
+    private void on_upload_input_changed () {
+        string user = this.user_entry.text;
+        string host = this.host_entry.text;
+
+        if (!user.validate() || !host.validate())
+            return;
+
+        // Take off port if necessary
+        int port_pos = host.index_of_char(':');
+        if (port_pos > 0) {
+            host = host.substring(0, port_pos);
+        }
+
+        this.ok_button.sensitive = (host.strip() != "") && (user.strip() != "");
+    }
+
+    /**
+     * Prompt a dialog to upload keys.
+     *
+     * @param keys The set of SSH keys that should be uploaded
+     */
+    public static void prompt(List<Key>? keys, Gtk.Window? parent) {
+        if (keys == null)
+            return;
+
+        Upload upload_dialog = new Upload(keys, parent);
+        for (;;) {
+            switch (upload_dialog.run()) {
+            case Gtk.ResponseType.HELP:
+                /* TODO: Help */
+                continue;
+            case Gtk.ResponseType.ACCEPT:
+                upload_dialog.upload_keys();
+                break;
+            default:
+                break;
+            };
+
+            break;
+        }
+
+        upload_dialog.destroy();
+    }
+
+}


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