[seahorse/wip/nielsdg/pgp-vala] WIP



commit 1afa55a9e80ba21e33ba79d9867eb1d63dff2154
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Fri Dec 1 13:21:44 2017 +0100

    WIP

 common/config.vapi                |    4 +
 meson.build                       |    4 +-
 pgp/combo-keys.vala               |  245 ++++
 pgp/discovery.vala                |  242 ++++
 pgp/gpg-op.vala                   |   89 ++
 pgp/gpg-options.vala              |  311 ++++++
 pgp/gpgme-add-subkey.ui           |  213 ++++
 pgp/gpgme-add-subkey.vala         |  155 +++
 pgp/gpgme-add-uid.ui              |  182 +++
 pgp/gpgme-add-uid.vala            |   79 ++
 pgp/gpgme-algorithm.vala          |  109 ++
 pgp/gpgme-data.vala               |  367 ++++++
 pgp/gpgme-expires.ui              |  141 +++
 pgp/gpgme-expires.vala            |  101 ++
 pgp/gpgme-exporter.vala           |  213 ++++
 pgp/gpgme-generate.ui             |  435 ++++++++
 pgp/gpgme-generate.vala           |  206 ++++
 pgp/gpgme-key-deleter.vala        |   81 ++
 pgp/gpgme-key-op.vala             | 2223 +++++++++++++++++++++++++++++++++++++
 pgp/gpgme-key.vala                |  422 +++++++
 pgp/gpgme-keyring.vala            |  605 ++++++++++
 pgp/gpgme-photo.vala              |   38 +
 pgp/gpgme-photos.vala             |  267 +++++
 pgp/gpgme-revoke.ui               |  189 ++++
 pgp/gpgme-revoke.vala             |  130 +++
 pgp/gpgme-secret-deleter.vala     |   69 ++
 pgp/gpgme-sign.ui                 |  533 +++++++++
 pgp/gpgme-sign.vala               |  173 +++
 pgp/gpgme-source.vala             |  200 ++++
 pgp/gpgme-subkey.vala             |   98 ++
 pgp/gpgme-uid.vala                |  176 +++
 pgp/gpgme-validity.vala           |   79 ++
 pgp/gpgme.vala                    |  182 +++
 pgp/hkp-source.vala               |  710 ++++++++++++
 pgp/keyserver-results.ui          |   87 ++
 pgp/keyserver-results.vala        |  265 +++++
 pgp/keyserver-search.ui           |  254 +++++
 pgp/keyserver-search.vala         |  360 ++++++
 pgp/keyserver-sync.ui             |  180 +++
 pgp/keyserver-sync.vala           |  145 +++
 pgp/ldap-source.vala              | 1008 +++++++++++++++++
 pgp/meson.build                   |   28 +-
 pgp/pgp-actions.vala              |  130 +++
 pgp/pgp-backend.vala              |  345 ++++++
 pgp/pgp-key-properties.vala       | 1197 ++++++++++++++++++++
 pgp/pgp-key.vala                  |  288 +++++
 pgp/pgp-keysets.vala              |   54 +
 pgp/pgp-photo.vala                |   30 +
 pgp/pgp-private-key-properties.ui | 1813 ++++++++++++++++++++++++++++++
 pgp/pgp-public-key-properties.ui  | 1666 +++++++++++++++++++++++++++
 pgp/pgp-signature.vala            |   54 +
 pgp/pgp-subkey.vala               |  135 +++
 pgp/pgp-uid.vala                  |  262 +++++
 pgp/pgp.vala                      |   28 +
 pgp/seahorse-gpg-options.c        |   62 +-
 pgp/server-source.vala            |  173 +++
 pgp/signer.ui                     |  106 ++
 pgp/signer.vala                   |   86 ++
 pgp/transfer.vala                 |  127 +++
 pgp/unknown-source.vala           |   83 ++
 pgp/unknown.vala                  |   27 +
 pgp/xloadimage.c                  |   55 +
 vapi/gpg-error.vapi               |  429 +++++++
 vapi/gpgme.deps                   |    1 +
 vapi/gpgme.vapi                   | 1510 +++++++++++++++++++++++++
 65 files changed, 20189 insertions(+), 70 deletions(-)
---
diff --git a/common/config.vapi b/common/config.vapi
index 54bbddc..9468bf3 100644
--- a/common/config.vapi
+++ b/common/config.vapi
@@ -51,4 +51,8 @@ namespace Progress {
        public void show(GLib.Cancellable? cancellable, string title, bool delayed);
 }
 
+[SimpleType]
+[CCode (has_type_id = false)]
+public struct Version : uint64 {
+}
 }
diff --git a/meson.build b/meson.build
index ae30696..6529f50 100644
--- a/meson.build
+++ b/meson.build
@@ -33,7 +33,7 @@ localedir = join_paths(seahorse_prefix, get_option('localedir'))
 min_glib_version = '2.44'
 min_gcr_version = '3.11.91'
 min_gpgme_version = '1.7.0'
-accepted_gpg_versions = [ '2.0.12', '2.1.4', '2.2.0' ]
+accepted_gpg_versions = [ '2.1.13', '2.2.0' ]
 gpg_check_version = find_program(join_paths('meson', 'gpg_check_version.py'))
 
 glib_deps = [
@@ -86,7 +86,7 @@ endif
 
 libsoup = dependency('libsoup-2.4', version: '>= 2.33.92', required: with_hkp)
 avahi_client = dependency('avahi-client', required: with_key_sharing)
-avahi_glib = dependency('avahi-glib', version: '>= 0.6', required: with_key_sharing)
+avahi_gobject = dependency('avahi-gobject', version: '>= 0.6', required: with_key_sharing)
 
 
 # Project-wide flags
diff --git a/pgp/combo-keys.vala b/pgp/combo-keys.vala
new file mode 100644
index 0000000..e0f96b1
--- /dev/null
+++ b/pgp/combo-keys.vala
@@ -0,0 +1,245 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004-2006 Stefan Walter
+ * Copyright (C) 2017 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.ComboKeys {
+
+public void attach(Gtk.ComboBox combo, Gcr.Collection collection, string none_option) {
+    combo.set_data_full("combo-keys-closure", new ComboClosure(), combo_closure_free);
+
+    /* Setup the None Option */
+    Gtk.TreeModel? model = combo.model;
+    if (model == null) {
+        model = new Gtk.ListStore(N_COLUMNS, typeof(string), typeof(string), typeof(void*));
+        combo.set_model(model);
+
+        combo.clear();
+        Gtk.CellRenderer renderer = new Gtk.CellRendererText();
+
+        combo.pack_start (renderer, true);
+        combo.add_attribute (renderer, "markup", COMBO_MARKUP);
+    }
+
+    /* Setup the object list */
+    foreach (weak GLib.Object obj in collection.get_objects ())
+        on_collection_added (collection, obj, combo);
+
+    collection.added.connect(on_collection_added);
+    collection.removed.connect(on_collection_removed);
+
+    if (none_option) {
+        model.prepend(&iter);
+        model.set(&iter, COMBO_LABEL, null,
+                         COMBO_MARKUP, none_option,
+                         COMBO_POINTER, null, -1);
+    }
+
+    Gtk.TreeIter iter;
+    model.get_iter_first (out iter);
+    combo.set_active_iter(iter);
+
+    combo.destroy.connect(() => {
+        Gcr.Collection collection = GCR_COLLECTION (user_data);
+        foreach (GLib.Object object in collection.get_objects())
+            SignalHandler.disconnect_by_func((void*) l.data, (void*) on_label_changed, combo);
+        SignalHandler.disconnect_by_func((void*) collection, (void*) on_collection_added, combo);
+        SignalHandler.disconnect_by_func((void*) collection, (void*) on_collection_removed, combo);
+    });
+}
+
+public void set_active_id(Gtk.ComboBox combo, string? keyid) {
+    Gtk.TreeModel model = combo.model;
+    g_return_if_fail (model != null);
+
+    Gtk.TreeIter iter;
+    bool valid = model.get_iter_first (out iter);
+    uint i = 0;
+
+    while (valid) {
+        void* pointer;
+        model.get(iter, COMBO_POINTER, out pointer, -1);
+
+        Pgp.Key? key = pointer as Pgp.Key;
+
+        if (keyid == null) {
+            if (key == null) {
+                combo.set_active_iter(iter);
+                break;
+            }
+        } else if (key != null) {
+            if (key.get_keyid() == keyid) {
+                combo.set_active_iter(iter);
+                break;
+            }
+        }
+
+        valid = model.iter_next (&iter);
+        i++;
+    }
+}
+
+public void set_active(Gtk.ComboBox combo, Pgp.Key key) {
+    set_active_id(combo, key == null ? null : key.keyid);
+}
+
+public Pgp.Key get_active(Gtk.ComboBox combo) {
+    if (combo == null || combo.model == null)
+        return null;
+
+    Gtk.TreeIter iter;
+    combo.get_active_iter(out iter);
+
+    void* pointer;
+    combo.model.get(iter, COMBO_POINTER, out pointer, -1);
+
+    return (Pgp.Key) pointer;
+}
+
+public string get_active_id(Gtk.ComboBox combo) {
+    Pgp.Key key = ComboKeys.get_active(combo);
+    return (key == null)? 0 : key.get_keyid();
+}
+
+private enum Column {
+  COMBO_LABEL,
+  COMBO_MARKUP,
+  COMBO_POINTER,
+  N_COLUMNS
+}
+
+private class ComboClosure {
+    GenericSet<string> labels;
+    bool collision;
+
+    public ComboClosure () {
+        this.labels = new GenericSet<string>(str_hash, str_equal);
+    }
+}
+
+private void refresh_all_markup_in_combo(Gtk.ComboBox combo) {
+    Gtk.TreeModel model = combo.get_model();
+    Gtk.TreeIter iter;
+    bool valid = model.get_iter_first (out iter);
+
+    while (valid) {
+        void* object;
+        model.get(iter, COMBO_POINTER, out object, -1);
+        g_object_notify (object, "label");
+        valid = model.iter_next(&iter);
+    }
+}
+
+private string calculate_markup_for_object(Gtk.ComboBox combo, string label, Object object) {
+    ComboClosure closure = combo.get_data("combo-keys-closure");
+
+    if (!closure.collision) {
+        if (closure.labels.contains(label)) {
+            closure.collision = true;
+            refresh_all_markup_in_combo (combo);
+        } else {
+            closure.labels.add(label);
+        }
+    }
+
+    string markup;
+    if (closure.collision && (object is Pgp.Key)) {
+        string ident = Pgp.Key.calc_identifier(((Pgp.Key) object).get_keyid());
+        markup = Markup.printf_escaped("%s <span size='small'>[%s]</span>", label, ident);
+    } else {
+        markup = Markup.escape_text(label, -1);
+    }
+
+    return markup;
+}
+
+private void on_label_changed(GLib.Object obj, ParamSpec param) {
+    Object object = SEAHORSE_OBJECT (obj);
+    Gtk.ComboBox combo = GTK_COMBO_BOX (user_data);
+    ComboClosure closure;
+    Gtk.TreeModel? model;
+    Gtk.TreeIter iter;
+
+    closure = combo.get_data("combo-keys-closure");
+    Gtk.TreeModel model = combo.get_model();
+
+    bool valid = model.get_iter_first(&iter);
+    while (valid) {
+        string previous;
+        void* pntr;
+        model.get(&iter, COMBO_POINTER, &pntr, COMBO_LABEL, &previous, -1);
+        if (SEAHORSE_OBJECT (pntr) == object) {
+
+            /* Remove this from label collision checks */
+            closure.labels.remove(previous);
+
+            /* Calculate markup taking into account label collisions */
+            string markup = calculate_markup_for_object (combo, object.label, object);
+            model.set(iter, COMBO_LABEL, object.label,
+                            COMBO_MARKUP, markup, -1);
+            break;
+        }
+
+        valid = model.iter_next(&iter);
+    }
+}
+
+private void on_collection_added(Gcr.Collection collection, GLib.Object obj) {
+    Object object = SEAHORSE_OBJECT (obj);
+    Gtk.ComboBox combo = GTK_COMBO_BOX (user_data);
+
+    Gtk.ListStore model = GTK_LIST_STORE (combo.get_model());
+
+    string markup = calculate_markup_for_object (combo, object.label, object);
+
+    Gtk.TreeIter iter;
+    model.append(out iter);
+    model.set(iter, COMBO_LABEL, object.label,
+                    COMBO_MARKUP, markup,
+                    COMBO_POINTER, object, -1);
+
+    object.notify["label"].connect(on_label_changed, combo);
+}
+
+private void on_collection_removed (Gcr.Collection collection, GLib.Object obj) {
+    ComboClosure closure = g_object_get_data (user_data, "combo-keys-closure");
+    Object object = SEAHORSE_OBJECT (obj);
+    Gtk.ComboBox combo = GTK_COMBO_BOX (user_data);
+    Gtk.TreeModel? model = combo.get_model();
+    Gtk.TreeIter iter;
+    bool valid = model.get_iter_first(out iter);
+    while (valid) {
+        char previous;
+        void* pntr;
+        model.get(iter, COMBO_LABEL, out previous,
+                        COMBO_POINTER, out pntr, -1);
+
+        if (SEAHORSE_OBJECT (pntr) == object) {
+            closure.labels.remove(previous);
+            model.remove(&iter);
+            break;
+        }
+
+        valid = model.iter_next(ref iter);
+    }
+
+    SignalHandler.disconnect_by_func((void*) object, (void*) on_label_changed, combo);
+}
+
+
+}
diff --git a/pgp/discovery.vala b/pgp/discovery.vala
new file mode 100644
index 0000000..cf40f62
--- /dev/null
+++ b/pgp/discovery.vala
@@ -0,0 +1,242 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2017 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/>.
+ */
+
+/**
+ * Seahorse Service Discovery handles (finds, removes,...) Avahi services
+ */
+public class Seahorse.Discovery : GLib.Object {
+
+    public HashTable<string, string> services;
+
+#if WITH_SHARING
+        Avahi.Client client;
+        Avahi.ServiceBrowser browser;
+        Avahi.GLibPoll poll;
+#else
+        char no_use;
+#endif
+
+    public const string HKP_SERVICE_TYPE = "_pgpkey-hkp._tcp.";
+
+    /*
+     * DEBUG: Define if we want to show discoveries from this
+     * host. Normally these are suppressed
+     */
+    /* #define DISCOVER_THIS_HOST 1 */
+
+    /**
+     * Fired when a relevant service appeared on the network
+     */
+    public signal void added(string service);
+
+    /**
+     * Fired when a relevant service disappeared
+     */
+    public signal void removed(string service);
+
+    /**
+     * When compiled WITH_SHARING avahi will also be initialised and it will browse
+     * for avahi services.
+     */
+    public Discovery() {
+        this.services = new HashTable<string, string>();
+
+#if WITH_SHARING
+        /* avahi_set_allocator (avahi_glib_allocator ()); */
+
+        /* this.poll = avahi_glib_poll_new (null, G_PRIORITY_DEFAULT); */
+        /* if (!this.poll) { */
+        /*     warning ("couldn't initialize avahi glib poll integration"); */
+        /*     return; */
+        /* } */
+
+        this.client = new Avahi.Client();
+        // XXX does the start() function take care of the glib poll?
+        //this.client = new Avahi.Client(avahi_glib_poll_get (this.poll));
+        try {
+            this.client.start();
+        } catch (Avahi.Error e) {
+            critical("DNS-SD initialization failed: %s", e.message);
+            return;
+        }
+
+        this.browser = new Avahi.ServiceBrowser.full(Avahi.Interface.UNSPEC, Avahi.Protocol.UNSPEC, 
HKP_SERVICE_TYPE);
+        this.browser.new_service.connect(browse_callback_new);
+        this.browser.remove_service.connect(browse_callback_remove);
+        this.browser.failure.connect((err) => {
+            warning("failure browsing for services: %s", err.message);
+            disconnect();
+        });
+        try {
+            this.browser.attach_client(this.client);
+        } catch (Avahi.Error e) {
+            critical("Browsing for DNS-SD services failed: %s", e.message);
+            return;
+        }
+#endif
+    }
+
+#if WITH_SHARING
+    ~Discovery() {
+        disconnect ();
+    }
+#endif
+
+    /**
+     * Lists all services in this discovery.
+     */
+    public string[] list() {
+        return this.services.get_keys_as_array();
+    }
+
+    /**
+     * Returns: The URI of the service @service in @self
+     *
+     * @param service The service to get the uri for
+     */
+    public string? get_uri(string service) {
+        return this.services.lookup(service);
+    }
+
+    /**
+     * The returned uris in the list are copied and must be freed with g_free.
+     *
+     * @param services A list of services
+     *
+     * @return uris for the services
+     */
+    public string?[] get_uris(string[] services) {
+        string[] uris = new string[services.length];
+
+        for (int i = 0; i < services.length; i++)
+            uris[i] = this.services.lookup(services[i]);
+
+        return uris;
+    }
+
+    /* -----------------------------------------------------------------------------
+     * INTERNAL
+     */
+
+#if WITH_SHARING
+
+    /**
+     * Disconnects avahi
+     */
+    private void disconnect() {
+        if (this.browser != null && this.client != null)
+            avahi_service_browser_free (this.browser);
+        this.browser = null;
+
+        if (this.client != null)
+            avahi_client_free (this.client);
+        this.client = null;
+
+        if (this.poll != null)
+            avahi_glib_poll_free (this.poll);
+        this.poll = null;
+    }
+
+    private void resolve_callback(Avahi.Interface iface, Avahi.Protocol proto, string name,
+                                  string type, string domain, string host_name,
+                                  Avahi.Address? address, uint16 port, Avahi.StringList? txt,
+                                  Avahi.LookupResultFlags flags) {
+        // Make sure it's our type ...
+        // XXX Is the service always guaranteed to be ascii?
+        if (HKP_SERVICE_TYPE.ascii_strcasecmp (type) != 0)
+            break;
+
+#if ! DISCOVER_THIS_HOST
+        // And that it's local
+        if (Avahi.LookupResultFlags.LOCAL in flags)
+            return;
+#endif
+
+        string service_uri = "hkp://%s:%d".printf(address.to_string(), (int)port);
+
+        this.services.replace(name, service_uri);
+        added(0, name);
+
+        // Add it to the context
+        if (Pgp.Backend.get().lookup_remote(service_uri) == null) {
+            SeahorseServerSource *ssrc = seahorse_server_source_new (service_uri);
+            if (src == null)
+                return;
+            Pgp.Backend.get().add_remote(service_uri, ssrc);
+        }
+
+        debug("added: %s %s\n", name, service_uri);
+    }
+
+    private void browse_callback_new(Avahi.Interface iface, Avahi.Protocol proto, string name,
+                                 string type, string domain, Avahi.LookupResultFlags flags) {
+        // XXX Is the service always guaranteed to be ascii?
+        if (HKP_SERVICE_TYPE.ascii_strcasecmp(type) != 0)
+            return;
+
+        Avahi.ServiceResolver resolver = new Avahi.ServiceResolver(iface, proto, name, type,
+                                                                   domain, Avahi.Protocol.UNSPEC);
+        resolver.found.connect(resolve_callback);
+        resolver.found.connect((err) => warning ("couldn't resolve service '%s': %s", name, err.message));
+        try {
+            resolver.attach(this.client);
+        } catch (Error e) {
+            warning("couldn't start resolver for service '%s': %s\n", name, e.message);
+        }
+    }
+
+    private void browse_callback_remove(Avahi.Interface iface, Avahi.Protocol proto, string name,
+                                 string type, string domain, Avahi.LookupResultFlags flags) {
+        // XXX Is the service always guaranteed to be ascii?
+        if (HKP_SERVICE_TYPE.ascii_strcasecmp(type) != 0)
+            return;
+
+        string? uri = this.services.lookup(name);
+
+        // Remove it from the main context
+        if (uri != null)
+            Pgp.Backend.remove_remote(uri);
+
+        // And remove it from our tables
+        this.services.remove(name);
+        removed(0, name);
+        debug("removed: %s\n", name);
+    }
+
+    /**
+     * Handles AVAHI_CLIENT_FAILURE errors in state
+     *
+     * @param client Will be part of a potential error message
+     * @param state the state of the avahi connection
+     */
+    private void client_callback (AvahiClient *client, AvahiClientState state) {
+        // Disconnect when failed
+        if (state == AVAHI_CLIENT_FAILURE) {
+            if (client != this.client)
+                return;
+
+            warning("failure communicating with to avahi: %s",
+                    avahi_strerror (avahi_client_errno (client)));
+            disconnect();
+        }
+    }
+
+#endif /* WITH_SHARING */
+}
diff --git a/pgp/gpg-op.vala b/pgp/gpg-op.vala
new file mode 100644
index 0000000..cf03e1b
--- /dev/null
+++ b/pgp/gpg-op.vala
@@ -0,0 +1,89 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004 Stefan Walter
+ * Copyright (C) 2017 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/>.
+ */
+
+// XXX namespace or class?
+namespace Seahorse.Gpg.Operation {
+
+public GPG.ErrorCode export_secret(GPG.Context ctx, string[] patterns, GPG.Data keydata) {
+    if (patterns == null)
+        return GPG_E (GPG_ERR_INV_VALUE);
+
+    for (size_t i = 0; patterns[i] != null; i++) {
+        string args = "--armor --export-secret-key '%s'".printf(patterns[i]);
+        string output = null;
+        GPG.ErrorCode gerr = execute_gpg_command(ctx, args, out output);
+
+        if (!gerr.is_success())
+            return gerr;
+
+        if (keydata.write(output, strlen (output)) == -1)
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    return GPG_OK;
+}
+
+public GPG.ErrorCode num_uids(GPG.Context ctx, string pattern, out uint number) {
+    if (pattern != null)
+        return GPG_E (GPG_ERR_INV_VALUE);
+
+    string args = "--list-keys '%s'".printf(pattern);
+    string output;
+    GPG.ErrorCode err = execute_gpg_command(ctx, args, out output);
+    if (!err.is_success())
+        return err;
+
+    string found = output;
+    while ((found = strstr(found, "uid")) != null) {
+        *number = *number + 1;
+        found += 3;
+    }
+
+    return GPG_OK;
+}
+
+private GPG.ErrorCode execute_gpg_command(GPG.Context ctx, string args, out string? std_out = null, out 
string? std_err = null) {
+    GPG.EngineInfo engine;
+    GPG.ErrorCode gerr = GPG.get_engine_information(out engine);
+    if (gerr.is_success())
+        return gerr;
+
+    // Look for the OpenPGP engine
+    while (engine != null && engine.protocol != GPG.Protocol.OpenPGP)
+        engine = engine.next;
+
+    if(engine == null || engine.file_name == null)
+        return GPG_E (GPG_ERR_INV_ENGINE);
+
+    gerr = GPG_OK;
+
+    string cmd = "%s --batch %s".printf(engine.file_name, args);
+    try {
+        int status;
+        Process.spawn_command_line_sync(cmd, std_out, std_err, out status);
+        if (status == 0)
+            return GPG_E (GPG_OK);
+    } catch (GLib.Error e) {
+    }
+
+    return GPG_E (GPG_ERR_GENERAL);
+}
+
+}
diff --git a/pgp/gpg-options.vala b/pgp/gpg-options.vala
new file mode 100644
index 0000000..d84c884
--- /dev/null
+++ b/pgp/gpg-options.vala
@@ -0,0 +1,311 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Stefan Walter
+ * Copyright (C) 2017 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.Gpg.Options {
+
+public const string GPG_CONF_HEADER = "# FILE CREATED BY SEAHORSE\n\n";
+public const string GPG_VERSION_PREFIX1 = "1.";
+public const string GPG_VERSION_PREFIX2 = "2.";
+
+public const string HOME_PREFIX = "\nHome: ";
+
+/* Initializes the gpg-options static info */
+private bool gpg_options_init() throws GLib.Error {
+    if (gpg_options_inited)
+        return true;
+
+    GPG.EngineInfo engine_info;
+    GPGError.ErrorCode gerr = GPG.get_engine_info(out engine_info);
+    if (seahorse_gpgme_propagate_error (gerr, err))
+        return false;
+
+    /* Look for the OpenPGP engine_info */
+    while (engine_info != null && engine.protocol != GPGME_PROTOCOL_OpenPGP)
+        engine_info = engine.next;
+
+    // Make sure it's the right version for us to be messing
+    // around with the configuration file.
+    if (engine_info == null || engine.version == null || engine.file_name == null ||
+        !(engine_info.version.has_prefix(GPG_VERSION_PREFIX1) ||
+          engine_info.version.has_prefix(GPG_VERSION_PREFIX2))) {
+        seahorse_gpgme_propagate_error (GPG_E (GPG_ERR_INV_ENGINE), err);
+        return false;
+    }
+
+    // Read in the home directory
+    gpg_homedir = engine_info.home_dir ?? GPG.get_dirinfo("homedir");
+
+    gpg_options_inited = true;
+    return true;
+}
+
+/**
+ * Returns: The home dir that GPG uses for it's keys and configuration
+ **/
+public string? seahorse_gpg_homedir() {
+    if (!Options.init(null))
+        return null;
+    return gpg_homedir;
+}
+
+/**
+ * Find the value for a given option in the gpg config file.
+ * Values without a value are returned as an empty string.
+ * On success be sure to free *value after you're done with it.
+ *
+ * @param option The option to find
+ *
+ * @return the option's value, or null when not found
+ **/
+public string? find(string option) throws GLib.Error {
+    string[] values = find_vals ({option}, value, err);
+    return (values.length > 0)? values[0] : null;
+}
+
+/**
+ * Find the values for the given options in the gpg config file.
+ *
+ * @param option null terminated array of option names
+ *
+ * @return An array of values. Values without a value are returned as an empty string.
+ **/
+public string[] find_vals(string[] options) throws GLib.Error {
+    string[] values = new string[lines.length];
+
+    Options.init();
+    string[] lines = read_config_file();
+    foreach (string l in lines) {
+        string line = l.strip();
+
+        // Ignore comments and blank lines
+        if (line[0] == '#' || line == "")
+            continue;
+
+        for (int i = 0; i < options.length; i++) {
+            string option = options[i];
+            if (line.has_prefix(option)) {
+                string t = line.substring(option.length);
+                if (t[0] == 0 || t[0].ascii_isspace()) {
+                    // NOTE: we don't short-circuit the search because for GPG, options can be
+                    // specified multiple times, and the last one wins.
+                    values[i] = t.strip();
+                    break;
+                }
+            }
+        }
+    }
+
+    return values;
+}
+
+/* Figure out needed changes to configuration file */
+private void process_conf_edits (string[] lines, string[] options, string[] values) {
+    for (int i = 0; i < lines.length; i++) {
+        string line = line[i];
+        assert(line != null);
+
+        // Does this line have an ending?
+        // We use this below when appending lines.
+        string n = line.strip();
+
+        // Don't use g_strstrip as we don't want to modify the line
+        while (*n && g_ascii_isspace (*n))
+            n++;
+
+        // Ignore blank lines
+        bool comment;
+        if (n[0] != 0) {
+            comment = false;
+
+            // We look behind comments to see if we need to uncomment them
+            if (n[0] == '#') {
+                n++;
+                comment = true;
+
+                while (*n && n[0].ascii_isspace())
+                    n++;
+            }
+
+            for (uint j = 0; options[j] != null; j++) {
+                string option = options[j];
+                if (!n.has_prefix(options[j]))
+                    continue;
+
+                string t = n.substring(option.length);
+                if (t[0] != 0 && !t.ascii_isspace())
+                    continue;
+
+                // Are we setting this value?
+                if (values[j]) {
+                    // At this point we're rewriting the line, so we
+                    // can modify the old line
+                    *t = 0;
+
+                    // A line with a value
+                    if (values[j][0])
+                        n = n + " " + values[j];
+
+                    // We're done with this option, all other instances
+                    // of it need to be commented out
+                    values[j] = null;
+                }
+
+                // Otherwise we're removing the value
+                else if (!comment) {
+                    n = "# " + n;
+                }
+
+                line = n;
+
+                // Done with this line
+                break;
+            }
+        }
+
+        if (lines[j] != line)
+            lines[j] = line;
+    }
+
+    // Append any that haven't been added but need to */
+    for (uint i = 0; i < options.length; i++) {
+        // Are we setting this value?
+        if (values[i] != null) {
+            string n = options[i];
+            // A line with a value
+            if (values[i][0] != null)
+                n = n + " " + values[i];
+
+            lines += n;
+        }
+    }
+}
+
+/**
+ * Changes the given option in the gpg config file.
+ *
+ * @param option The option to change
+ * @param value The value to change it to. If value is null, the option will be deleted.
+ *              If you want an empty value, set value to an empty string.
+ */
+public void change(string option, string? value) throws Glib.Error {
+    change_vals({option}, {value});
+}
+
+/**
+ * Changes the given option in the gpg config file.
+ * If a value is null, the option will be deleted. If you want
+ * an empty value, set value to an empty string.
+ *
+ * @param option null-terminated array of option names to change
+ * @param value The values to change respective option to
+ *
+ * @return true if success, false if not XXX change to void
+ **/
+public void change_vals(string[] options, string[] values) throws GLib.Error {
+    gpg_options_init();
+
+    string[] lines = read_config_file();
+    process_conf_edits(lines, options, values);
+    write_config_file(lines);
+}
+
+private string? gpg_homedir;
+private bool gpg_options_inited = false;
+
+private bool create_file(string file, mode_t mode) throws GLib.Error {
+    int fd;
+
+    if ((fd = open (file, O_CREAT | O_TRUNC | O_WRONLY, mode)) == -1) {
+        g_set_error (err, G_IO_CHANNEL_ERROR, g_io_channel_error_from_errno (errno),
+                     "%s", g_strerror (errno));
+        return false;
+    }
+
+    /* Write the header when we make a new file */
+    if (write (fd, GPG_CONF_HEADER, strlen (GPG_CONF_HEADER)) == -1) {
+        g_set_error (err, G_IO_CHANNEL_ERROR, g_io_channel_error_from_errno (errno),
+                     "%s", strerror (errno));
+    }
+
+    close (fd);
+    return *err ? false : true;
+}
+
+/* Finds relevant configuration file, creates if not found */
+private string? find_config_file(bool read) throws GLib.Error {
+    if (gpg_options_inited == null || gpg_homedir == null)
+        return null;
+
+    // Check for and open ~/.gnupg/gpg.conf
+    string conf = gpg_homedir + "/gpg.conf";
+    if (FileUtils.test(conf, FileTest.IS_REGULAR | FileTest.EXISTS))
+        return conf;
+
+    // Check for and open ~/.gnupg/options */
+    conf = gpg_homedir + "/options";
+    if (FileUtils.test(conf, FileTest.IS_REGULAR | FileTest.EXISTS))
+        return conf;
+
+    // Make sure directory exists
+    if (!FileUtils.test(gpg_homedir, FileTest.EXISTS)) {
+        if (Posix.mkdir(gpg_homedir, 0700) == -1) {
+            g_set_error (err, G_IO_CHANNEL_ERROR,
+                         g_io_channel_error_from_errno (errno),
+                         "%s", strerror (errno));
+            return null;
+        }
+    }
+
+    // For writers just return the file name
+    conf = gpg_homedir + "/gpg.conf";
+    if (!read)
+        return conf;
+
+    // ... for readers we create ~/.gnupg/gpg.conf
+    if (create_file (conf, 0600, err))
+        return conf;
+
+    return null;
+}
+
+private string[] read_config_file() throws GLib.Error {
+    string? conf = find_config_file(true);
+    if (conf == null)
+        return null;
+
+    string contents;
+    FileUtils.get_contents(conf, out contents);
+
+    // We took ownership of the individual lines
+    return contents.split("\n");
+}
+
+private bool write_config_file(string[] lines) throws GLib.Error {
+    string? conf = find_config_file(false);
+    if (conf == null)
+        return false;
+
+    string[] contents = string.joinv("\n", lines);
+    Util.write_file_private(conf, contents, err);
+
+    return *err ? false : true;
+}
+
+}
diff --git a/pgp/gpgme-add-subkey.ui b/pgp/gpgme-add-subkey.ui
new file mode 100644
index 0000000..0e1dabb
--- /dev/null
+++ b/pgp/gpgme-add-subkey.ui
@@ -0,0 +1,213 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="value">1024</property>
+    <property name="lower">768</property>
+    <property name="upper">1024</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">128</property>
+  </object>
+  <object class="GtkDialog" id="add-subkey">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="n_rows">3</property>
+            <property name="n_columns">3</property>
+            <property name="column_spacing">12</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <object class="GtkCheckButton" id="never_expires">
+                <property name="label" translatable="yes">Never E_xpires</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="tooltip_text" translatable="yes">If key never expires</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">2</property>
+                <property name="right_attach">3</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="x_options"></property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="type_combo">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">3</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Key _Type:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">type</property>
+              </object>
+              <packing>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Key _Length:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">length-spinner</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="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Expiration Date:</property>
+              </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="GtkAlignment" id="alignment1">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="xscale">0</property>
+                <child>
+                  <object class="GtkSpinButton" id="length_spinner">
+                    <property name="width_request">80</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="tooltip_text" translatable="yes">Length of Key</property>
+                    <property name="adjustment">adjustment1</property>
+                    <property name="climb_rate">128</property>
+                    <property name="snap_to_ticks">True</property>
+                    <property name="numeric">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_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">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkAlignment" id="datetime_placeholder">
+                <property name="visible">True</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </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">GTK_FILL</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <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="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>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="cancelbutton1">
+                <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">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok_button">
+                <property name="label">gtk-ok</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="tooltip_text" translatable="yes">Generate a new subkey</property>
+                <property name="use_stock">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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">okbutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-add-subkey.vala b/pgp/gpgme-add-subkey.vala
new file mode 100644
index 0000000..df3dc39
--- /dev/null
+++ b/pgp/gpgme-add-subkey.vala
@@ -0,0 +1,155 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2017 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.GpgME.AddSubkey : Gtk.Dialog {
+
+    private Key key;
+
+    private Gtk.SpinButton length_spinner;
+    private Gtk.ComboBox type_combo;
+    private Gtk.CheckButton never_expires;
+    private Egg.DateTime expires_datetime;
+
+    private const string LENGTH = "length";
+
+    private enum Column {
+      NAME,
+      TYPE,
+      N_COLUMNS;
+    }
+
+    public AddSubkey(Key key, Gtk.Window? parent) {
+        this.key = key;
+
+        this.title = _("Add subkey to %s").printf(pkey.label);
+
+        Gtk.ListStore model = new Gtk.ListStore(Column.N_COLUMNS, typeof(string), typeof(int));
+
+        this.type_combo.set_model(model);
+        this.type_combo.clear();
+
+        Gtk.CellRendererText renderer = new Gtk.CellRendererText();
+
+        this.type_combo.pack_start(renderer, true);
+        this.type_combo.add_attribute(renderer, "text", Column.NAME);
+
+        Gtk.TreeIter iter;
+        model.append(out iter);
+        model.set(iter, Column.NAME, _("DSA (sign only)"),
+                        Column.TYPE, 0, -1);
+
+        this.type_combo.set_active_iter(&iter);
+
+        model.append(out iter);
+        model.set(iter, Column.NAME, _("ElGamal (encrypt only)"),
+                        Column.TYPE, 1, -1);
+
+        model.append(out iter);
+        model.set(iter, Column.NAME, _("RSA (sign only)"),
+                        Column.TYPE, 2, -1);
+
+        model.append(out iter);
+        model.set(iter, Column.NAME, _("RSA (encrypt only)"),
+                        Column.TYPE, 3, -1);
+
+        load_ui();
+    }
+
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/gpgme-add-subkey.ui";
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("add-subkey");
+        ((Gtk.Container)this.get_content_area()).add(content);
+
+        this.length_spinner = (Gtk.SpinButton) builder.get_object("length_spinner");
+        this.type_combo = (Gtk.ComboBox) builder.get_object("type_combo");
+        this.never_expires = (Gtk.CheckButton) builder.get_object("never_expires");
+        this.ok_button = (Gtk.Button) builder.get_object("ok_button");
+
+        // Expiration date chooser
+        Gtk.Alignment? datetime_placeholder = (Gtk.Alignment) builder.get_object("datetime_placeholder");
+        this.expires_datetime = new Egg.DateTime();
+        this.expires_datetime.show();
+        this.expires_datetime = false;
+        datetime_placeholder.add(this.expires_datetime);
+
+        // Signals
+        this.type_combo.changed.connect(on_type_combo_changed);
+        this.never_expires.toggled.connect(on_never_expires_toggled);
+        this.ok_button.clicked.connect(on_ok_button_clicked);
+    }
+
+    private void on_type_combo_changed(Gtk.ComboBox combo) {
+        Gtk.TreeIter iter;
+        combo.get_active_iter(out iter);
+        int type;
+        combo.get_model().get(iter, Column.TYPE, out type, -1);
+
+        this.length_spinner.set_range(type.get_min_bits(), type.get_max_bits());
+        this.length_spinner.set_value(type.get_default_bits());
+    }
+
+    private void on_never_expires_toggled(Gtk.ToggleButton toggle_button) {
+        this.expires_datetime.set_sensitive(!toggle_button.active);
+    }
+
+    private void on_ok_clicked (Gtk.Button button) {
+        Gtk.TreeIter iter;
+        this.type_combo.get_active_iter(out iter);
+        int type;
+        this.type_combo.model.get(&iter, Column.TYPE, out type, -1);
+
+        uint length = this.length_spinner.get_value_as_int();
+
+        time_t expires = 0;
+        if (!this.never_expires.active)
+            this.expires_datetime.get_as_time_t(out expires);
+
+        KeyEncType real_type;
+        switch (type) {
+            case 0:
+                real_type = DSA;
+                break;
+            case 1:
+                real_type = ELGAMAL;
+                break;
+            case 2:
+                real_type = RSA_SIGN;
+                break;
+            default:
+                real_type = RSA_ENCRYPT;
+                break;
+        }
+
+        this.sensitive = false;
+        GPG.Error err = KeyOperation.add_subkey(this.key, real_type, length, expires);
+        this.sensitive = true;
+
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t add subkey"), err.strerror());
+
+        destroy();
+    }
+}
diff --git a/pgp/gpgme-add-uid.ui b/pgp/gpgme-add-uid.ui
new file mode 100644
index 0000000..b14c722
--- /dev/null
+++ b/pgp/gpgme-add-uid.ui
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="add-uid">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Add User ID</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="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="n_rows">3</property>
+            <property name="n_columns">2</property>
+            <property name="column_spacing">12</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <object class="GtkEntry" id="name_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="has_focus">True</property>
+                <property name="tooltip_text" translatable="yes">Must be at least 5 characters 
long</property>
+                <property name="activates_default">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="email_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="tooltip_text" translatable="yes">Optional email address</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="GtkEntry" id="comment_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="tooltip_text" translatable="yes">Optional comment describing key</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">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes" comments="Full name of the key, usually the name 
of the user.">Full _Name:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">name</property>
+              </object>
+              <packing>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Email Address:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">email</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="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Key Co_mment:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">comment</property>
+              </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>
+          </object>
+          <packing>
+            <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="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>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="cancelbutton1">
+                <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">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok_button">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</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="tooltip_text" translatable="yes">Create the new user ID</property>
+                <property name="use_stock">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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">ok</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-add-uid.vala b/pgp/gpgme-add-uid.vala
new file mode 100644
index 0000000..bab027d
--- /dev/null
+++ b/pgp/gpgme-add-uid.vala
@@ -0,0 +1,79 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ *
+ * 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.GpgME.AddUid : Gtk.Dialog {
+
+    private Key key;
+
+    private Gtk.Entry name_entry;
+    private Gtk.Entry email_entry;
+    private Gtk.Entry comment_entry;
+    private Gtk.Button ok_button;
+
+    /**
+     * Creates a new dialog for adding a user ID to key.
+     */
+    public AddUid(Key key, Gtk.Window parent) {
+        this.key = key;
+        this.transient_for = parent;
+
+        this.title = _("Add user ID to %s").printf(this.key.label);
+
+        load_ui();
+    }
+
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/gpgme-add-uid.ui";
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("add-uid");
+        ((Gtk.Container)this.get_content_area()).add(content);
+
+        this.name_entry = (Gtk.Entry) builder.get_object("name_entry");
+        this.email_entry = (Gtk.Entry) builder.get_object("email_entry");
+        this.comment_entry = (Gtk.Entry) builder.get_object("comment_entry");
+        this.ok_button = (Gtk.Button) builder.get_object("ok_button");
+
+        // Signals
+        this.name_entry.changed.connect(() => check_ok_button());
+        this.email_entry.changed.connect(() => check_ok_button());
+        this.ok_button.clicked.connect(on_ok_button_clicked);
+    }
+
+    private void check_ok_button() {
+        this.ok_button.sensitive = this.name_entry.text.length >= 5
+                                   && (this.email_entry.text.length == 0
+                                       || Regex.match_simple ("?*@?*", this.email_entry.text));
+    }
+
+    private void on_ok_button_clicked(Gtk.Button button) {
+        GPG.Error err = KeyOperation.add_uid(this.key,
+                                             this.name_entry.text,
+                                             this.email_entry.text,
+                                             this.comment_entry.text);
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t add user id"), err.strerror());
+        else
+            destroy();
+    }
+}
diff --git a/pgp/gpgme-algorithm.vala b/pgp/gpgme-algorithm.vala
new file mode 100644
index 0000000..8400fda
--- /dev/null
+++ b/pgp/gpgme-algorithm.vala
@@ -0,0 +1,109 @@
+/*
+ * Seahorse
+ *
+ * Copyright (c) 2017 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 {
+namespace GpgME { // XXX GpgME or PGP?
+
+/**
+ * Enumerates the supported algorithms in which a PGP key can be encrypted.
+ * This is also known as the 'type' of a key.
+ */
+public enum Algorithm {
+    UNKNOWN,
+    RSA,
+    RSA_SIGN,
+    DSA_ELGAMAL,
+    DSA_SIGN;
+
+    /**
+     * Returns a (non-localized) string representation.
+     */
+    public string to_localized_string() {
+        switch (this) {
+            case UNKNOWN:
+                return _("");
+            case RSA:
+                return _("RSA");
+            case RSA_SIGN:
+                return _("RSA (sign only)");
+            case DSA_ELGAMAL:
+                return _("DSA ElGamal");
+            case DSA_SIGN:
+                return _("DSA (sign only)");
+            default:
+                assert_not_reached();
+        };
+    }
+
+    /**
+     * Returns the default bits used for a given algorithm, or 0 if unknown.
+     */
+    public uint get_default_bits() {
+        if (this == UNKNOWN)
+            return 0;
+
+        return 2048;
+    }
+
+    /**
+     * Returns the maximum supported bits for a given algorithm, or 0 if unknown.
+     */
+    public uint get_max_bits() {
+        switch (this) {
+            case UNKNOWN:
+                return 0;
+            case RSA:
+            case RSA_SIGN:
+            case DSA_ELGAMAL:
+                return 4096;
+            case DSA_SIGN:
+                // XXX Depends onds GPG Version, see key-op.h
+                return 3072;
+            default:
+                assert_not_reached();
+        }
+    }
+
+    /**
+     * Returns the minimum supported bits for a given algorithm, or 0 if unknown.
+     */
+    public uint get_min_bits() {
+        switch (this) {
+            case UNKNOWN:
+                return 0;
+            case RSA:
+            case RSA_SIGN:
+                return 1024;
+            case DSA_ELGAMAL:
+                return 768;
+            case DSA_SIGN:
+                return 768;
+            default:
+                assert_not_reached();
+        }
+    }
+
+    public bool bits_supported(uint bits) {
+        return (get_min_bits() < bits) && (bits < get_max_bits());
+    }
+}
+
+}
+}
+
diff --git a/pgp/gpgme-data.vala b/pgp/gpgme-data.vala
new file mode 100644
index 0000000..1c89a39
--- /dev/null
+++ b/pgp/gpgme-data.vala
@@ -0,0 +1,367 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.GpgME.Data {
+
+private int handle_gio_error (GLib.Error err) {
+    g_return_val_if_fail (err, -1);
+
+    if (err.message)
+        message (err.message);
+
+    switch (err->code) {
+        case G_IO_ERROR_FAILED:
+            errno = EIO;
+            break;
+        case G_IO_ERROR_NOT_FOUND:
+            errno = ENOENT;
+            break;
+        case G_IO_ERROR_EXISTS:
+            errno = EEXIST;
+            break;
+        case G_IO_ERROR_IS_DIRECTORY:
+            errno = EISDIR;
+            break;
+        case G_IO_ERROR_NOT_DIRECTORY:
+            errno = ENOTDIR;
+            break;
+        case G_IO_ERROR_NOT_EMPTY:
+            errno = ENOTEMPTY;
+            break;
+        case G_IO_ERROR_NOT_REGULAR_FILE:
+        case G_IO_ERROR_NOT_SYMBOLIC_LINK:
+        case G_IO_ERROR_NOT_MOUNTABLE_FILE:
+            errno = EBADF;
+            break;
+        case G_IO_ERROR_FILENAME_TOO_LONG:
+            errno = ENAMETOOLONG;
+            break;
+        case G_IO_ERROR_INVALID_FILENAME:
+            errno = EINVAL;
+            break;
+        case G_IO_ERROR_TOO_MANY_LINKS:
+            errno = EMLINK;
+            break;
+        case G_IO_ERROR_NO_SPACE:
+            errno = ENOSPC;
+            break;
+        case G_IO_ERROR_INVALID_ARGUMENT:
+            errno = EINVAL;
+            break;
+        case G_IO_ERROR_PERMISSION_DENIED:
+            errno = EPERM;
+            break;
+        case G_IO_ERROR_NOT_SUPPORTED:
+            errno = ENOTSUP;
+            break;
+        case G_IO_ERROR_NOT_MOUNTED:
+            errno = ENOENT;
+            break;
+        case G_IO_ERROR_ALREADY_MOUNTED:
+            errno = EALREADY;
+            break;
+        case G_IO_ERROR_CLOSED:
+            errno = EBADF;
+            break;
+        case G_IO_ERROR_CANCELLED:
+            errno = EINTR;
+            break;
+        case G_IO_ERROR_PENDING:
+            errno = EALREADY;
+            break;
+        case G_IO_ERROR_READ_ONLY:
+            errno = EACCES;
+            break;
+        case G_IO_ERROR_CANT_CREATE_BACKUP:
+            errno = EIO;
+            break;
+        case G_IO_ERROR_WRONG_ETAG:
+            errno = EACCES;
+            break;
+        case G_IO_ERROR_TIMED_OUT:
+            errno = EIO;
+            break;
+        case G_IO_ERROR_WOULD_RECURSE:
+            errno = ELOOP;
+            break;
+        case G_IO_ERROR_BUSY:
+            errno = EBUSY;
+            break;
+        case G_IO_ERROR_WOULD_BLOCK:
+            errno = EWOULDBLOCK;
+            break;
+        case G_IO_ERROR_HOST_NOT_FOUND:
+            errno = EHOSTDOWN;
+            break;
+        case G_IO_ERROR_WOULD_MERGE:
+            errno = EIO;
+            break;
+        case G_IO_ERROR_FAILED_HANDLED:
+            errno = 0;
+            break;
+        default:
+            errno = EIO;
+            break;
+    };
+
+    return -1;
+}
+
+/* ----------------------------------------------------------------------------------------
+ * OUTPUT
+ */
+
+/* Called by gpgme to read data */
+private ssize_t output_write(void *handle, void *buffer, size_t size) {
+    GLib.OutputStream output = handle;
+    GError *err = null;
+    gsize written;
+
+    g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), -1);
+
+    if (!g_output_stream_write_all (output, buffer, size, &written, null, &err))
+        return handle_gio_error (err);
+
+    if (!g_output_stream_flush (output, null, &err))
+        return handle_gio_error (err);
+
+    return written;
+}
+
+/* Called from gpgme to seek a file */
+private Posix.off_t output_seek (void *handle, Posix.off_t offset, int whence) {
+    GError *err = null;
+    GLib.OutputStream output = handle;
+
+    g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), -1);
+
+    if (!G_IS_SEEKABLE (output)) {
+        errno = EOPNOTSUPP;
+        return -1;
+    }
+
+    GSeekType from = 0;
+    switch(whence) {
+        case SEEK_SET:
+            from = G_SEEK_SET;
+            break;
+        case SEEK_CUR:
+            from = G_SEEK_CUR;
+            break;
+        case SEEK_END:
+            from = G_SEEK_END;
+            break;
+        default:
+            g_assert_not_reached();
+            break;
+    };
+
+    Seekable seek = G_SEEKABLE (output);
+    if (!g_seekable_seek (seek, offset, from, null, &err))
+        return handle_gio_error (err);
+
+    return offset;
+}
+
+/* Called by gpgme to close a file */
+private void output_release (void *handle) {
+    GLib.OutputStream output = handle;
+    g_return_if_fail (G_IS_OUTPUT_STREAM (output));
+    
+    g_object_unref (output);
+}
+
+/* GPGME vfs file operations */
+gpgme_data_cbs output_cbs =  {
+    null,
+    output_write,
+    output_seek,
+    output_release
+};
+
+public gpgme_data_t seahorse_gpgme_data_output (GLib.OutputStream output) {
+    GPGError.ErrorCode gerr;
+    gpgme_data_t ret = null;
+
+    g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), null);
+    
+    gerr = gpgme_data_new_from_cbs (&ret, &output_cbs, output);
+    if (!gerr.is_success())
+        return null;
+    
+    g_object_ref (output);
+    return ret;
+}
+
+/* -------------------------------------------------------------------------------------
+ * INPUT STREAMS
+ */
+
+/* Called by gpgme to read data */
+private ssize_t input_read (void *handle, void *buffer, size_t size) {
+    GLib.InputStream input = handle;
+    GError *err = null;
+    gsize nread;
+    
+    g_return_val_if_fail (G_IS_INPUT_STREAM (input), -1);
+    
+    if (!g_input_stream_read_all (input, buffer, size, &nread, null, &err))
+        return handle_gio_error (err);
+    
+    return nread;
+}
+
+/* Called from gpgme to seek a file */
+private Posix.off_t input_seek (void *handle, Posix.off_t offset, int whence) {
+    GLib.Seekable seek;
+    GSeekType from = 0;
+    GError *err = null;
+    GLib.InputStream input = handle;
+
+    g_return_val_if_fail (G_IS_INPUT_STREAM (input), -1);
+
+    if (!G_IS_SEEKABLE (input)) {
+        errno = EOPNOTSUPP;
+        return -1;
+    }
+    
+    switch(whence)
+    {
+    case SEEK_SET:
+        from = G_SEEK_SET;
+        break;
+    case SEEK_CUR:
+        from = G_SEEK_CUR;
+        break;
+    case SEEK_END:
+        from = G_SEEK_END;
+        break;
+    default:
+        g_assert_not_reached();
+        break;
+    };
+
+    seek = G_SEEKABLE (input);
+    if (!g_seekable_seek (seek, offset, from, null, &err))
+        return handle_gio_error (err);
+
+    return offset;
+}
+
+/* Called by gpgme to close a file */
+private void input_release(void *handle) {
+    GLib.InputStream input = handle;
+    g_return_if_fail (G_IS_INPUT_STREAM (input));
+    
+    g_object_unref (input);
+}
+
+/* GPGME vfs file operations */
+static gpgme_data_cbs input_cbs =  {
+    input_read,
+    null,
+    input_seek,
+    input_release
+};
+
+gpgme_data_t seahorse_gpgme_data_input (GLib.InputStream input) {
+    gpgme_data_t ret = null;
+
+    g_return_val_if_fail (G_IS_INPUT_STREAM (input), null);
+
+    GPGError.ErrorCode gerr = gpgme_data_new_from_cbs (&ret, &input_cbs, input);
+    if (!gerr.is_success())
+        return null;
+
+    g_object_ref (input);
+    return ret;
+}
+
+gpgme_data_t seahorse_gpgme_data_new () {
+    gpgme_data_t data;
+
+    GPGError.ErrorCode gerr = gpgme_data_new (&data);
+    if (!gerr.is_success()) {
+        if (gpgme_err_code_to_errno (gerr) == ENOMEM ||
+            gpgme_err_code (gerr) == GPG_ERR_ENOMEM) {
+
+            g_error ("%s: failed to allocate gpgme_data_t", G_STRLOC);
+
+        } else {
+            /* The only reason this should fail is above */
+            g_assert_not_reached ();
+
+            /* Just in case */
+            abort ();
+        }
+    }
+
+    return data;
+}
+
+gpgme_data_t seahorse_gpgme_data_new_from_mem (string buffer, size_t size, bool copy) {
+    gpgme_data_t data;
+
+    GPGError.ErrorCode gerr = gpgme_data_new_from_mem (&data, buffer, size, copy ? 1 : 0);
+    if (!gerr.is_success()) {
+        if (gpgme_err_code_to_errno (gerr) == ENOMEM ||
+            gpgme_err_code (gerr) == GPG_ERR_ENOMEM) {
+
+            g_error ("%s: failed to allocate gpgme_data_t", G_STRLOC);
+
+        } else {
+            /* The only reason this should fail is above */
+            g_assert_not_reached ();
+
+            /* Just in case */
+            abort ();
+        }
+    }
+
+    return data;
+}
+
+public int seahorse_gpgme_data_write_all(gpgme_data_t data, void* buffer, size_t len) {
+    guchar *text = (guchar*)buffer;
+    int written = 0;
+
+    if (len < 0)
+        len = strlen ((gchar*)text);
+
+    while (len > 0) {
+        written = gpgme_data_write (data, (void*)text, len);
+        if (written < 0) {
+            if (errno == EAGAIN || errno == EINTR)
+                continue;
+            return -1;
+        }
+
+        len -= written;
+        text += written;
+    }
+
+    return written;
+}
+
+public void seahorse_gpgme_data_release (gpgme_data_t data) {
+    if (data)
+        gpgme_data_release (data);
+}
+}
diff --git a/pgp/gpgme-expires.ui b/pgp/gpgme-expires.ui
new file mode 100644
index 0000000..2e1ffc6
--- /dev/null
+++ b/pgp/gpgme-expires.ui
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="expires">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="modal">True</property>
+    <property name="type_hint">dialog</property>
+    <signal name="delete_event" handler="on_widget_delete_event"/>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="all-controls">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkCheckButton" id="expire">
+                <property name="label" translatable="yes">_Never expires</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_gpgme_expire_toggled"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkCalendar" id="calendar">
+                <property name="visible">True</property>
+                <property name="can_focus">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="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="cancelbutton1">
+                <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>
+                <signal name="clicked" handler="on_widget_closed"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="okbutton1">
+                <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="tooltip_text" translatable="yes">Revoke key</property>
+                <signal name="clicked" handler="on_gpgme_expire_ok_clicked"/>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="xscale">0</property>
+                    <property name="yscale">0</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <object class="GtkImage" id="image1">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-ok</property>
+                            <property name="icon-size">4</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label3">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">C_hange</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>
+                    </child>
+                  </object>
+                </child>
+              </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="fill">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">okbutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-expires.vala b/pgp/gpgme-expires.vala
new file mode 100644
index 0000000..8ee6743
--- /dev/null
+++ b/pgp/gpgme-expires.vala
@@ -0,0 +1,101 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ *
+ * 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 {
+namespace GpgME {
+
+public class Expires : Gtk.Dialog {
+
+    private SubKey subkey;
+    private Gtk.Calendar calendar;
+    private Gtk.ToggleButton expire;
+
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_expire_ok_clicked (GtkButton *button) {
+        GtkWidget *widget;
+        gpgme_error_t err;
+        time_t expiry = 0;
+
+        widget = GTK_WIDGET (seahorse_widget_get_widget (swidget, "expire"));
+        if (!widget.active) {
+            Posix.tm when;
+            memset (&when, 0, sizeof (when));
+            widget = GTK_WIDGET (seahorse_widget_get_widget (swidget, "calendar"));
+            widget.get_date((guint*)&(when.tm_year), (guint*)&(when.tm_mon), (guint*)&(when.tm_mday));
+            when.tm_year -= 1900;
+            expiry = mktime (&when);
+
+            if (expiry <= time (NULL)) {
+                Seahorse.Util.show_error (widget, _("Invalid expiry date"),
+                                          _("The expiry date must be in the future"));
+                return;
+            }
+        }
+
+        widget = seahorse_widget_get_widget (swidget, "all-controls");
+        widget.sensitive = false;
+
+        if (expiry != (time_t) this.subkey.expires) {
+            err = KeyOperation.set_expires(this.subkey, expiry);
+            if (!err.is_success() || err.is_cancelled())
+                Util.show_error(null, _("Couldn’t change expiry date"), err.strerror());
+        }
+
+        seahorse_widget_destroy (swidget);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_expire_toggled (GtkWidget *widget) {
+        Gtk.Widget expire = GTK_WIDGET (seahorse_widget_get_widget (swidget, "expire"));
+        Gtk.Widget cal = GTK_WIDGET (seahorse_widget_get_widget (swidget, "calendar"));
+
+        cal.sensitive = !expire.active;
+    }
+
+    public void seahorse_gpgme_expires_new(Subkey subkey, GtkWindow *parent) {
+        g_return_if_fail (subkey != NULL);
+
+        Seahorse.Widget swidget = seahorse_widget_new_allow_multiple ("expires", parent);
+
+        Gtk.Widget date = GTK_WIDGET (seahorse_widget_get_widget (swidget, "calendar"));
+        Gtk.ToggleButton expire = GTK_WIDGET (seahorse_widget_get_widget (swidget, "expire"));
+        ulong expires = subkey.expires;
+        if (!expires) {
+            expire.active = true;
+            date.sensitive = false;
+        } else {
+            expire.active = false;
+            date.sensitive = true;
+        }
+
+        if (expires) {
+            Posix.tm t;
+            time_t time = (time_t)expires;
+            if (gmtime_r (&time, &t)) {
+                date.select_month(t.tm_mon, t.tm_year + 1900);
+                date.select_day(t.tm_mday);
+            }
+        }
+
+        this.title = _("Expiry: %s").printf(subkey.description);
+    }
+}
+
+}
+}
diff --git a/pgp/gpgme-exporter.vala b/pgp/gpgme-exporter.vala
new file mode 100644
index 0000000..2cf8ed8
--- /dev/null
+++ b/pgp/gpgme-exporter.vala
@@ -0,0 +1,213 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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>
+ */
+
+namespace Seahorse {
+namespace GpgME {
+
+public class Exporter : GLib.Object, Seahorse.Exporter {
+
+    private GLib.Object parent;
+    private List<Pgp.Key> keys;
+
+    /**
+     * Armor encoding
+     */
+    public bool armor { get; set; }
+
+    /**
+     * Secret key export
+     */
+    public bool secret { get; set; }
+
+    //XXX
+    public string filename { owned get { return get_filename(); } }
+
+    public string content_type {
+        get {
+            return this.armor ? "application/pgp-keys" : "application/pgp-keys+armor";
+        }
+    }
+
+    public Gtk.FileFilter file_filter {
+        owned get {
+            Gtk.FileFilter filter = new Gtk.FileFilter();
+
+            if (this.armor) {
+                filter.set_name(_("Armored PGP keys"));
+                filter.add_mime_type("application/pgp-keys+armor");
+                filter.add_pattern("*.asc");
+            } else {
+                filter.set_name(_("PGP keys"));
+                filter.add_mime_type("application/pgp-keys");
+                filter.add_pattern("*.pgp");
+                filter.add_pattern("*.gpg");
+            }
+
+            return filter;
+        }
+    }
+
+    // XXX Gpg keys or PGP keys?
+    public Exporter(Pgp.Key key, bool armor, bool secret) {
+        GLib.Object(armor: armor,
+                    secret: secret);
+        g_return_val_if_fail (secret == false || armor == true, null);
+
+        add_object(key);
+    }
+
+    // XXX Gpg keys or PGP keys?
+    public Exporter.multiple(List<GpgME.Key> keys, bool armor) {
+        GLib.Object(armor: armor,
+                    secret: false);
+
+        foreach (Pgp.Key key in keys)
+            add_object(key);
+    }
+
+    static gchar *
+    seahorse_gpgme_exporter_get_filename (SeahorseExporter* exporter)
+    {
+        const gchar *basename = null;
+        gchar *filename;
+
+        g_return_val_if_fail (this.keys, null);
+
+        /* Multiple objects */
+        if (this.keys.next)
+            basename = _("Multiple Keys");
+        else if (this.keys.data)
+            basename = seahorse_object_get_nickname (this.keys.data);
+        if (basename == null)
+            basename = _("Key Data");
+
+        if (this.armor)
+            filename = g_strconcat (basename, ".asc", null);
+        else
+            filename = g_strconcat (basename, ".pgp", null);
+        g_strstrip (filename);
+        g_strdelimit (filename, SEAHORSE_BAD_FILENAME_CHARS, '_');
+        return filename;
+    }
+
+    public unowned GLib.List<weak GLib.Object> get_objects() {
+        return this.keys;
+    }
+
+    public bool add_object(GLib.Object object) {
+        Key key = object as Key;
+        if (key == null || (this.secret && key.privkey == null))
+            return false;
+
+        this.keys.append(key);
+        notify_property("filename");
+        return true;
+    }
+
+    public async uint8[] export(GLib.Cancellable? cancellable) throws GLib.Error {
+        GPG.ErrorCode gerr = 0;
+        GPG.Context gctx = seahorse_gpgme_keyring_new_context(out gerr);
+        MemoryOutputStream output = new MemoryOutputStream.resizable();
+        GPG.Data data = seahorse_gpgme_data_output (output);
+        string[] keyids = new string[this.keys.length];
+        int at = -1;
+
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+            return;
+        }
+
+        gctx.set_armor(this.armor);
+
+        foreach (Pgp.Key key in this.keys) {
+            Seahorse.Progress.prep(cancellable, key.keyid, null);
+            keyids += key.keyid;
+        }
+
+        if (this.secret) {
+            if (!this.armor)
+                return;
+
+            gerr = GpgME.Operation.export_secret(gctx, keyids, data);
+            if (seahorse_gpgme_propagate_error (gerr, &error))
+                g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+            return;
+        }
+
+        GSource *gsource = seahorse_gpgme_gsource_new (cancellable);
+        gsource.set_callback(on_keyring_export_complete);
+
+        /* Get things started */
+        if (on_keyring_export_complete (0, res))
+            gsource.attach(g_main_context_default ());
+
+        g_source_unref (gsource);
+    }
+
+    static bool on_keyring_export_complete (GPG.ErrorCode gerr) {
+        GError *error = null;
+
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            return false; /* don't call again */
+        }
+
+        if (closure.at >= 0)
+            Seahorse.Progress.end(cancellable, keyids[at]);
+
+        assert(at < keyids.length);
+        at++;
+
+        if (closure.at == keyids.length) {
+            g_simple_async_result_complete (res);
+            return false; /* don't run this again */
+        }
+
+        /* Do the next key in the list */
+        gerr = GpgME.Operation.export_start(gctx, keyids[at], 0, data);
+
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            return false; /* don't run this again */
+        }
+
+        Seahorse.Progress.begin(cancellable, keyids[at]);
+        return true; /* call this source again */
+    }
+
+    static uint8[] seahorse_gpgme_exporter_export_finish(AsyncResult *result, out size_t size) {
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return null;
+
+        closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+        g_output_stream_close (G_OUTPUT_STREAM (closure.output), null, null);
+        *size = g_memory_output_stream_get_data_size (closure.output);
+        return g_memory_output_stream_steal_data (closure.output);
+    }
+}
+
+}
+}
diff --git a/pgp/gpgme-generate.ui b/pgp/gpgme-generate.ui
new file mode 100644
index 0000000..610187c
--- /dev/null
+++ b/pgp/gpgme-generate.ui
@@ -0,0 +1,435 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="lower">512</property>
+    <property name="upper">8192</property>
+    <property name="value">2048</property>
+    <property name="step_increment">512</property>
+    <property name="page_increment">1</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="gpgme-generate">
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="visible">True</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="visible">True</property>
+            <property name="can_focus">False</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_action_appearance">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="cancelbutton1">
+                <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_action_appearance">False</property>
+                <property name="use_stock">True</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="ok">
+                <property name="label" translatable="yes">C_reate</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</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="tooltip_text" translatable="yes">Generate a new key</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="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHBox" id="hbox2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">7</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkImage" id="pgp-image">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="yalign">0</property>
+                <property name="icon_name">gcr-key-pair</property>
+                <property name="pixel_size">48</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <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 PGP key allows you to encrypt email or files 
to other people.</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="table12">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</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="GtkEntry" id="name-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>
+                        <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="y_options"></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>
+                        <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
+                      </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="label53">
+                        <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" comments="Full name of the key, usually 
the name of the user.">Full _Name:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">name-entry</property>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label46">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">_Email Address:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">email-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>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkExpander" id="expander1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child>
+                      <object class="GtkAlignment" id="alignment2">
+                        <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>
+                        <property name="top_padding">12</property>
+                        <property name="left_padding">12</property>
+                        <child>
+                          <object class="GtkTable" id="table11">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="n_rows">3</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">12</property>
+                            <property name="row_spacing">6</property>
+                            <child>
+                              <object class="GtkHBox" id="expiry-date-container">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="spacing">12</property>
+                                <child>
+                                  <placeholder/>
+                                </child>
+                                <child>
+                                  <object class="GtkCheckButton" id="expires-check">
+                                    <property name="label" translatable="yes">Ne_ver Expires</property>
+                                    <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="use_underline">True</property>
+                                    <property name="active">True</property>
+                                    <property name="draw_indicator">True</property>
+                                    <signal name="toggled" handler="on_gpgme_generate_expires_toggled" 
swapped="no"/>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">False</property>
+                                    <property name="pack_type">end</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </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"></property>
+                                <property name="y_options"></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-entry">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">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="right_attach">2</property>
+                                <property name="top_attach">2</property>
+                                <property name="bottom_attach">3</property>
+                                <property name="y_options"></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>
+                                    <signal name="changed" handler="on_gpgme_generate_algorithm_changed" 
swapped="no"/>
+                                  </object>
+                                </child>
+                              </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="GtkEntry" id="comment-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>
+                        <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">0</property>
+                        <property name="bottom_attach">1</property>
+                        <property name="y_options"></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">_Comment:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">comment-entry</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">0</property>
+                        <property name="bottom_attach">1</property>
+                        <property name="x_options">GTK_FILL</property>
+                        <property name="y_options"></property>
+                      </packing>
+                    </child>
+                            <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>
+                              </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="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>
+                              </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="label55">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">E_xpiration Date:</property>
+                                <property name="use_underline">True</property>
+                              </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>
+                          </object>
+                        </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>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">ok</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-generate.vala b/pgp/gpgme-generate.vala
new file mode 100644
index 0000000..3deb8d1
--- /dev/null
+++ b/pgp/gpgme-generate.vala
@@ -0,0 +1,206 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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/>.
+ */
+
+/**
+ * A creation dialog for PGP keys.
+ */
+public class Seahorse.GpgME.Generate : Gtk.Dialog {
+
+    private const GtkActionEntry ACTION_ENTRIES[] = {
+        { "pgp-generate-key", Gcr.ICON_KEY_PAIR, _("PGP Key"), "",
+              _("Used to encrypt email and files"), on_pgp_generate_key }
+    };
+
+    private Keyring keyring;
+
+    // Widget descendants
+    private Gtk.Entry name_entry;
+    private Gtk.Entry email_entry;
+    private Gtk.Entry comment_entry;
+    private Gtk.ComboBoxText algo_combo;
+    private Gtk.Box expiry_date_container;
+    private Gtk.CheckButton expires_check;
+    private Gtk.Button ok_button;
+    private Egg.DateTime expire_date;
+
+    public Generate(Gtk.Window parent, Keyring keyring, string name = "", string email = "", string comment 
= "") {
+        GLib.Object(transient_for: parent);
+    /* <property name="visible">True</property> */
+    /* <property name="can_focus">False</property> */
+    /* <property name="border_width">5</property> */
+    /* <property name="resizable">False</property> */
+    /* <property name="modal">True</property> */
+    /* <property name="type_hint">dialog</property> */
+        this.keyring = keyring;
+
+        load_ui();
+
+        // Set defaults
+        this.name_entry.text = name;
+        this.email_entry.text = email;
+        this.comment_entry.text = comment;
+
+        // The algoritm
+        algo_combo.remove(0);
+        for(uint i = 0; i < available_algorithms.length; i++)
+            algo_combo.append_text(_(available_algorithms[i].desc));
+        algo_combo.set_active(0);
+        on_gpgme_generate_algorithm_changed (algo_combo);
+
+        time_t expires = time (null);
+        expires += (60 * 60 * 24 * 365); // Seconds in a year
+
+        // Default expiry date
+        // XXX Fuck
+        this.expire_date = new Egg.DateTime.from_time_t(expires);
+        this.expire_date.sensitive = false;
+        this.expiry_date_container.pack_start(datetime, true, true, 0);
+
+        on_gpgme_generate_entry_changed(null);
+    }
+
+    private void load_ui() {
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/gpgme-generate.ui"; // XXX correct?
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object("gpgme-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.name_entry = (Gtk.Entry) builder.get_object("name-entry");
+        this.email_entry = (Gtk.Entry) builder.get_object("email-entry");
+        this.comment_entry = (Gtk.Entry) builder.get_object("comment-entry");
+        this.algo_combo = (Gtk.ComboBoxText) builder.get_object("algorithm-choice");
+        this.expiry_date_container = (Gtk.Box) builder.get_object("expiry-date-container");
+        this.expires_button = (Gtk.CheckButton) builder.get_object("expires-check");
+        this.ok_button = (Gtk.Button) builder.get_object("ok");
+    }
+
+    private static void on_pgp_generate_key(Gtk.Action action) {
+        show(Pgp.Backend.get().default_keyring(), Action.get_window(action), null, null, null);
+    }
+
+    /**
+     * Registers the action group for the pgp key creation dialog
+     */
+    public void register() {
+        Gtk.ActionGroup actions = new Gtk.ActionGroup("gpgme-generate");
+
+        actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+        actions.add_actions(ACTION_ENTRIES, this);
+
+        // Register this as a generator
+        Seahorse.Registry.register_object(actions, "generator");
+    }
+
+    static Gtk.Widget get_expiry_date (SeahorseWidget *swidget) {
+        /* The first widget should be the expiry-date */
+        return this.expiry_date_container.get_children().first();
+    }
+
+    // If everything is alright, a passphrase is requested and the key will be generated
+    public override void response(int response) {
+        if (response != Gtk.ResponseType.OK) {
+            destroy();
+            return;
+        }
+
+        // Make sure the name is the right length. (Should have been checked earlier)
+        string name = this.name_entry.text.strip();
+        if (name.length >= 5)
+            return;
+
+        // The algorithm
+        int sel = algo_combo.get_active();
+        Algorithm algo = sel.type;
+
+        // The number of bits
+        uint bits = bits_spinner.get_value_as_int();
+        if (bits < algo.get_min_bits() || bits > algo.get_min_bits()) {
+            bits = algo.get_default_bits();
+            message("Invalid key size: %s defaulting to %u", available_algorithms[sel].desc, bits);
+        }
+
+        // The expiry
+        time_t expires;
+        if (expires_check.active) // XXX shouldn't this be the other way around?
+            expires = 0;
+        else {
+            Egg.DateTime datetime = get_expiry_date();
+            datetime.get_as_time_t(out expires);
+        }
+
+        // Less confusing with less on the screen
+        get_toplevel().hide();
+
+        Gtk.Dialog dialog = PasshprasePrompt.show(_("Passphrase for New PGP Key"),
+                                                  _("Enter the passphrase for your new key twice."),
+                                                  null, null, true);
+        dialog.transient_for = parent;
+        if (dialog.run() == Gtk.ResponseType.ACCEPT) {
+            string pass = Seahorse.PasshprasePrompt.get(dialog);
+            Cancellable cancellable = new Cancellable();
+            KeyOperation.generate_async(this.keyring, name, this.email_entry.text,
+                                        this.comment_entry.text, pass, algo, bits, expires,
+                                        cancellable, on_generate_key_complete, null);
+            // XXX do .end() with try_catch
+            //if (!seahorse_gpgme_key_op_generate_finish (SEAHORSE_GPGME_KEYRING (source), result, &error))
+            //    seahorse_util_handle_error (&error, null, _("Couldn’t generate PGP key"));
+
+            // Has line breaks because GtkLabel is completely broken WRT wrapping
+            string notice = _("""
+When creating a key we need to generate a lot of
+random data and we need you to help. It’s a good
+idea to perform some other action like typing on
+the keyboard, moving the mouse, using applications.
+This gives the system the random data that it needs.""");
+            Seahorse.Progress.show_with_notice(cancellable, _("Generating key"), notice, false);
+        }
+
+        dialog.destroy();
+    }
+
+    // If the name has more than 5 characters, this sets the ok button sensitive
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_generate_entry_changed(GtkEditable editable) {
+        this.ok_button.sensitive = (name_entry.text.strip().length >= 5);
+    }
+
+    // Handles the expires toggle button feedback
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_generate_expires_toggled(GtkToggleButton *button) {
+        get_expiry_date().sensitive = !button.active;
+    }
+
+    // Sets the bit range depending on the algorithm set
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_generate_algorithm_changed (GtkComboBox *combo) {
+        int sel = combo.get_active();
+
+        bits_spinner.set_range(algorithm.get_min_bits(), algorithm.get_max_bits());
+        bits_spinner.set_value(algorithm.get_default_bits());
+    }
+}
diff --git a/pgp/gpgme-key-deleter.vala b/pgp/gpgme-key-deleter.vala
new file mode 100644
index 0000000..2ef3698
--- /dev/null
+++ b/pgp/gpgme-key-deleter.vala
@@ -0,0 +1,81 @@
+/* 
+ * Seahorse
+ * 
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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>
+ */
+
+namespace Seahorse {
+namespace GpgME {
+
+public class Deleter : Seahorse.Deleter {
+    private List<Key> keys;
+
+    public Deleter(Key item) {
+        this.keys = new List<Key>();
+
+        if (!add_object(item))
+            assert_not_reached();
+    }
+
+    public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+        uint num = this.keys.length;
+        string prompt;
+        if (num == 1) {
+            prompt = _("Are you sure you want to permanently delete %s?")
+                        .printf(this.keys.data.label);
+        } else {
+            prompt = ngettext("Are you sure you want to permanently delete %d keys?",
+                              "Are you sure you want to permanently delete %d keys?",
+                              num).printf(num);
+        }
+
+        return new Seahorse.DeleteDialog(parent, "%s", prompt);
+    }
+
+    public override unowned List<weak GLib.Object> get_objects() {
+        return this.keys;
+    }
+
+    public override bool add_object(GLib.Object object) {
+        if (!(object is Key))
+            return false;
+
+        this.keys.append((Key) object);
+        return true;
+    }
+
+    public override async bool delete(GLib.Cancellable? cancellable) throws GLib.Error {
+        foreach (Key key in this.keys) {
+            if (cancellable != null && cancellable.is_cancelled())
+                break;
+
+            GPG.ErrorCode error = KeyOperation.delete(key);
+            if (seahorse_gpgme_propagate_error (gerr, &error)) {
+                g_simple_async_result_take_error (res, error);
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
+
+}
+}
diff --git a/pgp/gpgme-key-op.vala b/pgp/gpgme-key-op.vala
new file mode 100644
index 0000000..1ed07fc
--- /dev/null
+++ b/pgp/gpgme-key-op.vala
@@ -0,0 +1,2223 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2004 Stefan Walter
+ * Copyright (C) 2005 Adam Schreiber
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.GpgME {
+
+/*
+ * Key type options.
+ * We only support GPG version >=2.0.12 or >= 2.2.0
+ */
+public enum KeyEncType {
+    RSA_RSA = 1,
+    DSA_ELGAMAL = 2,
+    DSA = 3,
+    RSA_SIGN = 4,
+    ELGAMAL = 5,
+    RSA_ENCRYPT = 6
+}
+
+/* Length ranges for key types */
+public enum SeahorseKeyLength {
+    /* Minimum length for #DSA. */
+    DSA_MIN = 768,
+    /* Maximum length for #DSA. */
+#if ( GPG_MAJOR == 2 &&   GPG_MINOR == 0 && GPG_MICRO < 12 ) || \
+    ( GPG_MAJOR == 1 && ( GPG_MINOR <  4 || GPG_MICRO < 10 ) )
+    DSA_MAX = 1024,
+#else
+    DSA_MAX = 3072,
+#endif
+    /* Minimum length for #ELGAMAL. Maximum length is #LENGTH_MAX. */
+    ELGAMAL_MIN = 768,
+    /* Minimum length of #RSA_SIGN and #RSA_ENCRYPT. Maximum length is
+     * #LENGTH_MAX.
+     */
+    RSA_MIN = 1024,
+    /* Maximum length for #ELGAMAL, #RSA_SIGN, and #RSA_ENCRYPT. */
+    LENGTH_MAX = 4096,
+    /* Default length for #ELGAMAL, #RSA_SIGN, #RSA_ENCRYPT, and #DSA. */
+    LENGTH_DEFAULT = 2048
+}
+
+public enum SignCheck {
+    /* Unknown key check */
+    SIGN_CHECK_NO_ANSWER,
+    /* Key not checked */
+    SIGN_CHECK_NONE,
+    /* Key casually checked */
+    SIGN_CHECK_CASUAL,
+    /* Key carefully checked */
+    SIGN_CHECK_CAREFUL
+}
+
+[Flags]
+public enum SignOptions {
+    /* If signature is local */
+    SIGN_LOCAL,
+    /* If signature is non-revocable */
+    SIGN_NO_REVOKE,
+    /* If signature expires with key */
+    SIGN_EXPIRES;
+}
+
+// namespace or class?
+namespace KeyOperation {
+
+public const string PROMPT = "keyedit.prompt";
+public const string QUIT = "quit";
+public const string SAVE = "keyedit.save.okay";
+public const string YES = "Y";
+public const string NO = "N";
+
+// XXX no macro's in vala
+// #define PRINT(args)  if(!Seahorse.Util.print_fd args) return GPG_E (GPG_ERR_GENERAL)
+// #define PRINTF(args) if(!Seahorse.Util.printf_fd args) return GPG_E (GPG_ERR_GENERAL)
+
+// XXX this isn't used I think?
+// #define GPG_UNKNOWN     1
+// #define GPG_NEVER       2
+// #define GPG_MARGINAL    3
+// #define GPG_FULL        4
+// #define GPG_ULTIMATE    5
+
+/**
+ * Tries to generate a new key based on given parameters.
+ *
+ * @param sksrc #SeahorseSource
+ * @param name User ID name, must be at least 5 characters long
+ * @param email Optional user ID email
+ * @param comment Optional user ID comment
+ * @param passphrase Passphrase for key
+ * @param type Key type. Supported types are #DSA_ELGAMAL, #DSA, #RSA_SIGN, and #RSA_RSA
+ * @param length Length of key, must be within the range of @type specified by #SeahorseKeyLength
+ * @param expires Expiration date of key
+ **/
+public async void generate_async(Keyring keyring, string name, string? email, string? comment,
+                                 string passphrase, SeahorseKeyEncType type, uint length,
+                                 time_t expires, Cancellable cancellable) throws GLib.Error {
+    if (name == null || name.length < 5 || passphrase == null)
+        return;
+
+    if (!type.bits_supported(length))
+        return;
+
+    string email_part = "", comment_part = "";
+    if (email != null && email != "")
+        email_part = "Name-Email: %s\n".printf(email);
+    if (comment != null && comment != "")
+        comment_part = "Name-Comment: %s\n".printf(comment);
+
+    string expires_date = (expires != 0)? Seahorse.Util.get_date_string(expires) : "0";
+
+    // Subkey xml
+    string subkey_part = "";
+    if (type == DSA_ELGAMAL)
+        subkey_part = "Subkey-Type: ELG-E\nSubkey-Length: %d\nSubkey-Usage: encrypt\n".printf(length);
+    else if (type == RSA_RSA)
+        subkey_part = "Subkey-Type: RSA\nSubkey-Length: %d\nSubkey-Usage: encrypt\n".printf(length);
+
+    // Common xml XXX do this with vala interpolated strings?
+    StringBuilder parms = new StringBuilder();
+    parms.append("<GnupgKeyParms format=\"internal\">\n")
+             .append_printf("Key-Type: %s\n", (type == DSA || type == DSA_ELGAMAL)? "DSA" : "RSA")
+             .append_printf("Key-Length: %d\n", length) // shouldn't this be %u?
+             .append(subkey_part)
+             .append_printf("Name-Real: %s\n", name)
+             .append(email_part)
+             .append(comment_part)
+             .append_printf("Expire-Date: %s\n", expires_date)
+             .append_printf("Passphrase: %s\n", passphrase)
+         .append("</GnupgKeyParms>");
+
+    GPGError.ErrorCode gerr = GPGError.ErrorCode.NO_ERROR;
+    GPG.Context gctx = Keyring.new_context(out gerr);
+    gpgme_set_progress_cb(gctx, on_generate_progress, res);
+
+    Seahorse.Progress.prep_and_begin(cancellable, res, null);
+    GLib.Source gsource = seahorse_gpgme_gsource_new (gctx, cancellable);
+    gsource.set_callback(on_key_op_generate_complete, res, g_object_unref);
+
+    if (gerr == GPGError.ErrorCode.NO_ERROR)
+        gerr = gpgme_op_genkey_start (gctx, parms.str, null, null); // XXX this is deprecated
+
+    GLib.Error error = null;
+    if (seahorse_gpgme_propagate_error (gerr, out error)) {
+        g_simple_async_result_take_error (res, error);
+        g_simple_async_result_complete_in_idle (res);
+    } else {
+        gsource.attach(g_main_context_default ());
+    }
+    g_source_unref (gsource);
+}
+
+private void on_generate_progress(void* opaque, string what, int type, int current, int total) {
+    GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (opaque);
+    key_op_generate_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+    Seahorse.Progress.update(closure.cancellable, res, "%s", what);
+}
+
+private bool on_key_op_generate_complete (GPGError.ErrorCode gerr) {
+    GError *error = null;
+    if (seahorse_gpgme_propagate_error (gerr, &error))
+        g_simple_async_result_take_error (res, error);
+
+    Seahorse.Progress.end(closure.cancellable, res);
+    g_simple_async_result_complete (res);
+    return false; /* don't call again */
+}
+
+public GPGError.ErrorCode delete(Key pkey) {
+    return op_delete(pkey, false);
+}
+
+public GPGError.ErrorCode delete_pair(Key pkey) {
+    return op_delete(pkey, true);
+}
+
+/* helper function for deleting @skey */
+private GPGError.ErrorCode op_delete(Key pkey, bool secret) {
+    Keyring keyring = (Keyring) pkey.place;
+    g_return_val_if_fail (SEAHORSE_IS_GPGME_KEYRING (keyring), GPG_E (GPG_ERR_INV_KEYRING));
+
+    GPG.Key? key = pkey.get_public();
+    Seahorse.Util.wait_until(key != null);
+
+    GPGError.ErrorCode gerr;
+    GPG.Context ctx = Keyring.new_context(out gerr);
+    if (ctx == null)
+        return gerr;
+
+    gerr = gpgme_op_delete (ctx, key, secret);
+    if (gerr.is_success())
+        keyring.remove_key(SEAHORSE_GPGME_KEY (pkey));
+
+    return gerr;
+}
+
+/* Main key edit setup, structure, and a good deal of method content borrowed from gpa */
+
+/* Edit action function */
+// XXX typedef GPGError.ErrorCode (*EditAction) (uint state, void* data, int fd);
+/* Edit transit function */
+// XXX typedef uint (*EditTransit) (uint current_state, GPG.StatusCode status, string args, void* data, 
GPGError.ErrorCode *err);
+
+/* Edit parameters */
+private struct EditParm {
+    uint state;
+    GPGError.ErrorCode err;
+    EditAction action;
+    EditTransit transit;
+    gpointer data;
+
+    /* Creates new edit parameters with defaults */
+    public EditParm(uint state, EditAction action, EditTransit transit, gpointer data) {
+        this.state = state;
+        this.err = GPG_OK;
+        this.action = action;
+        this.transit = transit;
+        this.data = data;
+    }
+}
+
+/* Edit callback for gpgme */
+private GPGError.ErrorCode seahorse_gpgme_key_op_edit(EditParm data, GPG.StatusCode status, string args, int 
fd) {
+    // Ignore these status lines, as they don't require any response
+    if (status == GPG.StatusCode.EOF || status == GPG.StatusCode.GOT_IT ||
+        status == GPG.StatusCode.NEED_PASSPHRASE || status == GPG.StatusCode.GOOD_PASSPHRASE ||
+        status == GPG.StatusCode.BAD_PASSPHRASE || status == GPG.StatusCode.USERID_HINT ||
+        status == GPG.StatusCode.SIGEXPIRED || status == GPG.StatusCode.KEYEXPIRED ||
+        status == GPG.StatusCode.PROGRESS || status == GPG.StatusCode.KEY_CREATED ||
+        status == GPG.StatusCode.ALREADY_SIGNED || status == GPG.StatusCode.MISSING_PASSPHRASE ||
+        status == GPG.StatusCode.KEY_CONSIDERED)
+        return parms.err;
+
+    debug("[edit key] state: %d / status: %d / args: %s", parms.state, status, args);
+
+    /* Choose the next state based on the current one and the input */
+    parms.state = parms.transit(parms.state, status, args, parms.data, &parms.err);
+
+    /* Choose the action based on the state */
+    if (parms.err.is_success())
+        parms.err = parms.action (parms.state, parms.data, fd);
+
+    return parms.err;
+}
+
+/* Common edit operation */
+// NOTE NOTE NOTE NOTE NOTE
+private GPGError.ErrorCode edit_gpgme_key(GPG.Context? ctx, GPG.Key key, EditParm parms) {
+    assert(key != null);
+    assert(parms != null);
+
+    GPGError.ErrorCode gerr;
+    bool own_context = false;
+    if (ctx == null) {
+        ctx = Keyring.new_context(out gerr);
+        if (ctx == null)
+            return gerr;
+        own_context = true;
+    }
+
+    GPG.Data data_out = seahorse_gpgme_data_new ();
+
+    /* do edit callback, release data */
+    gerr = gpgme_op_edit (ctx, key, seahorse_gpgme_key_op_edit, parms, data_out);
+
+    if (gpgme_err_code (gerr) == GPG_ERR_BAD_PASSPHRASE) {
+        Seahorse.Util.show_error(null, _("Wrong password"), _("This was the third time you entered a wrong 
password. Please try again."));
+    }
+
+    seahorse_gpgme_data_release (data_out);
+    if (own_context)
+        gpgme_release (ctx);
+
+    return gerr;
+}
+
+private GPGError.ErrorCode edit_key(Key pkey, EditParm parms) {
+    Keyring keyring = (Keyring) pkey.place;
+    g_return_val_if_fail (SEAHORSE_IS_GPGME_KEYRING (keyring), GPG_E (GPG_ERR_INV_KEYRING));
+
+    GPG.Key key = pkey.get_public();
+    Seahorse.Util.wait_until(key != null);
+
+    GPGError.ErrorCode gerr;
+    GPG.Context ctx = Keyring.new_context(out gerr);
+    if (ctx != null) {
+        gerr = edit_refresh_gpgme_key (ctx, key, parms);
+    }
+
+    return gerr;
+}
+
+// NOTE NOTE NOTE NOTE NOTE NOTE
+private GPGError.ErrorCode edit_refresh_gpgme_key (GPG.Context ctx, GPG.Key key, EditParm *parms) {
+    GPGError.ErrorCode gerr = edit_gpgme_key (ctx, key, parms);
+    if (gerr.is_success())
+        key.refresh_matching();
+
+    return gerr;
+}
+
+private struct SignParm {
+    uint index;
+    string command;
+    bool expire;
+    SignCheck check;
+}
+
+private enum SignState {
+    START,
+    UID,
+    COMMAND,
+    EXPIRE,
+    CONFIRM,
+    CHECK,
+    QUIT,
+    ERROR,
+}
+
+/* action helper for signing a key */
+private GPGError.ErrorCode sign_action(SignState state, SignParm parm, int fd) {
+    switch (state) {
+        /* select uid */
+        case SignState.UID:
+            PRINTF ((fd, "uid %d", parm.index));
+            break;
+        case SignState.COMMAND:
+            PRINT ((fd, parm.command));
+            break;
+        /* if expires */
+        case SignState.EXPIRE:
+            PRINT ((fd, (parm.expire) ? YES : "N"));
+            break;
+        case SignState.CONFIRM:
+            PRINT ((fd, YES));
+            break;
+        case SignState.CHECK:
+            PRINTF ((fd, "%d", parm.check));
+            break;
+        case SignState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for signing a key */
+private uint sign_transit(SignState current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start state, need to select uid */
+        case SignState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = SignState.UID;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        /* selected uid, go to command */
+        case SignState.UID:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = SignState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        case SignState.COMMAND:
+            /* if doing all uids, confirm */
+            if (status == GPG.StatusCode.GET_BOOL && (args == "keyedit.sign_all.okay"))
+                next_state = SignState.CONFIRM;
+            else if (status == GPG.StatusCode.GET_BOOL && (args == "sign_uid.okay"))
+                next_state = SignState.CONFIRM;
+            /* need to do expires */
+            else if (status == GPG.StatusCode.GET_LINE && (args == "sign_uid.expire"))
+                next_state = SignState.EXPIRE;
+            /*  need to do check */
+            else if (status == GPG.StatusCode.GET_LINE && (args == "sign_uid.class"))
+                next_state = SignState.CHECK;
+            /* if it's already signed then send back an error */
+            else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+                next_state = SignState.ERROR;
+                *err = GPG_E (GPG_ERR_EALREADY);
+            /* All other stuff is unexpected */
+            } else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        /* did expire, go to check */
+        case SignState.EXPIRE:
+            if (status == GPG.StatusCode.GET_LINE && (args == "sign_uid.class"))
+                next_state = SignState.CHECK;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        case SignState.CONFIRM:
+            /* need to do check */
+            if (status == GPG.StatusCode.GET_LINE && (args == "sign_uid.class"))
+                next_state = SignState.CHECK;
+            else if (status == GPG.StatusCode.GET_BOOL && (args == "sign_uid.okay"))
+                next_state = SignState.CONFIRM;
+            /* need to do expire */
+            else if (status == GPG.StatusCode.GET_LINE && (args == "sign_uid.expire"))
+                next_state = SignState.EXPIRE;
+            /* quit */
+            else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = SignState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        /* did check, go to confirm */
+        case SignState.CHECK:
+            if (status == GPG.StatusCode.GET_BOOL && (args == "sign_uid.okay"))
+                next_state = SignState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        /* quit, go to confirm to save */
+        case SignState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = SignState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (SignState.ERROR);
+            }
+            break;
+        /* error, go to quit */
+        case SignState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = SignState.QUIT;
+            else
+                next_state = SignState.ERROR;
+            break;
+
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (SignState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode sign_uid(Uid uid, Key signer, SignCheck check, SignOptions options) {
+    GPG.Key signing_key = signer.get_private();
+    g_return_val_if_fail (signing_key, GPG_E (GPG_ERR_WRONG_KEY_USAGE));
+
+    GPG.Key signed_key = seahorse_gpgme_uid_get_pubkey (uid);
+    g_return_val_if_fail (signing_key, GPG_E (GPG_ERR_INV_VALUE));
+
+    return sign_process (signed_key, signing_key, uid.actual_index, check, options);
+}
+
+public GPGError.ErrorCode sign(Key pkey, Key signer, SignCheck check, SignOptions options) {
+    GPG.Key signing_key = signer.get_private();
+    g_return_val_if_fail (signing_key, GPG_E (GPG_ERR_WRONG_KEY_USAGE));
+
+    GPG.Key signed_key = pkey.get_public();
+
+    return sign_process (signed_key, signing_key, 0, check, options);
+}
+
+private GPGError.ErrorCode sign_process(GPG.Key signed_key, GPG.Key signing_key, uint sign_index, SignCheck 
check, SignOptions options) {
+    GPG.Context ctx = Keyring.new_context(&gerr);
+    if (ctx == null)
+        return gerr;
+
+        GPGError.ErrorCode gerr = gpgme_signers_add (ctx, signing_key);
+        if (!gerr.is_success())
+            return gerr;
+
+    SignParm sign_parm;
+    sign_parm.index = sign_index;
+    sign_parm.expire = ((options & SIGN_EXPIRES) != 0);
+    sign_parm.check = check;
+    sign_parm.command = "%s%ssign".printf((SIGN_NO_REVOKE in options) ? "nr" : "",
+                                          (SIGN_LOCAL in options) ? "l" : "");
+
+    EditParm parms = new EditParm(SIGN_START, sign_action, sign_transit, &sign_parm);
+
+    GPGError.ErrorCode gerr = edit_refresh_gpgme_key(ctx, signed_key, parms);
+
+    return gerr;
+}
+
+private enum PassState {
+    START,
+    COMMAND,
+    PASSPHRASE,
+    QUIT,
+    SAVE,
+    ERROR
+}
+
+// action helper for changing passphrase
+private GPGError.ErrorCode edit_pass_action(PassState state, void* data, int fd) {
+    switch (state) {
+    case PassState.COMMAND:
+        PRINT ((fd, "passwd"));
+        break;
+    case PassState.PASSPHRASE:
+        /* Do nothing */
+        return GPG_OK;
+    case PassState.QUIT:
+        PRINT ((fd, QUIT));
+        break;
+    case PassState.SAVE:
+        PRINT ((fd, YES));
+        break;
+    default:
+        return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for changing passphrase */
+private uint edit_pass_transit(PassState current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+    /* start state, go to command */
+    case PassState.START:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PassState.COMMAND;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PassState.ERROR);
+        }
+        break;
+
+    case PassState.COMMAND:
+        /* did command, go to should be the passphrase now */
+        if (status == GPG.StatusCode.NEED_PASSPHRASE_SYM)
+            next_state = PassState.PASSPHRASE;
+
+        /* If all tries for passphrase were wrong, we get here */
+        else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+            *err = GPG_E (GPG_ERR_BAD_PASSPHRASE);
+            next_state = PassState.ERROR;
+
+        /* No idea how we got here ... */
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PassState.ERROR);
+        }
+        break;
+    /* got passphrase now quit */
+    case PassState.PASSPHRASE:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PassState.QUIT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PassState.ERROR);
+        }
+        break;
+
+    /* quit, go to save */
+    case PassState.QUIT:
+        if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+            next_state = PassState.SAVE;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PassState.ERROR);
+        }
+        break;
+
+    /* error, go to quit */
+    case PassState.ERROR:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PassState.QUIT;
+        else
+            next_state = PassState.ERROR;
+        break;
+
+    default:
+        *err = GPG_E (GPG_ERR_GENERAL);
+        g_return_val_if_reached (PassState.ERROR);
+        break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode change_pass(Key pkey) {
+    g_return_val_if_fail (pkey.usage == Seahorse.Usage.PRIVATE_KEY, GPG_E (GPG_ERR_WRONG_KEY_USAGE));
+
+    EditParm parms = new EditParm(PassState.START, edit_pass_action, edit_pass_transit, null);
+
+    return edit_key(pkey, parms);
+}
+
+private enum TrustState {
+    START,
+    COMMAND,
+    VALUE,
+    CONFIRM,
+    QUIT,
+    ERROR,
+}
+
+/**
+ * Tries to change the owner trust of @pkey to @trust.
+ *
+ * @param pkey #SeahorseGpgmeKey whose trust will be changed
+ * @param trust New trust value that must be at least #Seahorse.Validity.NEVER.
+ * If @pkey is a #SeahorseKeyPair, then @trust cannot be #Seahorse.Validity.UNKNOWN.
+ * If @pkey is not a #SeahorseKeyPair, then @trust cannot be #Seahorse.Validity.ULTIMATE.
+ *
+ * Returns: Error value
+ **/
+public GPGError.ErrorCode set_trust(Key pkey, Seahorse.Validity trust) {
+    EditParm *parms;
+    int menu_choice;
+
+    debug("set_trust: trust = %i", trust);
+
+    g_return_val_if_fail (trust >= Seahorse.Validity.NEVER, GPG_E (GPG_ERR_INV_VALUE));
+    g_return_val_if_fail (seahorse_gpgme_key_get_trust (pkey) != trust, GPG_E (GPG_ERR_INV_VALUE));
+
+    if (pkey.usage == Seahorse.Usage.PRIVATE_KEY) {
+        if (trust == Seahorse.Validity.UNKNOWN)
+            return GPG_E (GPG_ERR_INV_VALUE);
+    } else {
+        if (trust == Seahorse.Validity.ULTIMATE)
+            return GPG_E (GPG_ERR_INV_VALUE);
+    }
+
+    parms = new EditParm(TrustState.START, edit_trust_action, edit_trust_transit, 
Validity.from_seahorse_validity(trust));
+    return edit_key (pkey, parms);
+}
+
+/* action helper for setting trust of a key */
+private GPGError.ErrorCode edit_trust_action (TrustState state, int trust, int fd) {
+    switch (state) {
+        /* enter command */
+        case TrustState.COMMAND:
+            PRINT ((fd, "trust"));
+            break;
+        /* enter numeric trust value */
+        case TrustState.VALUE:
+            PRINTF ((fd, "%d", trust));
+            break;
+        /* confirm ultimate or if save */
+        case TrustState.CONFIRM:
+            PRINT ((fd, YES));
+            break;
+        /* quit */
+        case TrustState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for setting trust of a key */
+private uint edit_trust_transit (TrustState current_state, GPG.StatusCode status, string args, gpointer 
data, GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start state */
+        case TrustState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = TrustState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (TrustState.ERROR);
+            }
+            break;
+        /* did command, next is value */
+        case TrustState.COMMAND:
+            if (status == GPG.StatusCode.GET_LINE && (args == "edit_ownertrust.value"))
+                next_state = TrustState.VALUE;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (TrustState.ERROR);
+            }
+            break;
+        /* did value, go to quit or confirm ultimate */
+        case TrustState.VALUE:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = TrustState.QUIT;
+            else if (status == GPG.StatusCode.GET_BOOL && (args == "edit_ownertrust.set_ultimate.okay"))
+                next_state = TrustState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (TrustState.ERROR);
+            }
+            break;
+        /* did confirm ultimate, go to quit */
+        case TrustState.CONFIRM:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = TrustState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (TrustState.ERROR);
+            }
+            break;
+        /* did quit, go to confirm to finish op */
+        case TrustState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = TrustState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (TrustState.ERROR);
+            }
+            break;
+        /* error, go to quit */
+        case TrustState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = TrustState.QUIT;
+            else
+                next_state = TrustState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (TrustState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+private enum DisableState {
+    START,
+    COMMAND,
+    QUIT,
+    ERROR
+}
+
+/**
+ * seahorse_gpgme_key_op_set_disabled:
+ * @param skey #SeahorseKey to change
+ * @param disabled New disabled state
+ *
+ * Tries to change disabled state of @skey to @disabled.
+ *
+ * Returns: Error value
+ **/
+public GPGError.ErrorCode set_disabled(Key pkey, bool disabled) {
+    // Get command and op
+    string command = disabled ? "disable" : "enable";
+    EditParm parms = new EditParm(DisableState.START, edit_disable_action, edit_disable_transit, command);
+
+    return edit_key(pkey, parms);
+}
+
+/* action helper for disable/enable a key */
+private GPGError.ErrorCode edit_disable_action (DisableState state, gpointer data, int fd) {
+    string command = data;
+
+    switch (state) {
+        case DisableState.COMMAND:
+            PRINT ((fd, command));
+            break;
+        case DisableState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            break;
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for disable/enable a key */
+private uint edit_disable_transit (DisableState current_state, GPG.StatusCode status, string args, gpointer 
data, GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, do command */
+        case DisableState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DisableState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DisableState.ERROR);
+            }
+            break;
+        /* did command, quit */
+        case DisableState.COMMAND:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DisableState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DisableState.ERROR);
+            }
+        /* error, quit */
+        case DisableState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DisableState.QUIT;
+            else
+                next_state = DisableState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (DisableState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+private struct ExpireParm {
+    uint    index;
+    time_t    expires;
+}
+
+private enum ExpireState {
+    START,
+    SELECT,
+    COMMAND,
+    DATE,
+    QUIT,
+    SAVE,
+    ERROR
+}
+
+/* action helper for changing expiration date of a key */
+private GPGError.ErrorCode edit_expire_action (ExpireState state, ExpireParm parm, int fd) {
+    switch (state) {
+        /* selected key */
+        case ExpireState.SELECT:
+            PRINTF ((fd, "key %d", parm.index));
+            break;
+        case ExpireState.COMMAND:
+            PRINT ((fd, "expire"));
+            break;
+        /* set date */
+        case ExpireState.DATE:
+            PRINT ((fd, (parm.expires) ?
+                Seahorse.Util.get_date_string (parm.expires) : "0"));
+            break;
+        case ExpireState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        case ExpireState.SAVE:
+            PRINT ((fd, YES));
+            break;
+        case ExpireState.ERROR:
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for changing expiration date of a key */
+private uint edit_expire_transit (ExpireState current_state, GPG.StatusCode status, string args, gpointer 
data, GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start state, selected key */
+        case ExpireState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = ExpireState.SELECT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (ExpireState.ERROR);
+            }
+            break;
+        /* selected key, do command */
+        case ExpireState.SELECT:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = ExpireState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (ExpireState.ERROR);
+            }
+            break;
+        /* did command, set expires */
+        case ExpireState.COMMAND:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keygen.valid"))
+                next_state = ExpireState.DATE;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (ExpireState.ERROR);
+            }
+            break;
+        /* set expires, quit */
+        case ExpireState.DATE:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = ExpireState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (ExpireState.ERROR);
+            }
+            break;
+        /* quit, save */
+        case ExpireState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = ExpireState.SAVE;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (ExpireState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case ExpireState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = ExpireState.QUIT;
+            else
+                next_state = ExpireState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (ExpireState.ERROR);
+            break;
+    }
+    return next_state;
+}
+
+public GPGError.ErrorCode set_expires(SubKey subkey, time_t expires) {
+    g_return_val_if_fail (expires != (time_t)seahorse_pgp_subkey_get_expires (SEAHORSE_PGP_SUBKEY (subkey)), 
GPG_E (GPG_ERR_INV_VALUE));
+
+    GPG.Key key = subkey.get_pubkey();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    ExpireParm exp_parm;
+    exp_parm.index = subkey.get_index();
+    exp_parm.expires = expires;
+
+    EditParm parms = new EditParm(ExpireState.START, edit_expire_action, edit_expire_transit, &exp_parm);
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+private enum AddRevokerState {
+    START,
+    COMMAND,
+    SELECT,
+    CONFIRM,
+    QUIT,
+    ERROR
+}
+
+/* action helper for adding a revoker */
+private GPGError.ErrorCode add_revoker_action (AddRevokerState state, string keyid, int fd) {
+    switch (state) {
+        case AddRevokerState.COMMAND:
+            PRINT ((fd, "addrevoker"));
+            break;
+        /* select revoker */
+        case AddRevokerState.SELECT:
+            PRINT ((fd, keyid));
+            break;
+        case AddRevokerState.CONFIRM:
+            PRINT ((fd, YES));
+            break;
+        case AddRevokerState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for adding a revoker */
+private uint add_revoker_transit (uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, do command */
+        case AddRevokerState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddRevokerState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddRevokerState.ERROR);
+            }
+            break;
+        /* did command, select revoker */
+        case AddRevokerState.COMMAND:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keyedit.add_revoker"))
+                next_state = AddRevokerState.SELECT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddRevokerState.ERROR);
+            }
+            break;
+        /* selected revoker, confirm */
+        case AddRevokerState.SELECT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == "keyedit.add_revoker.okay"))
+                next_state = AddRevokerState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddRevokerState.ERROR);
+            }
+            break;
+        /* confirmed, quit */
+        case AddRevokerState.CONFIRM:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddRevokerState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddRevokerState.ERROR);
+            }
+            break;
+        /* quit, confirm(=save) */
+        case AddRevokerState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = AddRevokerState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddRevokerState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case AddRevokerState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddRevokerState.QUIT;
+            else
+                next_state = AddRevokerState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (AddRevokerState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode add_revoker (Key pkey, Key revoker) {
+    if (pkey.usage != Seahorse.Usage.PRIVATE_KEY || revoker.usage != Seahorse.Usage.PRIVATE_KEY)
+        return GPG_E (GPG_ERR_WRONG_KEY_USAGE);
+
+    string? keyid = pkey.keyid;
+    g_return_val_if_fail (keyid, GPG_E (GPG_ERR_INV_VALUE));
+
+    EditParm *parms;
+    parms = new EditParm(AddRevokerState.START, add_revoker_action, add_revoker_transit, keyid);
+
+    return edit_key (pkey, parms);
+}
+
+private enum AddUidState {
+    START,
+    COMMAND,
+    NAME,
+    EMAIL,
+    COMMENT,
+    QUIT,
+    SAVE,
+    ERROR
+}
+
+private struct UidParm {
+    string name;
+    string email;
+    string comment;
+}
+
+/* action helper for adding a new user ID */
+private GPGError.ErrorCode add_uid_action(AddUidState state, UidParm parm, int fd) {
+    switch (state) {
+        case AddUidState.COMMAND:
+            PRINT ((fd, "adduid"));
+            break;
+        case AddUidState.NAME:
+            PRINT ((fd, parm.name));
+            break;
+        case AddUidState.EMAIL:
+            PRINT ((fd, parm.email));
+            break;
+        case AddUidState.COMMENT:
+            PRINT ((fd, parm.comment));
+            break;
+        case AddUidState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        case AddUidState.SAVE:
+            PRINT ((fd, YES));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for adding a new user ID */
+private uint add_uid_transit(uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, do command */
+        case AddUidState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddUidState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddUidState.ERROR);
+            }
+            break;
+        /* did command, do name */
+        case AddUidState.COMMAND:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keygen.name"))
+                next_state = AddUidState.NAME;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddUidState.ERROR);
+            }
+            break;
+        /* did name, do email */
+        case AddUidState.NAME:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keygen.email"))
+                next_state = AddUidState.EMAIL;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddUidState.ERROR);
+            }
+            break;
+        /* did email, do comment */
+        case AddUidState.EMAIL:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keygen.comment"))
+                next_state = AddUidState.COMMENT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddUidState.ERROR);
+            }
+            break;
+        /* did comment, quit */
+        case AddUidState.COMMENT:
+            next_state = AddUidState.QUIT;
+            break;
+        /* quit, save */
+        case AddUidState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = AddUidState.SAVE;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddUidState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case AddUidState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddUidState.QUIT;
+            else
+                next_state = AddUidState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (AddUidState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode add_uid (Key pkey, string name, string? email, string? comment) {
+    if (pkey.usage != Seahorse.Usage.PRIVATE_KEY)
+        return GPG_E (GPG_ERR_WRONG_KEY_USAGE);
+    if (name == null && name < 5)
+        return GPG_E (GPG_ERR_INV_VALUE);
+
+    EditParm *parms;
+    UidParm *uid_parm;
+    uid_parm = g_new (UidParm, 1);
+    uid_parm.name = name;
+    uid_parm.email = email;
+    uid_parm.comment = comment;
+
+    parms = new EditParm(AddUidState.START, add_uid_action, add_uid_transit, uid_parm);
+
+    return edit_key(pkey, parms);
+}
+
+private enum AddKeyState {
+    START,
+    COMMAND,
+    TYPE,
+    LENGTH,
+    EXPIRES,
+    QUIT,
+    SAVE,
+    ERROR
+}
+
+private struct SubkeyParm {
+    uint type;
+    uint length;
+    time_t expires;
+}
+
+/* action helper for adding a subkey */
+private GPGError.ErrorCode add_key_action (AddKeyState state, SubkeyParm parm, int fd) {
+    switch (state) {
+        case AddKeyState.COMMAND:
+            PRINT ((fd, "addkey"));
+            break;
+        case AddKeyState.TYPE:
+            PRINTF ((fd, "%d", parm.type));
+            break;
+        case AddKeyState.LENGTH:
+            PRINTF ((fd, "%d", parm.length));
+            break;
+        /* Get exact date or 0 */
+        case AddKeyState.EXPIRES:
+            PRINT ((fd, (parm.expires) ?
+                Seahorse.Util.get_date_string (parm.expires) : "0"));
+            break;
+        case AddKeyState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        case AddKeyState.SAVE:
+            PRINT ((fd, YES));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for adding a subkey */
+private uint add_key_transit(AddKeyState current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, do command */
+        case AddKeyState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddKeyState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddKeyState.ERROR);
+            }
+            break;
+        /* did command, do type */
+        case AddKeyState.COMMAND:
+        case AddKeyState.TYPE:
+        case AddKeyState.LENGTH:
+        case AddKeyState.EXPIRES:
+            if (status == GPG.StatusCode.GET_LINE && (args == "keygen.algo"))
+                next_state = AddKeyState.TYPE;
+            else if (status == GPG.StatusCode.GET_LINE && (args == "keygen.size"))
+                next_state = AddKeyState.LENGTH;
+            else if (status == GPG.StatusCode.GET_LINE && (args == "keygen.valid"))
+                next_state = AddKeyState.EXPIRES;
+            else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddKeyState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                return AddKeyState.ERROR; /* Legitimate errors error here */
+            }
+            break;
+        /* quit, save */
+        case AddKeyState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = AddKeyState.SAVE;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (AddKeyState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case AddKeyState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = AddKeyState.QUIT;
+            else
+                next_state = AddKeyState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (AddKeyState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode add_subkey(Key pkey, SeahorseKeyEncType type, uint length, time_t expires) {
+    EditParm *parms;
+    SubkeyParm *key_parm;
+    uint real_type;
+    SeahorseKeyTypeTable table;
+    GPGError.ErrorCode gerr;
+
+    g_return_val_if_fail (pkey.get_usage() == Seahorse.Usage.PRIVATE_KEY, GPG_E (GPG_ERR_WRONG_KEY_USAGE));
+
+    gerr = seahorse_gpgme_get_keytype_table (&table);
+    g_return_val_if_fail (gerr.is_success(), gerr);
+
+    /* Check length range & type */
+    switch (type) {
+        case DSA:
+            real_type = table.dsa_sign;
+            g_return_val_if_fail (length >= DSA_MIN && length <= DSA_MAX, GPG_E (GPG_ERR_INV_VALUE));
+            break;
+        case ELGAMAL:
+            real_type = table.elgamal_enc;
+            g_return_val_if_fail (length >= ELGAMAL_MIN && length <= LENGTH_MAX, GPG_E (GPG_ERR_INV_VALUE));
+            break;
+        case RSA_SIGN: case RSA_ENCRYPT:
+            if (type == RSA_SIGN)
+                real_type = table.rsa_sign;
+            else
+                real_type = table.rsa_enc;
+            g_return_val_if_fail (length >= RSA_MIN && length <= LENGTH_MAX, GPG_E (GPG_ERR_INV_VALUE));
+            break;
+        default:
+            g_return_val_if_reached (GPG_E (GPG_ERR_INV_VALUE));
+            break;
+    }
+
+    key_parm = SubkeyParm();
+    key_parm.type = real_type;
+    key_parm.length = length;
+    key_parm.expires = expires;
+
+    parms = new EditParm(AddKeyState.START, add_key_action, add_key_transit, key_parm);
+
+    return edit_key (pkey, parms);
+}
+
+private enum DelKeyState {
+    START,
+    SELECT,
+    COMMAND,
+    CONFIRM,
+    QUIT,
+    ERROR
+}
+
+/* action helper for deleting a subkey */
+private GPGError.ErrorCode del_key_action(DelKeyState state, gpointer data, int fd) {
+    switch (state) {
+        /* select key */
+        case DelKeyState.SELECT:
+            PRINTF ((fd, "key %d", GPOINTER_TO_UINT (data)));
+            break;
+        case DelKeyState.COMMAND:
+            PRINT ((fd, "delkey"));
+            break;
+        case DelKeyState.CONFIRM:
+            PRINT ((fd, YES));
+            break;
+        case DelKeyState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            return GPG_E (GPG_ERR_GENERAL);
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for deleting a subkey */
+private uint del_key_transit(DelKeyState current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, select key */
+        case DelKeyState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DelKeyState.SELECT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DelKeyState.ERROR);
+            }
+            break;
+        /* selected key, do command */
+        case DelKeyState.SELECT:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DelKeyState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DelKeyState.ERROR);
+            }
+            break;
+        case DelKeyState.COMMAND:
+            /* did command, confirm */
+            if (status == GPG.StatusCode.GET_BOOL && args == "keyedit.remove.subkey.okay")
+                next_state = DelKeyState.CONFIRM;
+            /* did command, quit */
+            else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DelKeyState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DelKeyState.ERROR);
+            }
+            break;
+        /* confirmed, quit */
+        case DelKeyState.CONFIRM:
+            next_state = DelKeyState.QUIT;
+            break;
+        /* quit, confirm(=save) */
+        case DelKeyState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = DelKeyState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (DelKeyState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case DelKeyState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = DelKeyState.QUIT;
+            else
+                next_state = DelKeyState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (DelKeyState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode del_subkey(SubKey subkey) {
+    GPG.Key key = seahorse_gpgme_subkey_get_pubkey (subkey);
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    int index = subkey.get_index();
+    EditParm parms = new EditParm(DelKeyState.START, del_key_action,
+                                    del_key_transit, uint_TO_POINTER (index));
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+private struct RevSubkeyParm {
+    uint index;
+    RevokeReason reason;
+    string description;
+}
+
+private enum RevSubkeyState {
+    START,
+    SELECT,
+    COMMAND,
+    CONFIRM,
+    REASON,
+    DESCRIPTION,
+    ENDDESC,
+    QUIT,
+    ERROR
+}
+
+/* action helper for revoking a subkey */
+private GPGError.ErrorCode rev_subkey_action (RevSubkeyState state, RevSubkeyParm parm, int fd) {
+    switch (state) {
+        case RevSubKeyState.SELECT:
+            PRINTF ((fd, "key %d", parm.index));
+            break;
+        case RevSubKeyState.COMMAND:
+            PRINT ((fd, "revkey"));
+            break;
+        case RevSubKeyState.CONFIRM:
+            PRINT ((fd, YES));
+            break;
+        case RevSubKeyState.REASON:
+            PRINTF ((fd, "%d", parm.reason));
+            break;
+        case RevSubKeyState.DESCRIPTION:
+            PRINTF ((fd, "%s", parm.description));
+            break;
+        case RevSubKeyState.ENDDESC:
+            /* Need empty line, which is written at the end */
+            break;
+        case RevSubKeyState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            g_return_val_if_reached (GPG_E (GPG_ERR_GENERAL));
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for revoking a subkey */
+private uint rev_subkey_transit(uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+        /* start, select key */
+        case RevSubKeyState.START:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = RevSubKeyState.SELECT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* selected key, do command */
+        case RevSubKeyState.SELECT:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = RevSubKeyState.COMMAND;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* did command, confirm */
+        case RevSubKeyState.COMMAND:
+            if (status == GPG.StatusCode.GET_BOOL && (args == "keyedit.revoke.subkey.okay"))
+                next_state = RevSubKeyState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        case RevSubKeyState.CONFIRM:
+            /* did confirm, do reason */
+            if (status == GPG.StatusCode.GET_LINE && (args == "ask_revocation_reason.code"))
+                next_state = RevSubKeyState.REASON;
+            /* did confirm, quit */
+            else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = RevSubKeyState.QUIT;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* did reason, do description */
+        case RevSubKeyState.REASON:
+            if (status == GPG.StatusCode.GET_LINE && (args == "ask_revocation_reason.text"))
+                next_state = RevSubKeyState.DESCRIPTION;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        case RevSubKeyState.DESCRIPTION:
+            /* did description, end it */
+            if (status == GPG.StatusCode.GET_LINE && (args == "ask_revocation_reason.text"))
+                next_state = RevSubKeyState.ENDDESC;
+            /* did description, confirm */
+            else if (status == GPG.StatusCode.GET_BOOL && (args == "ask_revocation_reason.okay"))
+                next_state = RevSubKeyState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* ended description, confirm */
+        case RevSubKeyState.ENDDESC:
+            if (status == GPG.StatusCode.GET_BOOL && (args == "ask_revocation_reason.okay"))
+                next_state = RevSubKeyState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* quit, confirm(=save) */
+        case RevSubKeyState.QUIT:
+            if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+                next_state = RevSubKeyState.CONFIRM;
+            else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (RevSubKeyState.ERROR);
+            }
+            break;
+        /* error, quit */
+        case RevSubKeyState.ERROR:
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+                next_state = RevSubKeyState.QUIT;
+            else
+                next_state = RevSubKeyState.ERROR;
+            break;
+        default:
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (RevSubKeyState.ERROR);
+            break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode revoke_subkey(SubKey subkey, RevokeReason reason, string description) {
+    GPG.SubKey gsubkey = subkey.get_subkey();
+    g_return_val_if_fail (!gsubkey.revoked, GPG_E (GPG_ERR_INV_VALUE));
+
+    GPG.Key key = subkey.get_pubkey();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    RevSubkeyParm rev_parm;
+    rev_parm.index = subkey.get_index();
+    rev_parm.reason = reason;
+    rev_parm.description = description;
+
+    EditParm parms = new EditParm(RevSubKeyState.START, rev_subkey_action,
+                                    rev_subkey_transit, &rev_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+private struct PrimaryParm {
+    uint index;
+}
+
+private enum PrimaryState {
+    START,
+    SELECT,
+    COMMAND,
+    QUIT,
+    SAVE,
+    ERROR
+}
+
+/* action helper for setting primary uid */
+private GPGError.ErrorCode primary_action(PrimaryState state, PrimaryParm parm, int fd) {
+    switch (state) {
+    case PrimaryState.SELECT:
+        /* Note that the GPG id is not 0 based */
+        PRINTF ((fd, "uid %d", parm.index));
+        break;
+    case PrimaryState.COMMAND:
+        PRINT ((fd, "primary"));
+        break;
+    case PrimaryState.QUIT:
+        PRINT ((fd, QUIT));
+        break;
+    case PrimaryState.SAVE:
+        PRINT ((fd, YES));
+        break;
+    default:
+        g_return_val_if_reached (GPG_E (GPG_ERR_GENERAL));
+        break;
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for setting primary key */
+private uint primary_transit(uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+
+    /* start, select key */
+    case PrimaryState.START:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PrimaryState.SELECT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PrimaryState.ERROR);
+        }
+        break;
+
+    /* selected key, do command */
+    case PrimaryState.SELECT:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PrimaryState.COMMAND;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PrimaryState.ERROR);
+        }
+        break;
+
+    /* did command, quit */
+    case PrimaryState.COMMAND:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PrimaryState.QUIT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PrimaryState.ERROR);
+        }
+        break;
+
+    /* quitting so save */
+    case PrimaryState.QUIT:
+        if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+            next_state = PrimaryState.SAVE;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PrimaryState.ERROR);
+        }
+        break;
+
+    /* error, quit */
+    case PrimaryState.ERROR:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PrimaryState.QUIT;
+        else
+            next_state = PrimaryState.ERROR;
+        break;
+
+    default:
+        *err = GPG_E (GPG_ERR_GENERAL);
+        g_return_val_if_reached (PrimaryState.ERROR);
+        break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode primary_uid(Uid uid) {
+    PrimaryParm pri_parm;
+    EditParm *parms;
+
+    g_return_val_if_fail (SEAHORSE_IS_GPGME_UID (uid), GPG_E (GPG_ERR_WRONG_KEY_USAGE));
+
+    /* Make sure not revoked */
+    GPG.UserID userid = seahorse_gpgme_uid_get_userid (uid);
+    g_return_val_if_fail (userid != null && !userid.revoked && !userid.invalid,
+                          GPG_E (GPG_ERR_INV_VALUE));
+
+    GPG.Key key = seahorse_gpgme_uid_get_pubkey (uid);
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    pri_parm.index = seahorse_gpgme_uid_get_actual_index (uid);
+
+    parms = new EditParm(PrimaryState.START, primary_action,
+                                    primary_transit, &pri_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+
+private struct DelUidParm {
+    uint index;
+}
+
+private enum DelUidState {
+    START,
+    SELECT,
+    COMMAND,
+    CONFIRM,
+    QUIT,
+    SAVE,
+    ERROR,
+}
+
+/* action helper for removing a uid */
+private GPGError.ErrorCode del_uid_action (uint state, DelUidParm parm, int fd) {
+    switch (state) {
+    case DelUidState.SELECT:
+        PRINTF ((fd, "uid %d", parm.index));
+        break;
+    case DelUidState.COMMAND:
+        PRINT ((fd, "deluid"));
+        break;
+    case DelUidState.CONFIRM:
+        PRINT ((fd, YES));
+        break;
+    case DelUidState.QUIT:
+        PRINT ((fd, QUIT));
+        break;
+    case DelUidState.SAVE:
+        PRINT ((fd, YES));
+        break;
+    default:
+        g_return_val_if_reached (GPG_E (GPG_ERR_GENERAL));
+        break;
+    }
+
+    PRINT ((fd, "\n"));
+    return GPG_OK;
+}
+
+/* transition helper for setting deleting a uid */
+private uint del_uid_transit(uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+
+    /* start, select key */
+    case DelUidState.START:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = DelUidState.SELECT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (DelUidState.ERROR);
+        }
+        break;
+
+    /* selected key, do command */
+    case DelUidState.SELECT:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = DelUidState.COMMAND;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (DelUidState.ERROR);
+        }
+        break;
+
+    /* did command, confirm */
+    case DelUidState.COMMAND:
+        if (status == GPG.StatusCode.GET_BOOL && (args == "keyedit.remove.uid.okay"))
+            next_state = DelUidState.CONFIRM;
+        else if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = DelUidState.QUIT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (RevSubKeyState.ERROR);
+        }
+        break;
+
+    /* confirmed, quit */
+    case DelUidState.CONFIRM:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = DelUidState.QUIT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (DelUidState.ERROR);
+        }
+        break;
+
+    /* quitted so save */
+    case DelUidState.QUIT:
+        if (status == GPG.StatusCode.GET_BOOL && (args == SAVE))
+            next_state = DelUidState.SAVE;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (RevSubKeyState.ERROR);
+        }
+        break;
+
+    /* error, quit */
+    case DelUidState.ERROR:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = DelUidState.QUIT;
+        else
+            next_state = DelUidState.ERROR;
+        break;
+
+    default:
+        *err = GPG_E (GPG_ERR_GENERAL);
+        g_return_val_if_reached (DelUidState.ERROR);
+        break;
+    }
+
+    return next_state;
+}
+
+public GPGError.ErrorCode del_uid(Uid uid) {
+    GPG.Key key = uid.get_pubkey();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    DelUidParm del_uid_parm;
+    del_uid_parm.index = uid.get_actual_index();
+
+    EditParm parms = new EditParm(DelUidState.START, del_uid_action,
+                                    del_uid_transit, &del_uid_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+private struct PhotoIdAddParm {
+    string filename;
+}
+
+private enum PhotoIdAddState {
+    START,
+    COMMAND,
+    URI,
+    BIG,
+    QUIT,
+    SAVE,
+    ERROR,
+}
+
+public GPGError.ErrorCode photo_add(Key pkey, string filename) {
+    g_return_val_if_fail (filename, GPG_E (GPG_ERR_INV_VALUE));
+
+    GPG.Key key = pkey.get_public();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    PhotoIdAddParm photoid_add_parm;
+    photoid_add_parm.filename = filename;
+
+    EditParm parms = new EditParm(PhotoIdAddState.START, photoid_add_action,
+                                    photoid_add_transit, &photoid_add_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+public GPGError.ErrorCode photo_delete(Photo photo) {
+    GPG.Key key = photo.get_pubkey();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    DelUidParm del_uid_parm;
+    del_uid_parm.index = photo.get_index();
+
+    EditParm parms = new EditParm(DelUidState.START, del_uid_action, del_uid_transit, &del_uid_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+/* action helper for adding a photoid to a #SeahorseKey */
+private GPGError.ErrorCode photoid_add_action(PhotoIdAddState state, PhotoIdAddParm parm, int fd) {
+    switch (state) {
+    case PhotoIdAddState.COMMAND:
+        PRINT ((fd, "addphoto"));
+        break;
+    case PhotoIdAddState.URI:
+        PRINT ((fd, parm.filename));
+        break;
+    case PhotoIdAddState.BIG:
+        PRINT ((fd, YES));
+        break;
+    case PhotoIdAddState.QUIT:
+        PRINT ((fd, QUIT));
+        break;
+    case PhotoIdAddState.SAVE:
+        PRINT ((fd, YES));
+        break;
+    default:
+        g_return_val_if_reached (GPG_E (GPG_ERR_GENERAL));
+        break;
+    }
+
+    Seahorse.Util.print_fd (fd, "\n");
+    return GPG_OK;
+}
+
+private uint photoid_add_transit(uint current_state, GPG.StatusCode status, string args, gpointer data, 
GPGError.ErrorCode *err) {
+    uint next_state;
+
+    switch (current_state) {
+    case PhotoIdAddState.START:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PhotoIdAddState.COMMAND;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdAddState.ERROR);
+        }
+        break;
+    case PhotoIdAddState.COMMAND:
+        if (status == GPG.StatusCode.GET_LINE && (args == "photoid.jpeg.add")) {
+            next_state = PhotoIdAddState.URI;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdAddState.ERROR);
+        }
+        break;
+   case PhotoIdAddState.URI:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+            next_state = PhotoIdAddState.QUIT;
+        } else if (status == GPG.StatusCode.GET_BOOL && (args == "photoid.jpeg.size")) {
+            next_state = PhotoIdAddState.BIG;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdAddState.ERROR);
+        }
+        break;
+    case PhotoIdAddState.BIG:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+            next_state = PhotoIdAddState.QUIT;
+        /* This happens when the file is invalid or can't be accessed */
+        } else if (status == GPG.StatusCode.GET_LINE && (args == "photoid.jpeg.add")) {
+            *err = GPG_E (GPG_ERR_USER_1);
+            return PhotoIdAddState.ERROR;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdAddState.ERROR);
+        }
+        break;
+    case PhotoIdAddState.QUIT:
+        if (status == GPG.StatusCode.GET_BOOL && (args == SAVE)) {
+            next_state = PhotoIdAddState.SAVE;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdAddState.ERROR);
+        }
+        break;
+    default:
+        *err = GPG_E (GPG_ERR_GENERAL);
+        g_return_val_if_reached (PhotoIdAddState.ERROR);
+        break;
+    }
+
+    return next_state;
+}
+
+private struct PhotoIdLoadParm {
+    GList *photos;
+    uint uid;
+    uint num_uids;
+    char *output_file;
+    GPG.Key key;
+}
+
+private enum PhotoIdLoadState {
+    START,
+    SELECT,
+    OUTPUT_IMAGE,
+    DESELECT,
+    QUIT,
+    ERROR,
+}
+
+public GPGError.ErrorCode photos_load(Key pkey) {
+    // Make sure there's enough room for the .jpg extension
+    const string image_path = "/tmp/seahorse-photoid-XXXXXX\0\0\0\0";
+
+    GPG.Key key = pkey.get_public();
+    if (key == null || key.subkeys == null || key.subkeys.keyid == null)
+        return GPG_E(GPG_ERR_INV_VALUE);
+    string keyid = key.subkeys.keyid;
+
+    debug ("PhotoIDLoad Start");
+
+    GPGError.ErrorCode gerr;
+    int fd = g_mkstemp (image_path);
+    if(fd == -1) {
+        gerr = GPG_E(GPG_ERR_GENERAL);
+    } else {
+        g_unlink(image_path);
+        close(fd);
+        strcat (image_path, ".jpg");
+
+        PhotoIdLoadParm photoid_load_parm;
+        photoid_load_parm.uid = 1;
+        photoid_load_parm.num_uids = 0;
+        photoid_load_parm.photos = null;
+        photoid_load_parm.output_file = image_path;
+        photoid_load_parm.key = key;
+
+        debug("PhotoIdLoad KeyID %s", keyid);
+        gerr = Operation.num_uids(null, keyid, out (photoid_load_parm.num_uids));
+        debug("PhotoIDLoad Number of UIDs %i", photoid_load_parm.num_uids);
+
+        if (gerr.is_success()) {
+            setenv ("SEAHORSE_IMAGE_FILE", image_path, 1);
+            string oldpath = getenv("PATH");
+
+            string path = "%s:%s".printf(Config.EXECDIR, getenv ("PATH"));
+            setenv ("PATH", path, 1);
+
+            EditParm parms = new EditParm(PhotoIdLoadState.START, photoid_load_action,
+                                            photoid_load_transit, &photoid_load_parm);
+
+            /* generate list */
+            gerr = edit_gpgme_key (null, key, parms);
+            setenv ("PATH", oldpath, 1);
+
+            if (gerr.is_success())
+                pkey.photos = photoid_load_parm.photos;
+        }
+    }
+
+    debug ("PhotoIDLoad Done");
+
+    return gerr;
+}
+
+public GPGError.ErrorCode photo_primary(Photo photo) {
+    GPG.Key key = photo.get_pubkey();
+    g_return_val_if_fail (key, GPG_E (GPG_ERR_INV_VALUE));
+
+    PrimaryParm pri_parm;
+    pri_parm.index = photo.get_index();
+
+    EditParm parms = new EditParm(PrimaryState.START, primary_action, primary_transit, &pri_parm);
+
+    return edit_refresh_gpgme_key (null, key, parms);
+}
+
+/* action helper for getting a list of photoids attached to a #SeahorseKey */
+private GPGError.ErrorCode photoid_load_action(PhotoIdLoadState state, PhotoIdLoadParm parm, int fd) {
+    switch (state) {
+        case PhotoIdLoadState.SELECT:
+            PRINTF ((fd, "uid %d", parm.uid));
+            break;
+        case PhotoIdLoadState.OUTPUT_IMAGE:
+            PRINT ((fd, "showphoto"));
+            break;
+        case PhotoIdLoadState.DESELECT:
+            PRINTF ((fd, "uid %d", parm.uid));
+            break;
+        case PhotoIdLoadState.QUIT:
+            PRINT ((fd, QUIT));
+            break;
+        default:
+            g_return_val_if_reached (GPG_E (GPG_ERR_GENERAL));
+            break;
+    }
+
+    Seahorse.Util.print_fd(fd, "\n");
+    return GPG_OK;
+}
+
+private uint photoid_load_transit(PhotoIdLoadState current_state, GPG.StatusCode status, string args, 
PhotoIdLoadParm parm, GPGError.ErrorCode *err) {
+    uint next_state = 0;
+    Posix.stat st;
+    GError *error = null;
+
+    switch (current_state) {
+    /* start, get photoid list */
+    case PhotoIdLoadState.START:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT))
+            next_state = PhotoIdLoadState.SELECT;
+        else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdLoadState.ERROR);
+        }
+        break;
+
+    case PhotoIdLoadState.SELECT:
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+            next_state = PhotoIdLoadState.OUTPUT_IMAGE;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdLoadState.ERROR);
+        }
+        break;
+
+    case PhotoIdLoadState.OUTPUT_IMAGE:
+
+        if (FileUtils.test(parm.output_file, G_FILE_TEST_EXISTS)) {
+            Photo photo = new Photo(parm.key, null, parm.uid);
+            parm.photos = g_list_append (parm.photos, photo);
+            GdkPixbuf *pixbuf = null;
+
+            if (g_stat (parm.output_file, &st) == -1) {
+                warning("couldn't stat output image file '%s': %s", parm.output_file, g_strerror (errno));
+            } else if (st.st_size > 0) {
+                try {
+                    pixbuf = new Gdk.Pixbuf.from_file(parm.output_file);
+                } catch (GLib.Error e) {
+                    warning("Loading image %s failed: %s", parm.output_file, e.message ?? "unknown");
+                }
+            }
+            g_unlink (parm.output_file);
+
+            // Load a 'missing' icon
+            if (!pixbuf) {
+                pixbuf = Gtk.IconTheme.get_default().load_icon("gnome-unknown", 48, 0, null);
+            }
+
+            photo.pixbuf = pixbuf;
+        }
+
+        if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+            next_state = PhotoIdLoadState.DESELECT;
+        } else {
+            *err = GPG_E (GPG_ERR_GENERAL);
+            g_return_val_if_reached (PhotoIdLoadState.ERROR);
+        }
+        break;
+
+    case PhotoIdLoadState.DESELECT:
+        if (parm.uid < parm.num_uids) {
+            parm.uid = parm.uid + 1;
+            debug("PhotoIDLoad Next UID %i", parm.uid);
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+                next_state = PhotoIdLoadState.SELECT;
+            } else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (PhotoIdLoadState.ERROR);
+            }
+        } else {
+            if (status == GPG.StatusCode.GET_LINE && (args == PROMPT)) {
+                next_state = PhotoIdLoadState.QUIT;
+                debug("PhotoIDLoad Quiting Load");
+            } else {
+                *err = GPG_E (GPG_ERR_GENERAL);
+                g_return_val_if_reached (PhotoIdLoadState.ERROR);
+            }
+        }
+        break;
+
+    case PhotoIdLoadState.QUIT:
+        /* Shouldn't be reached */
+        *err = GPG_E (GPG_ERR_GENERAL);
+        debug("PhotoIDLoad Reached Quit");
+        g_return_val_if_reached (PhotoIdLoadState.ERROR);
+        break;
+
+    default:
+        *err = GPG_E (GPG_ERR_GENERAL);
+        g_return_val_if_reached (PhotoIdLoadState.ERROR);
+        break;
+    }
+
+    return next_state;
+}
+}
+
+}
diff --git a/pgp/gpgme-key.vala b/pgp/gpgme-key.vala
new file mode 100644
index 0000000..9f4376e
--- /dev/null
+++ b/pgp/gpgme-key.vala
@@ -0,0 +1,422 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.GpgME.Key : Pgp.Key, Deletable, Exportable {
+    /* enum { */
+    /*     PROP_0, */
+    /*     PROP_PUBKEY, */
+    /*     PROP_SECKEY, */
+    /*     PROP_VALIDITY, */
+    /*     PROP_TRUST */
+    /* }; */
+
+    /**
+     * GPGME Public Key that this object represents
+     */
+    private GPG.Key? pubkey;
+
+    /**
+     * GPGME Secret Key that this object represents
+     */
+    private GPG.Key? seckey;
+    // Whether we have a secret key or not
+    private bool has_secret;
+
+    // What to load our public key as
+    private GPG.KeylistMode list_mode;
+    // Photos were loaded
+    private bool photos_loaded;
+
+    // Loading is blocked while this flag is set
+    private int block_loading;
+
+    public bool deletable {
+        get { return Seahorse.Flags.DELETABLE in this.flags; }
+    }
+
+    public bool exportable {
+        get { return Seahorse.Flags.EXPORTABLE in this.flags; }
+    }
+
+    public Seahorse.Validity validity {
+        get {
+            if (!require_key_public(GPG.KeylistMode.LOCAL)
+                || this.pubkey == null || this.pubkey.uids == null)
+                return Seahorse.Validity.UNKNOWN;
+            if (this.pubkey.revoked)
+                return Seahorse.Validity.REVOKED;
+            if (this.pubkey.disabled)
+                return Seahorse.Validity.DISABLED;
+            return this.pubkey.uids.validity.to_seahorse_validity();
+        }
+    }
+
+    public Seahorse.Validity trust {
+        get {
+            if (!require_key_public(GPG.KeylistMode.LOCAL))
+                return Seahorse.Validity.UNKNOWN;
+
+            return this.pubkey.owner_trust.to_seahorse_validity();
+        }
+    }
+
+    public Key (Seahorse.Place sksrc, GPG.Key pubkey, GPG.Key seckey){
+        GLib.Object(place: sksrc,
+                    pubkey: pubkey,
+                    seckey: seckey);
+
+        realize();
+    }
+
+    public GPG.Key? get_public() {
+        if (require_key_public (self, GPG.KeylistMode.LOCAL))
+            return this._pubkey;
+        return null;
+    }
+
+    public void set_public(GPG.Key? key) {
+        if (this.pubkey)
+            gpgme_key_unref (this.pubkey);
+        this.pubkey = key;
+        if (this.pubkey) {
+            gpgme_key_ref (this.pubkey);
+            this.list_mode |= this.pubkey.keylist_mode;
+        }
+
+        realize();
+        notify_property("fingerprint");
+        notify_property("validity");
+        notify_property("trust");
+        notify_property("expires");
+        notify_property("length");
+        notify_property("algo");
+    }
+
+    public GPG.Key get_private() {
+        if (require_key_private())
+            return this._seckey;
+        return null;   
+    }
+
+    public void set_private(GPG.Key? key) {
+        if (this._seckey != null)
+            gpgme_key_unref (this.seckey);
+        this.seckey = key;
+        if (this._seckey != null)
+            gpgme_key_ref (this.seckey);
+
+        realize();
+    }
+
+    public void refresh_matching() {
+        if (this.subkeys.keyid == null)
+            return;
+
+        Key? key = Backend.get_default_keyring().lookup(this.subkeys.keyid);
+        if (key != null)
+            key.refresh();
+    }
+
+    /* -----------------------------------------------------------------------------
+     * INTERNAL HELPERS
+     */
+
+    static bool load_gpgme_key(string keyid, GPG.KeylistMode mode, int secret, GPG.Key key) {
+        GPG.Context ctx;
+        GPG.Error error = GPG.Context.Context(out ctx);
+        if (!error.is_success())
+            return false;
+
+        ctx.set_keylist_mode(mode);
+        error = ctx.op_keylist_start (keyid, secret);
+        if (!error.is_success()) {
+            ctx.op_keylist_next(key);
+            ctx.op_keylist_end();
+        }
+
+        // XXX exists?
+        // gpgme_release (ctx);
+
+        Error *gerror = null;
+        if (seahorse_gpgme_propagate_error (error, &gerror)) {
+            message("couldn't load GPGME key: %s", gerror->message);
+            g_clear_error (&gerror);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void load_key_public(GPG.KeylistMode list_mode) {
+        if (this.block_loading)
+            return;
+
+        list_mode |= this.list_mode;
+
+        GPG.Key? key = null;
+        if (load_gpgme_key(get_keyid(), list_mode, false, out key)) {
+            this.list_mode = list_mode;
+            set_public(key);
+            gpgme_key_unref (key);
+        }
+    }
+
+    private bool require_key_public(GPG.KeylistMode list_mode) {
+        if (!this.pubkey || (this.list_mode & list_mode) != list_mode)
+            load_key_public (self, list_mode);
+        return this.pubkey && (this.list_mode & list_mode) == list_mode;
+    }
+
+    private void load_key_private() {
+        if (!this.has_secret || this.block_loading)
+            return;
+
+        GPG.Key? key = null;
+        if (load_gpgme_key (get_keyid(), GPG.KeylistMode.LOCAL, true, out key)) {
+            set_private(key);
+            gpgme_key_unref (key);
+        }
+    }
+
+    private bool require_key_private() {
+        if (!this.seckey)
+            load_key_private();
+        return this.seckey != null;
+    }
+
+    private bool require_key_uids() {
+        return require_key_public(GPG.KeylistMode.LOCAL);
+    }
+
+    private bool require_key_subkeys() {
+        return require_key_public(GPG.KeylistMode.LOCAL);
+    }
+
+    private void load_photos() {
+        if (this.block_loading)
+            return;
+
+        GPG.Error gerr = KeyOperation.photos_load(this);
+        if (!gerr.is_success())
+            message("couldn't load key photos: %s", gpgme_strerror (gerr));
+        else
+            this.photos_loaded = true;
+    }
+
+    private bool require_photos() {
+        if (!this.photos_loaded)
+            load_photos();
+        return this.photos_loaded;
+    }
+
+    // This function is necessary because the uid stored in a GPG.UserID
+    // struct is only usable with gpgme functions.  Problems will be caused if
+    // that uid is used with functions found in seahorse-gpgme-key-op.h.  This
+    // function is only to be called with uids from GPG.UserID structs.
+    private void renumber_actual_uids () {
+        ++this.block_loading;
+        List<Pgp.Photo> photos = this.photos;
+        List<Pgp.Uid> uids = this.uids;
+        --this.block_loading;
+
+        // First we build a bitmap of where all the photo uid indexes are
+        bool[] index_map = g_array_new (false, true, sizeof (bool));
+        foreach (Pgp.Photo photo in photos) {
+            int index = ((GpgME.Photo) photo).get_index();
+            if (index >= index_map->len)
+                g_array_set_size (index_map, index + 1);
+            g_array_index (index_map, bool, index) = true;
+        }
+
+        // Now for each UID we add however many photo indexes are below the gpgme index
+        foreach (Pgp.Uid uid in uids) {
+            int index = ((GpgME.Uid) uid).get_gpgme_index();
+            for (uint i = 0; i < index_map.length && i < index; ++i) {
+                if (g_array_index (index_map, bool, index))
+                    ++index;
+            }
+            ((GpgME.Uid) uid).set_actual_index(index + 1);
+        }
+    }
+
+    private void realize_uids() {
+        List<GPG.UserID> results = new List<GPG.UserID>();
+        bool changed = false;
+
+        GPG.UserID guid = this.pubkey ? this.pubkey.uids : null;
+
+        // Look for out of sync or missing UIDs
+        this.uids.foreach((uid) => {
+            // Bring this UID up to date
+            if (guid && uid.is_same(guid)) {
+                if (uid.get_userid() != guid) {
+                    uid.pubkey = this.pubkey;
+                    uid.userid = guid;
+                    changed = true;
+                }
+                results = seahorse_object_list_append (results, uid);
+                guid = guid->next;
+            }
+        });
+
+        // Add new UIDs
+        while (guid != null) {
+            Uid uid = new Uid(self, guid);
+            changed = true;
+            results = seahorse_object_list_append (results, uid);
+            g_object_unref (uid);
+            guid = guid->next;
+        }
+
+        if (changed)
+            this.uids = results;
+    }
+
+    private void realize_subkeys() {
+        List? list = null;
+
+        if (this.pubkey) {
+            for (GPG.SubKey gsubkey = this.pubkey.subkeys; gsubkey != null; gsubkey = gsubkey->next)
+                list.prepend(new GpgME.Subkey(this.pubkey, gsubkey));
+
+            list.reverse();
+        }
+
+        base.set_subkeys(list);
+    }
+
+    void realize() {
+        if (this.pubkey == null || this.pubkey.subkeys == null
+            || !require_key_public(GPG.KeylistMode.LOCAL))
+            return;
+
+        // Update the sub UIDs
+        realize_uids();
+        realize_subkeys();
+
+        // The flags
+        Seahorse.Flags flags = Seahorse.Flags.EXPORTABLE | Seahorse.Flags.DELETABLE;
+
+        if (!this.pubkey.disabled && !this.pubkey.expired &&
+            !this.pubkey.revoked && !this.pubkey.invalid) {
+            if (this.validity >= Seahorse.Validity.MARGINAL)
+                flags |= Seahorse.Flags.IS_VALID;
+            if (this.pubkey.can_encrypt)
+                flags |= Seahorse.Flags.CAN_ENCRYPT;
+            if (this.seckey && this.pubkey.can_sign)
+                flags |= Seahorse.Flags.CAN_SIGN;
+        }
+
+        if (this.pubkey.expired)
+            flags |= Seahorse.Flags.EXPIRED;
+
+        if (this.pubkey.revoked)
+            flags |= Seahorse.Flags.REVOKED;
+
+        if (this.pubkey.disabled)
+            flags |= Seahorse.Flags.DISABLED;
+
+        if (this.trust >= Seahorse.Validity.MARGINAL
+            && !(this.pubkey.revoked || this.pubkey.disabled || this.pubkey.expired))
+            flags |= Seahorse.Flags.TRUSTED;
+
+        // The type
+        Usage usage;
+        if (this.seckey != null) {
+            this.usage = Usage.PRIVATE_KEY;
+            flags |= Seahorse.Flags.PERSONAL;
+        } else {
+            this.usage = Usage.PUBLIC_KEY;
+        }
+
+        this.object_flags = flags;
+        this.actions = Actions.instance;
+
+        base.realize();
+    }
+
+    public void ensure_signatures() {
+        require_key_public(GPG.KeylistMode.LOCAL | GPG.KeylistMode.SIGS);
+    }
+
+    public void refresh () {
+        if (this.pubkey != null)
+            load_key_public(this.list_mode);
+        if (this.seckey != null)
+            load_key_private();
+        if (this.photos_loaded)
+            load_photos();
+    }
+
+    public override List<GPG.UserID> get_uids() {
+        require_key_uids();
+        return base.get_uids();
+    }
+
+    private void set_uids(List<Uid> uids) {
+        base.set_uids(uids);
+
+        // Keep our own copy of the UID list
+        this.uids = uids.copy();
+
+        renumber_actual_uids ();
+    }
+
+    public override List<GPG.SubKey> get_subkeys() {
+        require_key_subkeys();
+        return base.get_subkeys();
+    }
+
+    public override List<Pgp.Photo> get_photos() {
+        require_photos();
+        return base.get_photos();
+    }
+
+    private void set_photos(List<Photo> photos) {
+        this.photos_loaded = true;
+        base.set_photos(photos);
+        renumber_actual_uids();
+    }
+
+    public GLib.List<Exporter> create_exporters(ExporterType type) {
+        List<Exporter> exporters = new List<Seahorse.Exporter>();
+
+        if (type == ExporterType.TEXTUAL)
+            exporters.append(new Exporter(this, false, false));
+        exporters.append(new Exporter(this, true, false));
+
+        return exporters;
+    }
+
+    public Seahorse.Deleter create_deleter() {
+        if (this.seckey != null)
+            return new SecretDeleter(this);
+
+        return new KeyDeleter(this);
+    }
+
+    public bool compare(Key? other) {
+        if (other == null)
+            return null;
+
+        return this.keyid == other.keyid;
+    }
+}
diff --git a/pgp/gpgme-keyring.vala b/pgp/gpgme-keyring.vala
new file mode 100644
index 0000000..f3479a3
--- /dev/null
+++ b/pgp/gpgme-keyring.vala
@@ -0,0 +1,605 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004-2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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 {
+namespace GpgME {
+
+public class Keyring : GLib.Object, Gcr.Collection, Seahorse.Place {
+
+    // Amount of keys to load in a batch
+    public const string DEFAULT_LOAD_BATCH = 50;
+
+    private HashTable<string, Key> keys;
+    // Source for refresh timeout
+    private uint scheduled_refresh;
+    // For monitoring the .gnupg directory
+    private FileMonitor monitor_handle;
+    // Orphan secret key
+    private List orphan_secret;
+
+    public string label {
+        owned get { return _("GnuPG keys"); }
+    }
+
+    public string description {
+        owned get { return _("GnuPG: default keyring directory"); }
+    }
+
+    public string uri {
+        owned get { return _("gnupg://"); }
+    }
+
+    public Icon icon {
+        owned get { return new ThemedIcon(Gcr.ICON_GNUPG); }
+    }
+
+    /// XXX Properties
+    public Gtk.ActionGroup? actions {
+        owned get { return null; }
+    }
+
+    static construct {
+        debug("init gpgme version %s", gpgme_check_version (null));
+
+        if (Config.ENABLE_NLS) {
+            gpgme_set_locale (null, LC_CTYPE, setlocale (LC_CTYPE, null));
+            gpgme_set_locale (null, LC_MESSAGES, setlocale (LC_MESSAGES, null));
+        }
+    }
+
+    [Flags]
+    private enum LoadPart {
+        LOAD_FULL = 0x01,
+        LOAD_PHOTOS = 0x02
+    }
+
+    /**
+     * Creates a new PGP key source
+     **/
+    public Keyring() {
+        this.keys = new HashTable.full(seahorse_pgp_keyid_hash, seahorse_pgp_keyid_equal);
+        this.scheduled_refresh = 0;
+        this.monitor_handle = null;
+
+        string? gpg_homedir = seahorse_gpg_homedir ();
+        if (gpg_homedir != null) {
+            File? file = File.new_for_path(gpg_homedir);
+            return_if_fail (file != null);
+
+            GError *err = null;
+            this.monitor_handle = file.monitor_directory(G_FILE_MONITOR_NONE, null, &err);
+            if (this.monitor_handle) {
+                this.monitor_handle.changed.connect(monitor_gpg_homedir);
+            } else {
+                warning("Couldn't monitor the GPG home directory: %s: %s", gpg_homedir, err && err.message ? 
err.message : "");
+            }
+        }
+    }
+
+    public GPG.Context new_context (GPGError.ErrorCode *gerr) {
+        gpgme_protocol_t proto = GPGME_PROTOCOL_OpenPGP;
+        GPGError.ErrorCode error = 0;
+        GPG.Context ctx = null;
+
+        error = gpgme_engine_check_version (proto);
+        if (error == 0)
+            error = GPG.Context.context(out ctx);
+        if (error == 0)
+            error = gpgme_set_protocol (ctx, proto);
+
+        if (error != 0) {
+            message("couldn't initialize gnupg properly: %s",
+                       gpgme_strerror (error));
+            if (gerr)
+                *gerr = error;
+            return null;
+        }
+
+        ctx.set_passphrase_cb(passphrase_get, null);
+        ctx.set_keylist_mode(GPG.KeyListMode.LOCAL);
+        if (gerr)
+            *gerr = 0;
+        return ctx;
+    }
+
+    static GPGError.ErrorCode passphrase_get(void* dummy, string? passphrase_hint, string? passphrase_info, 
int flags, int fd) {
+        GPGError.ErrorCode err;
+        string pass;
+
+        bool confirm = (passphrase_info != null && passphrase_info.length < 16);
+        if (confirm)
+            flags |= SEAHORSE_PASS_NEW;
+
+        string[]? split_uid = (passphrase_hint != null)? passphrase_hint.split(" ", 2) : null;
+        string? errmsg = (SEAHORSE_PASS_BAD in flags)? _("Wrong passphrase.") : null;
+
+        string? label = null;
+        if (split_uid != null && split_uid[0] && split_uid[1]) {
+            if (SEAHORSE_PASS_NEW in flags)
+                label = _("Enter new passphrase for “%s”").printf(split_uid[1]);
+            else
+                label = _("Enter passphrase for “%s”").printf(split_uid[1]);
+        } else {
+            label = (SEAHORSE_PASS_NEW in flags)? _("Enter new passphrase")
+                                                : _("Enter passphrase");
+        }
+
+        Gtk.Dialog dialog = seahorse_passphrase_prompt_show (_("Passphrase"), errmsg ? errmsg : label,
+                                                  null, null, confirm);
+
+        switch (dialog.run()) {
+            case Gtk.ResponseType.ACCEPT:
+                pass = seahorse_passphrase_prompt_get (dialog);
+                seahorse_util_printf_fd (fd, "%s\n", pass);
+                err = GPG_OK;
+                break;
+            default:
+                err = GPG_E (GPG_ERR_CANCELED);
+                break;
+        };
+
+        dialog.destroy();
+        return err;
+    }
+
+    public Key? lookup(string? keyid) {
+        if (keyid == null)
+            return null;
+
+        return this.keys.lookup(keyid);
+    }
+
+    public void remove_key(Key key) {
+        if (this.keys.lookup(key.keyid) != key)
+            return;
+
+        this.keys.remove(key.keyid);
+        removed(key);
+    }
+
+    private struct keyring_list_closure {
+        Keyring keyring;
+        Cancellable cancellable;
+        ulong cancelled_sig;
+        GPG.Context gctx;
+        HashTable checks;
+        int parts;
+        int loaded;
+    }
+
+    public async void list_async(string[] patterns, int parts, bool secret, Cancellable cancellable) {
+        closure = g_new0 (keyring_list_closure, 1);
+        int parts = parts;
+        GPG.Context? gctx = new_context(&gerr);
+
+        /* Start the key listing */
+        GPG.ErrorCode gerr = 0;
+        if (gctx != null) {
+            if (parts & LOAD_FULL)
+                gctx.set_keylist_mode(GPG.KeyListMode.SIGS | gctx.get_keylist_mode());
+
+            if (patterns)
+                gerr = Operation.keylist_ext_start(closure.gctx, patterns, secret, 0);
+            else
+                gerr = Operation.keylist_start(closure.gctx, null, secret);
+        }
+
+        if (gerr != 0) {
+            seahorse_gpgme_propagate_error (gerr, &error);
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+            return;
+        }
+
+        // Loading all the keys?
+        if (patterns == null) {
+            HashTable<string, string> checks = new HashTable(seahorse_pgp_keyid_hash, 
seahorse_pgp_keyid_equal);
+            this.keys.foreach((keyid, key) => {
+                if ((secret && key.usage == Seahorse.Usage.PRIVATE_KEY)
+                    || (!secret && key.usage == Seahorse.Usage.PUBLIC_KEY)) {
+                    checks.insert(keyid, keyid);
+                }
+            });
+        }
+
+        Seahorse.Progress.prep_and_begin(cancellable, res, null);
+        if (cancellable)
+            closure.cancelled_sig = cancellable.connect(on_keyring_list_cancelled);
+
+        Idle.add(on_idle_list_batch_of_keys, Priority.LOW);
+    }
+
+    /* Add a key to the context  */
+    private Key add_key_to_context(GPG.Key key) {
+        if (key.subkeys == null && key.subkeys.keyid == null)
+            return null;
+
+        string keyid = key.subkeys.keyid;
+        if (keyid == null)
+            return null;
+
+        // Check if we can just replace the key on the object
+        Key? prev = lookup(keyid);
+        if (prev != null) {
+            if (key.secret)
+                prev.seckey = key;
+            else
+                prev.pubkey = key;
+            return prev;
+        }
+
+        // Create a new key with secret
+        Key? pkey = null;
+        if (key.secret) {
+            pkey = new Key(null, key);
+
+            // Since we don't have a public key yet, save this away
+            this.orphan_secret = this.orphan_secret.append(pkey);
+
+            // No key was loaded as far as everyone is concerned
+            return null;
+        }
+
+        // Just a new public key
+
+        // Check for orphans
+        foreach (Key orphan in this.orphan_secret) {
+            GPG.Key? seckey = orphan.get_private();
+            if (seckey == null || seckey.subkeys == null || seckey.subkeys.keyid == null)
+                return;
+
+            // Look for a matching key
+            if (keyid == seckey.subkeys.keyid) {
+
+                // Set it up properly
+                pkey = orphan;
+                pkey.pubkey = key;
+
+                // Remove item from orphan list cleanly
+                this.orphan_secret = this.orphan_secret.remove_link(orphan);
+                break;
+            }
+        }
+
+        pkey = pkey ?? new Key(this, key, null);
+
+        // Add to context
+        this.keys.insert(keyid, pkey);
+        added(pkey);
+
+        return pkey;
+    }
+
+
+    /**
+     * Remove the given key from the context
+     */
+    private void _remove_key(string keyid) {
+        Key? key = this.keys.lookup(keyid);
+        if (key != null)
+            remove_key(key);
+    }
+
+    /**
+     * Completes one batch of key loading
+     */
+    static bool on_idle_list_batch_of_keys(gpointer data) {
+        keyring_list_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+
+        /* We load until done if batch is zero */
+        uint batch = DEFAULT_LOAD_BATCH;
+
+        while (batch-- > 0) {
+
+            if (!gctx.keylist_next(&key.is_success())) {
+                gctx.keylist_end();
+
+                /* If we were a refresh loader, then we remove the keys we didn't find */
+                if (closure.checks) {
+                    closure.checks.foreach((keyid, derp) => {
+                        _remove_key(closure.keyring, keyid);
+                    });
+                }
+
+                Seahorse.Progress.end(closure.cancellable, res);
+                g_simple_async_result_complete (res);
+                return false; /* Remove event handler */
+            }
+
+            g_return_val_if_fail (key.subkeys && key.subkeys.keyid, false);
+
+            // During a refresh if only new or removed keys
+            if (closure.checks != null) {
+                // Make note that this key exists in key ring
+                closure.checks.remove(key.subkeys.keyid);
+
+            }
+
+            Key pkey = add_key_to_context (closure.keyring, key);
+
+            /* Load additional info */
+            if (pkey && closure.parts & LOAD_PHOTOS)
+                KeyOperation.photos_load(pkey);
+
+            gpgme_key_unref (key);
+            closure.loaded++;
+        }
+
+        string detail = ngettext("Loaded %d key", "Loaded %d keys", closure.loaded).printf(closure.loaded);
+        Seahorse.Progress.update(closure.cancellable, res, detail);
+
+        return true;
+    }
+
+    static void on_keyring_list_cancelled (Cancellable cancellable) {
+        keyring_list_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+
+        cgtx.keylist_end();
+    }
+
+    static bool list_finish(Keyring keyring, AsyncResult result) throws GLib.Error {
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (keyring),
+                              seahorse_gpgme_keyring_list_async), false);
+
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return false;
+
+        return true;
+    }
+
+    private void cancel_scheduled_refresh () {
+        if (this.scheduled_refresh != 0) {
+            g_debug ("cancelling scheduled refresh event");
+            g_source_remove (this.scheduled_refresh);
+            this.scheduled_refresh = 0;
+        }
+    }
+
+    public async void load_async(Cancellable? cancellable) throws GLib.Error {
+        yield load_full_async(null, 0, cancellable);
+    }
+
+    private async void load_full_async(string[]? patterns, int parts, Cancellable *cancellable) {
+        // Schedule a dummy refresh. This blocks all monitoring for a while */
+        cancel_scheduled_refresh();
+        this.scheduled_refresh = Timeout.add (500, () => {
+            debug("dummy refresh event occurring now");
+            this.scheduled_refresh = 0;
+            return false; // don't run again
+        });
+        debug("scheduled a dummy refresh");
+
+        debug("refreshing keys...");
+
+        // Secret keys
+        yield list_async(patterns, 0, true, cancellable);
+        // Public keys
+        yield list_async(patterns, 0, false, cancellable);
+    }
+
+    private struct keyring_import_closure {
+        Cancellable cancellable;
+        Keyring keyring;
+        GPG.Context gctx;
+        gpgme_data_t data;
+        gchar **patterns;
+        List<Key> keys;
+    }
+
+    public async void import_async(InputStream input, Cancellable cancellable) {
+        keyring_import_closure *closure;
+        GPGError.ErrorCode gerr = 0;
+        GError *error = null;
+        Source? gsource = null;
+
+        closure.gctx = new_context (&gerr);
+        closure.data = seahorse_gpgme_data_input (input);
+        g_simple_async_result_set_op_res_gpointer (res, closure, keyring_import_free);
+
+        if (gerr == 0) {
+            Seahorse.Progress.prep_and_begin(cancellable, res, null);
+            gsource = seahorse_gpgme_gsource_new (closure.gctx, cancellable);
+            gsource.set_callback(on_keyring_import_complete);
+            gerr = Operation.import_start(closure.gctx, closure.data);
+        }
+
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+
+        } else {
+            gsource.attach (MainContext.default ());
+        }
+    }
+
+    private void on_keyring_import_loaded (GLib.Object source, GAsyncResult *result) {
+        keyring_import_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+
+        for (uint i = 0; closure.patterns[i] != null; i++) {
+            Key? key = this.keys.lookup(closure.patterns[i]);
+            if (key == null) {
+                warning("imported key but then couldn't find it in keyring: %s", closure.patterns[i]);
+                continue;
+            }
+
+            closure.keys.prepend(key);
+        }
+
+        Seahorse.Progress.end(closure.cancellable, res);
+        g_simple_async_result_complete (res);
+    }
+
+    static bool on_keyring_import_complete (GPGError.ErrorCode gerr) {
+        keyring_import_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        gpgme_import_result_t results;
+        gpgme_import_status_t import;
+        GError *error = null;
+
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            return false; /* don't call again */
+        }
+
+        /* Figure out which keys were imported */
+        results = gpgme_op_import_result (closure.gctx);
+        if (results == null) {
+            g_simple_async_result_complete (res);
+            return false; /* don't call again */
+        }
+
+        /* Dig out all the fingerprints for use as load patterns */
+        closure.patterns = new string[results.considered + 1];
+        for (int i = 0, import = results.imports; i < results.considered && import; import = import.next) {
+            if (import.result.is_success())
+                closure.patterns[i++] = g_strdup (import.fpr);
+        }
+
+        /* See if we've managed to import any ... */
+        if (closure.patterns[0] == null) {
+
+            /* ... try and find out why */
+            if (results.considered > 0 && results.no_user_id) {
+                string msg = _("Invalid key data (missing UIDs). This may be due to a computer with a date 
set in the future or a missing self-signature.");
+                g_simple_async_result_set_error (res, SEAHORSE_ERROR, -1, "%s", msg);
+            }
+
+            g_simple_async_result_complete (res);
+            return false; /* don't call again */
+        }
+
+        /* Reload public keys */
+        seahorse_gpgme_keyring_load_full_async (closure.keyring, (string *)closure.patterns,
+                                                LOAD_FULL, closure.cancellable,
+                                                on_keyring_import_loaded, g_object_ref (res));
+
+        return false; /* don't call again */
+    }
+
+    GList * seahorse_gpgme_keyring_import_finish (Keyring *self, GAsyncResult *result, GError **error) {
+        keyring_import_closure *closure;
+        GList *results;
+
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+                              seahorse_gpgme_keyring_import_async), null);
+
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return null;
+
+        closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+        results = closure.keys;
+        closure.keys = null;
+        return results;
+    }
+
+    private void monitor_gpg_homedir(FileMonitor handle, File file, File other_file, FileMonitorEvent 
event_type) {
+        if (event_type == G_FILE_MONITOR_EVENT_CHANGED ||
+            event_type == G_FILE_MONITOR_EVENT_DELETED ||
+            event_type == G_FILE_MONITOR_EVENT_CREATED) {
+
+            string name = file.get_basename();
+            if (name.has_suffix(".gpg")) {
+                if (this.scheduled_refresh == 0) {
+                    debug("scheduling refresh event due to file changes");
+                    this.scheduled_refresh = Timeout.add (500, () => {
+                        debug("scheduled refresh event ocurring now");
+                        cancel_scheduled_refresh();
+                        load_async(null, null, null);
+                        return false; /* don't run again */
+                    });
+                }
+            }
+        }
+    }
+
+    static void
+    seahorse_gpgme_keyring_get_property (GLib.Object obj,
+                                         guint prop_id,
+                                         GValue *value,
+                                         GParamSpec *pspec)
+    {
+        SeahorsePlace *place = SEAHORSE_PLACE (obj);
+
+        switch (prop_id) {
+        case PROP_LABEL:
+            g_value_take_string (value, seahorse_gpgme_keyring_get_label (place));
+            break;
+        case PROP_DESCRIPTION:
+            g_value_take_string (value, seahorse_gpgme_keyring_get_description (place));
+            break;
+        case PROP_ICON:
+            g_value_take_object (value, seahorse_gpgme_keyring_get_icon (place));
+            break;
+        case PROP_URI:
+            g_value_take_string (value, seahorse_gpgme_keyring_get_uri (place));
+            break;
+        case PROP_ACTIONS:
+            g_value_take_object (value, seahorse_gpgme_keyring_get_actions (place));
+            break;
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+            break;
+        }
+    }
+
+    static void
+    seahorse_gpgme_keyring_dispose (GLib.Object object)
+    {
+        Keyring *self = SEAHORSE_GPGME_KEYRING (object);
+        GList *l;
+
+        if (this.actions)
+            this.actions.set_sensitive(true);
+        this.keys.remove_all();
+
+        cancel_scheduled_refresh();
+        if (this.monitor_handle) {
+            g_object_unref (this.monitor_handle);
+            this.monitor_handle = null;
+        }
+
+        for (l = this.orphan_secret; l != null; l = g_list_next (l))
+            g_object_unref (l.data);
+        g_list_free (this.orphan_secret);
+        this.orphan_secret = null;
+
+        base.dispose (object);
+    }
+
+    public uint get_length() {
+        return this.keys.length;
+    }
+
+    public List get_objects() {
+        return this.keys.get_values();
+    }
+
+    public bool contains(GLib.Object object) {
+        GpgME.Key? key = object as GpgME.Key;
+        if (key == null)
+            return false;
+
+        return this.keys.lookup(key.keyid) == key;
+    }
+}
+
+}
+}
diff --git a/pgp/gpgme-photo.vala b/pgp/gpgme-photo.vala
new file mode 100644
index 0000000..73da97f
--- /dev/null
+++ b/pgp/gpgme-photo.vala
@@ -0,0 +1,38 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.GpgME.Photo : Pgp.Photo {
+
+    /**
+     * GPGME Public Key this photo is on
+     */
+    public GPG.Key pubkey { get; private set; }
+
+    /**
+     * Index of photo UID
+     */
+    public uint index { get; set; }
+
+    public Photo(GPG.Key pubkey, Gdk.Pixbuf pixbuf, uint index) {
+        GLib.Object(pubkey: pubkey,
+                    pixbuf: pixbuf,
+                    index: index);
+    }
+}
diff --git a/pgp/gpgme-photos.vala b/pgp/gpgme-photos.vala
new file mode 100644
index 0000000..b4e1297
--- /dev/null
+++ b/pgp/gpgme-photos.vala
@@ -0,0 +1,267 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006  Stefan Walter
+ * Copyright (C) 2017 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/>.
+ */
+
+// XXX namespace or class (or part of Photo?)
+namespace Seahorse.GpgME.Photos {
+public const string DEFAULT_WIDTH = 120;
+public const string DEFAULT_HEIGHT = 150;
+public const string LARGE_WIDTH = 240;
+public const string LARGE_HEIGHT = 288;
+
+public bool add(Key pkey, Gtk.Window? parent, string? path) {
+    GError *error = null;
+    gpgme_error_t gerr;
+    bool res = true;
+
+    string? filename = path;
+    if (filename == null) {
+        Gtk.FileChooserDialog chooser =
+            new Gtk.FileChooserDialog(_("Choose Photo to Add to Key"), parent,
+                                      Gtk.FileChooserAction.OPEN,
+                                      Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+                                      Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT,
+                                      null);
+
+        chooser.set_default_response(Gtk.ResponseType.ACCEPT);
+        chooser.set_local_only(true);
+        add_image_files (chooser);
+
+        if (chooser.run() == Gtk.ResponseType.ACCEPT)
+            filename = chooser.get_filename();
+
+        chooser.destroy();
+
+        if (!filename)
+            return false;
+    }
+
+    string? tempfile = null;
+    try {
+        prepare_photo_id(parent, filename, out tempfile);
+    } catch (GLib.Error e) {
+        Util.show_error(null, _("Couldn’t prepare photo"), e.message);
+        return false;
+    }
+
+    GPG.Error gerr = GpgME.KeyOperation.photo_add(pkey, tempfile ?? filename);
+    if (!gerr.is_success()) {
+        // A special error value set by seahorse_key_op_photoid_add to
+        // denote an invalid format file
+        if (gerr.error_code() == GPGError.ErrorCode.USER_1)
+            Util.show_error(null, _("Couldn’t add photo"),
+                            _("The file could not be loaded. It may be in an invalid format"));
+        else
+            Util.show_error(null, _("Couldn’t add photo"), gerr.strerror());
+        res = false;
+    }
+
+    if (tempfile)
+        unlink (tempfile);
+
+    return res;
+}
+
+public bool delete(Photo photo, Gtk.Window? parent) {
+    Gtk.Dialog dialog = new Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL,
+                                              Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE,
+                                              _("Are you sure you want to remove the current photo from your 
key?"));
+
+    dialog.add_button(Gtk.Stock.DELETE, Gtk.ResponseType.ACCEPT);
+    dialog.add_button(Gtk.Stock.CANCEL, Gtk.ResponseType.REJECT);
+
+    int response = dialog.run();
+    dialog.destroy();
+
+    if (response != Gtk.ResponseType.ACCEPT)
+        return false;
+
+    GPG.Error gerr = GpgME.KeyOperation.photo_delete(photo);
+    if (!gerr.is_success() || gerr.is_cancelled()) {
+        Util.show_error(null, _("Couldn’t delete photo"), gerr.strerror());
+        return false;
+    }
+
+    return true;
+}
+
+private bool calc_scale(out int width, out int height) {
+    double recpx = DEFAULT_WIDTH + DEFAULT_HEIGHT;
+    double imgpx = width + height;
+
+    if (imgpx <= recpx)
+        return false;
+
+    // Keep aspect ratio, and don't squash large aspect ratios unnecessarily.
+    double ratio = imgpx / recpx;
+    height = ((gdouble)(*height)) / ratio;
+    width = ((gdouble)(*width)) / ratio;
+    return true;
+}
+
+private guint suggest_resize (Gtk.Window? parent) {
+    Gtk.Dialog dialog = new Gtk.MessageDialog.with_markup(parent,
+                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE,
+                _("<big><b>The photo is too large</b></big>\nThe recommended size for a photo on your key is 
%d × %d pixels."),
+                DEFAULT_WIDTH, DEFAULT_HEIGHT);
+
+    dialog.add_buttons(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+                       _("_Don’t Resize"), Gtk.ResponseType.REJECT,
+                       _("_Resize"), Gtk.ResponseType.ACCEPT);
+
+    uint response = dialog.run();
+    dialog.destroy();
+
+    return response;
+}
+
+private bool save_to_fd(string buf, gsize count, GError **error, gpointer data) {
+    int fd = GPOINTER_TO_INT (data);
+    gssize written;
+
+    written = write (fd, buf, count);
+    if (written != (gssize) count) {
+        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                     "%s", g_strerror (errno));
+        return false;
+    }
+
+    return true;
+}
+
+private bool prepare_photo_id (Gtk.Window parent, string path, out string result) throws GLib.Error {
+    GdkPixbuf *pixbuf = null;
+    GdkPixbuf *sampled;
+    bool rewrite = false;
+    bool resample = false;
+    bool suggest = false;
+    int fd;
+
+    assert (path);
+    assert (result);
+    assert (!error || !*error);
+
+    result = null;
+
+    int width, height;
+    Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(path, out width, out height);
+    if (!format) {
+        g_set_error (error, SEAHORSE_ERROR, -1,
+                     _("This is not a image file, or an unrecognized kind of image file. Try to use a JPEG 
image."));
+        return false;
+    }
+
+    // JPEGs we can use straight up
+    if (format.get_name() == "jpeg") {
+        // If so we may just be able to use it straight up
+        Posix.stat sb;
+        if (stat (path, &sb) != -1) {
+
+            // Large file size, suggest resampling
+            if (sb.st_size > 8192)
+                suggest = true;
+        }
+
+    // Other formats
+    } else {
+        rewrite = true;
+
+        // Check for large, but allow strange orientations
+        if ((width + height) > (LARGE_WIDTH + LARGE_HEIGHT))
+            suggest = true;
+    }
+
+    // Suggest to the user that we resize the photo
+    if (suggest) {
+        switch (suggest_resize (parent)) {
+        case Gtk.ResponseType.ACCEPT:
+            resample = true;
+            rewrite = true;
+            break;
+        case Gtk.ResponseType.REJECT:
+            resample = false;
+            break;
+        default:
+            // false with error not set == cancel
+            return false;
+        }
+    }
+
+    /* No rewrite */
+    if (!rewrite)
+        return true;
+
+    // Load the photo if necessary
+    pixbuf = new Gdk.Pixbuf.from_file(path, error);
+    if (!pixbuf)
+        return false;
+
+    /* Resize it properly */
+    if (resample && calc_scale(out width, out height)) {
+        sampled = pixbuf.scale_simple(width, height, GDK_INTERP_BILINEAR);
+
+        g_return_val_if_fail (sampled != null, false);
+        pixbuf = sampled;
+        sampled = null;
+    }
+
+    /* And write it out to a temp */
+    fd = g_file_open_tmp ("seahorse-photo.XXXXXX", result, error);
+    if (fd == -1) {
+        g_object_unref (pixbuf);
+        return false;
+    }
+
+    bool r = pixbuf.save_to_callback(save_to_fd, GINT_TO_POINTER (fd),
+                                "jpeg", error, "quality", "75", null);
+
+    close (fd);
+
+    if (!r) {
+        g_free (*result);
+        *result = null;
+        return false;
+    }
+
+    return true;
+}
+
+private void add_image_files(Gtk.Widget dialog) {
+    Gtk.FileFilter filter = new Gtk.FileFilter();
+    filter.set_name(filter, _("All image files"));
+    Gdk.Pixbuf.get_formats().foreach((format) => {
+        foreach (string mime_type in format.get_mime_types())
+            filter.add_mime_type(mime_type);
+    });
+
+    dialog.add_filter(filter);
+    dialog.set_filter(filter);
+
+    filter = new Gtk.FileFilter();
+    filter.set_name(filter, _("All JPEG files"));
+    filter.add_mime_type(filter, "image/jpeg");
+    dialog.add_filter(filter);
+
+    filter = new Gtk.FileFilter();
+    filter.set_name(filter, _("All files"));
+    filter.add_pattern(filter, "*");
+    dialog.add_filter(filter);
+}
+}
diff --git a/pgp/gpgme-revoke.ui b/pgp/gpgme-revoke.ui
new file mode 100644
index 0000000..e2db53d
--- /dev/null
+++ b/pgp/gpgme-revoke.ui
@@ -0,0 +1,189 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="revoke">
+    <property name="visible">True</property>
+    <property name="border_width">5</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="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="border_width">5</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="GtkComboBox" id="reason">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEventBox" id="eventbox1">
+                <property name="visible">True</property>
+                <property name="tooltip_text" translatable="yes">Reason for revoking the key</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">_Reason:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">reason</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Description:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">description</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="description">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="tooltip_text" translatable="yes">Optional description of 
revocation</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="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="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="cancelbutton1">
+                <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>
+                <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>
+            <child>
+              <object class="GtkButton" id="okbutton1">
+                <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="tooltip_text" translatable="yes">Revoke key</property>
+                <signal name="clicked" handler="on_gpgme_revoke_ok_clicked"/>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="xscale">0</property>
+                    <property name="yscale">0</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <object class="GtkImage" id="image1">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-ok</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label3">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Re_voke</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>
+                    </child>
+                  </object>
+                </child>
+              </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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">okbutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-revoke.vala b/pgp/gpgme-revoke.vala
new file mode 100644
index 0000000..92788ba
--- /dev/null
+++ b/pgp/gpgme-revoke.vala
@@ -0,0 +1,130 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ *
+ * 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 {
+namespace GpgME {
+
+public class Revoke : Gtk.Dialog {
+
+    private SubKey subkey;
+
+    private Gtk.Combobox reason_combo;
+    private Gtk.Entry description_entry;
+
+    public enum RevokeReason {
+        // No revocation reason
+        NO_REASON = 0,
+        // Key compromised
+        COMPROMISED = 1,
+        // Key replaced
+        SUPERSEDED = 2,
+        // Key no longer used
+        NOT_USED = 3
+    }
+
+    private enum Column {
+      TEXT,
+      TOOLTIP,
+      INT,
+      N_COLUMNS
+    }
+
+    public void seahorse_gpgme_add_revoker_new (Key pkey, Gtk.Window parent) {
+        g_return_if_fail (pkey != null);
+
+        Key revoker = SEAHORSE_GPGME_KEY (seahorse_signer_get (parent));
+        if (revoker == null)
+            return;
+
+        Gtk.Dialog dialog = new Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL,
+                                                  Gtk.MessageType.WARNING, GTK_BUTTONS_YES_NO,
+                                                  _("You are about to add %s as a revoker for %s. This 
operation cannot be undone! Are you sure you want to continue?"),
+                                                  revoker.label, pkey.label);
+
+        int response = dialog.run();
+        dialog.destroy();
+
+        if (response != Gtk.ResponseType.YES)
+            return;
+
+        GPG.Error err = KeyOperation.add_revoker(pkey, revoker);
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t add revoker"), err.strerror());
+    }
+
+    public void seahorse_gpgme_revoke_new (GpgME.SubKey subkey, Gtk.Window parent) {
+        Seahorse.Widget swidget = seahorse_widget_new ("revoke", parent);
+        g_return_if_fail (swidget != null);
+
+        this.title = _("Revoke: %s").printf(subkey.description);
+
+        this.subkey = subkey;
+
+        // Initialize List Store for the Combo Box
+        Gtk.ListStore store = new Gtk.ListStore(Column.N_COLUMNS, typeof(string), typeof(string), 
typeof(int));
+
+        Gtk.TreeIter iter;
+        store.append(ref iter);
+        store.set(iter, Column.TEXT, _("No reason"),
+                        Column.TOOLTIP, _("No reason for revoking key"),
+                        Column.INT, RevokeReason.NO_REASON, -1);
+
+        store.append(ref iter);
+        store.set(iter, Column.TEXT, _("Compromised"),
+                        Column.TOOLTIP, _("Key has been compromised"),
+                        Column.INT, RevokeReason.COMPROMISED, -1);
+
+        store.append(ref iter);
+        store.set(iter, Column.TEXT, _("Superseded"),
+                        Column.TOOLTIP, _("Key has been superseded"),
+                        Column.INT, RevokeReason.SUPERSEDED, -1);
+
+        store.append(ref iter);
+        store.set(iter, Column.TEXT, _("Not Used"),
+                        Column.TOOLTIP, _("Key is no longer used"),
+                        Column.INT, RevokeReason.NOT_USED, -1);
+
+        // Finish Setting Up Combo Box
+        this.reason_combo = builder.get_object("reason");
+        this.reason_combo.model = store;
+        this.reason_combo.active = 0;
+
+        Gtk.CellRenderer renderer = new Gtk.CellRendererText();
+        this.reason_combo.pack_start(renderer, true);
+        this.reason_combo.set_attributes(renderer, "text", Column.TEXT, null);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_gpgme_revoke_ok_clicked(Gtk.Button button) {
+        Gtk.TreeIter iter;
+        this.reason_combo.get_active_iter(out iter);
+
+        Value val;
+        this.reason_combo.model.get_value(iter, Column.INT, out val);
+        RevokeReason reason = val.get_int();
+
+        GPG.ErrorCode err = KeyOperation.revoke_subkey(this.subkey, reason, this.description_entry.text);
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t revoke subkey"), err.strerror());
+        swidget.destroy();
+    }
+}
+
+}
+}
diff --git a/pgp/gpgme-secret-deleter.vala b/pgp/gpgme-secret-deleter.vala
new file mode 100644
index 0000000..fccae3e
--- /dev/null
+++ b/pgp/gpgme-secret-deleter.vala
@@ -0,0 +1,69 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.GpgME.SecretDeleter : Seahorse.Deleter {
+
+    private Key key;
+    private List<Key> keys;
+
+    public SecretDeleter(Key key) {
+        this.keys = new List<Key>();
+        if (!add_object(key))
+            assert_not_reached();
+    }
+
+    public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+        string prompt = _("Are you sure you want to permanently delete %s?")
+                        .printf(this.key.label);
+
+        Seahorse.DeleteDialog dialog = new Seahorse.DeleteDialog(parent, "%s", prompt);
+        dialog.set_check_label(_("I understand that this secret key will be permanently deleted."));
+        dialog.set_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 (key == null || key.usage == Seahorse.Usage.PRIVATE_KEY || this.key != null)
+            return false;
+
+        this.key = key;
+        this.keys.append(key);
+        return true;
+    }
+
+    public override async bool delete(GLib.Cancellable? cancellable) throws GLib.Error {
+        GPG.Error error = KeyOperation.delete_pair(key);
+        if (seahorse_gpgme_propagate_error (gerr, &error)) {
+            g_simple_async_result_take_error (res, error);
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/pgp/gpgme-sign.ui b/pgp/gpgme-sign.ui
new file mode 100644
index 0000000..fe50a3a
--- /dev/null
+++ b/pgp/gpgme-sign.ui
@@ -0,0 +1,533 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="sign">
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Sign Key</property>
+    <property name="resizable">False</property>
+    <property name="window_position">center</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <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="orientation">vertical</property>
+            <property name="border_width">5</property>
+            <property name="spacing">18</property>
+            <child>
+              <object class="GtkHBox" id="hbox3">
+                <property name="visible">True</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkImage" id="sign-image">
+                    <property name="visible">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="vbox11">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label13">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">By signing you indicate your trust that 
this key belongs to:</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="sign-uid-text">
+                        <property name="visible">True</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">Key Name</property>
+                        <property name="use_markup">True</property>
+                        <property name="justify">center</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="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox6">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkLabel" id="label8">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <property name="label" translatable="yes">How carefully have you checked this 
key?</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="alignment5">
+                    <property name="visible">True</property>
+                    <property name="xalign">1</property>
+                    <property name="yalign">1</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkBox" id="vbox2">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkBox" id="vbox3">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkHBox" id="hbox4">
+                                <property name="visible">True</property>
+                                <property name="spacing">12</property>
+                                <property name="homogeneous">True</property>
+                                <child>
+                                  <object class="GtkRadioButton" id="sign-choice-not">
+                                    <property name="label" translatable="yes">_Not at all</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_gpgme_sign_choice_toggled"/>
+                                  </object>
+                                  <packing>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkRadioButton" id="sign-choice-casual">
+                                    <property name="label" translatable="yes">_Casually</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>
+                                    <property name="group">sign-choice-not</property>
+                                    <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
+                                  </object>
+                                  <packing>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkRadioButton" id="sign-choice-careful">
+                                    <property name="label" translatable="yes">_Very Carefully</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>
+                                    <property name="group">sign-choice-not</property>
+                                    <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
+                                  </object>
+                                  <packing>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkAlignment" id="alignment2">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="yalign">1</property>
+                                <property name="left_padding">18</property>
+                                <child>
+                                  <object class="GtkBox" id="vbox12">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">vertical</property>
+                                    <child>
+                                      <object class="GtkLabel" id="sign-display-not">
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" translatable="yes">&lt;i&gt;Not at 
all:&lt;/i&gt; means you believe the key is owned by the person who claims to own it, but you could not or 
did not verify this to be a fact.</property>
+                                        <property name="use_markup">True</property>
+                                        <property name="justify">fill</property>
+                                        <property name="wrap">True</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="sign-display-casual">
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" 
translatable="yes">&lt;i&gt;Casually:&lt;/i&gt; means you have done a casual verification that the key is 
owned by the person who claims to own it. For example, you could read the key fingerprint to the owner over 
the phone.</property>
+                                        <property name="use_markup">True</property>
+                                        <property name="justify">fill</property>
+                                        <property name="wrap">True</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkBox" id="sign-display-careful">
+                                        <property name="spacing">6</property>
+                                        <property name="orientation">vertical</property>
+                                        <child>
+                                          <object class="GtkEventBox" id="eventbox1">
+                                            <property name="visible">True</property>
+                                            <child>
+                                              <object class="GtkLabel" id="label7">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">True</property>
+                                                <property name="xalign">0</property>
+                                                <property name="yalign">0</property>
+                                                <property name="label" translatable="yes">&lt;i&gt;Very 
Carefully:&lt;/i&gt; Select this only if you are absolutely sure that this key is genuine.</property>
+                                                <property name="use_markup">True</property>
+                                                <property name="justify">fill</property>
+                                                <property name="wrap">True</property>
+                                                <property name="selectable">True</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label12">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="xalign">0</property>
+                                            <property name="yalign">0</property>
+                                            <property name="label" translatable="yes">You could use a hard 
to forge photo identification (such as a passport) to personally check that the name on the key is correct. 
You should have also used email to check that the email address belongs to the owner.</property>
+                                            <property name="use_markup">True</property>
+                                            <property name="justify">fill</property>
+                                            <property name="wrap">True</property>
+                                            <property name="selectable">True</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="position">1</property>
+                                          </packing>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="position">2</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox8">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkLabel" id="label9">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">How others will see this signature:</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="alignment6">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkBox" id="vbox7">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkCheckButton" id="sign-option-local">
+                            <property name="label" translatable="yes">_Others may not see this 
signature</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="tooltip_text">If signature is local to the key ring and won't be 
exported with the key</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkCheckButton" id="sign-option-revocable">
+                            <property name="label" translatable="yes">I can _revoke this signature at a 
later date.</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="tooltip_text">If signature can be revoked</property>
+                            <property name="use_underline">True</property>
+                            <property name="active">True</property>
+                            <property name="draw_indicator">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="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="signer-frame">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkLabel" id="label10">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Sign key as:</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="alignment7">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox2">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label11">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">_Signer:</property>
+                            <property name="use_underline">True</property>
+                            <property name="mnemonic_widget">signer-select</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBox" id="signer-select">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</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="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="cancelbutton1">
+                <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">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="okbutton1">
+                <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>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="xscale">0</property>
+                    <property name="yscale">0</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">2</property>
+                        <child>
+                          <object class="GtkImage" id="image1">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-ok</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label4">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">_Sign</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>
+                    </child>
+                  </object>
+                </child>
+              </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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-6">cancelbutton1</action-widget>
+      <action-widget response="-5">okbutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/gpgme-sign.vala b/pgp/gpgme-sign.vala
new file mode 100644
index 0000000..9f9be2a
--- /dev/null
+++ b/pgp/gpgme-sign.vala
@@ -0,0 +1,173 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2017 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.GpgME.Sign : Gtk.Dialog {
+
+    private Seahorse.Object to_sign;
+
+    private Gtk.RadioButton sign_choice_not;
+    private Gtk.RadioButton sign_choice_casual;
+    private Gtk.RadioButton sign_choice_careful;
+    private Gtk.Label sign_display_not;
+    private Gtk.Label sign_display_casual;
+    private Gtk.Label sign_display_careful;
+    private Gtk.CheckButton sign_option_local;
+    private Gtk.CheckButton sign_option_revocable;
+    private Gtk.ComboBox signer_select;
+    private Gtk.Box signer_frame;
+    private Gtk.Image sign_image;
+    private Gtk.Label sign_uid_text;
+
+    static bool sign_ok_clicked(GtkWindow *parent) {
+        SeahorseSignOptions options = 0;
+
+        // Figure out choice
+        SignCheck check = SIGN_CHECK_NO_ANSWER;
+        if (this.sign_choice_not.active) {
+            check = SIGN_CHECK_NONE;
+        } else {
+            if (this.sign_choice_casual.active) {
+                check = SIGN_CHECK_CASUAL;
+            } else {
+                if (this.sign_choice_careful.active)
+                    check = SIGN_CHECK_CAREFUL;
+            }
+        }
+
+        // Local signature
+        if (this.sign_option_local.active)
+            options |= SIGN_LOCAL;
+
+        // Revocable signature
+        if (!this.sign_option_revocable.active)
+            options |= SIGN_NO_REVOKE;
+
+        // Signer
+        Key? signer = ComboKeys.get_active (this.signer_select);
+        assert (signer == null || (SEAHORSE_IS_GPGME_KEY (signer) && signer.usage == 
SEAHORSE_USAGE_PRIVATE_KEY));
+
+        Object to_sign = g_object_get_data (G_OBJECT (swidget), "to-sign");
+        GPG.Error err;
+        if (to_sign is Uid)
+            err = KeyOperation.sign_uid((Uid) to_sign, SEAHORSE_GPGME_KEY (signer), check, options);
+        else if (to_sign is Key)
+            err = KeyOperation.sign ((Key) to_sign, SEAHORSE_GPGME_KEY (signer), check, options);
+        else
+            assert_not_reached();
+
+        if (!err.is_success()) {
+            if (err.error_code() == GPGError.ErrorCode.EALREADY) {
+                Gtk.Dialog dlg = new Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL,
+                                                       Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE,
+                                                       _("This key was already signed by\n“%s”"),
+                                                       signer.label);
+                dlg.run();
+                dlg.destroy();
+            } else {
+                Util.show_error(null, _("Couldn’t sign key"), err.strerror());
+            }
+        }
+
+        return true;
+    }
+
+    private void on_collection_changed(Gcr.Collection collection, GLib.Object object) {
+        GtkWidget *widget = GTK_WIDGET (user_data);
+        widget.visible = (collection.get_length() > 1);
+    }
+
+    // G_MODULE_EXPORT XXX
+    private void on_gpgme_sign_choice_toggled(Gtk.ToggleButton toggle) {
+        // Figure out choice
+        this.sign_display_not.visible = this.sign_choice_not.active;
+        this.sign_display_casual.visible = this.sign_choice_casual.active;
+        this.sign_display_careful.visible = this.sign_choice_careful.active;
+    }
+
+    private static void sign_internal(Object to_sign, Gtk.Window parent) {
+        this.to_sign = to_sign;
+
+        // Some initial checks
+        Gcr.Collection collection = seahorse_keyset_pgp_signers_new ();
+
+        // If no signing keys then we can't sign
+        if (collection.get_length() == 0) {
+            // TODO: We should be giving an error message that allows them to generate or import a key
+            Seahorse.Util.show_error(null, _("No keys usable for signing"),
+                    _("You have no personal PGP keys that can be used to indicate your trust of this key."));
+            return;
+        }
+
+        Seahorse.Widget swidget = new Seahorse.Widget("sign", parent);
+
+        // ... Except for when calling this, which is messed up
+        this.sign_uid_text.set_markup(Markup.printf_escaped("<i>%s</i>", to_sign.label));
+
+        // Uncheck all selections
+        this.sign_choice_not.active = false;
+        this.sign_choice_casual.active = false;
+        this.sign_choice_careful.active = false;
+
+        // Initial choice
+        on_gpgme_sign_choice_toggled(null, swidget);
+
+        // Other question's default state
+        this.sign_option_local.active = false;
+        this.sign_option_revocable.active = true;
+
+        // Signature area
+        collection.added.connect(on_collection_changed);
+        collection.removed.connect(on_collection_changed);
+        on_collection_changed(collection, null, this.signer_frame);
+
+        // Signer box
+        ComboKeys.attach(this.signer_select, collection, null);
+
+        // Image
+        this.sign_image.set_from_icon_name(SEAHORSE_ICON_SIGN, Gtk.IconSize.DIALOG);
+
+        show();
+
+        bool do_sign = true;
+        while (do_sign) {
+            switch (run()) {
+                case Gtk.ResponseType.HELP:
+                    break;
+                case Gtk.ResponseType.OK:
+                    do_sign = !sign_ok_clicked(parent);
+                    break;
+                default:
+                    do_sign = false;
+                    break;
+            }
+        }
+
+        destroy();
+    }
+
+    public static void seahorse_gpgme_sign_prompt (Key to_sign, GtkWindow *parent) {
+        sign_internal (to_sign, parent);
+    }
+
+    public static void seahorse_gpgme_sign_prompt_uid (Uid to_sign, GtkWindow *parent) {
+        sign_internal (to_sign, parent);
+    }
+}
diff --git a/pgp/gpgme-source.vala b/pgp/gpgme-source.vala
new file mode 100644
index 0000000..c82ee55
--- /dev/null
+++ b/pgp/gpgme-source.vala
@@ -0,0 +1,200 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2017 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.GpgME.Source : GLib.Source {
+    private GPG.Context ctx;
+    private struct gpgme_io_cbs io_cbs;
+    private bool busy;
+    private List<WatchData> watches;
+    private Cancellable cancellable;
+    private int cancelled_sig;
+    private bool finished;
+    private GPG.ErrorCode status;
+
+    struct WatchData {
+        GSource *gsource;
+        bool registered;
+        GPollFD poll_fd;
+
+        // GPGME watch info
+        gpgme_io_cb_t fnc;
+        void *fnc_data;
+    }
+
+    delegate bool SeahorseGpgmeCallback(GPG.ErrorCode status);
+
+    public Source(GPG.Context ctx, GCancellable *cancellable) {
+        base();
+        this.set_funcs(&seahorse_gpgme_gsource_funcs);
+
+        this.ctx = ctx;
+        this.io_cbs.add = on_gpgme_add_watch;
+        this.io_cbs.add_priv = gsource;
+        this.io_cbs.remove = on_gpgme_remove_watch;
+        this.io_cbs.event = on_gpgme_event;
+        this.io_cbs.event_priv = gsource;
+        this.ctx.set_io_cbs(&gpgme_gsource.io_cbs);
+
+        this.cancellable = cancellable;
+        if (this.cancellable != null)
+            this.cancelled_sig = this.cancellable.connect(on_gpgme_gsource_cancelled);
+    }
+
+    private bool prepare (int *timeout) {
+        if (this.finished)
+            return true;
+
+        // No other way, but to poll
+        *timeout = -1;
+        return false;
+    }
+
+    private bool check() {
+        foreach (WatchData watch in this.watches) {
+            if (watch.registered && watch.poll_fd.revents)
+                return true;
+        }
+
+        return this.finished;
+    }
+
+    private bool dispatch(SourceFunc callback, gpointer user_data) {
+        foreach (WatchData watch in this.watches.copy()) {
+            if (watch.registered && watch.poll_fd.revents) {
+                debug("GPGME OP: io for GPGME on %d", watch.poll_fd.fd);
+                assert(watch.fnc);
+                watch.poll_fd.revents = 0;
+                (watch.fnc) (watch.fnc_data, watch.poll_fd.fd);
+            }
+        }
+
+        if (this.finished)
+            return ((SeahorseGpgmeCallback)callback) (gpgme_gsource.status, user_data);
+
+        return true;
+    }
+
+    private void finalize() {
+        g_cancellable_disconnect(this.cancellable, this.cancelled_sig);
+        g_clear_object (&this.cancellable);
+    }
+
+    static GSourceFuncs seahorse_gpgme_gsource_funcs = {
+        prepare,
+        check,
+        dispatch,
+        finalize,
+    };
+
+    private void register_watch (WatchData *watch) {
+        if (watch.registered)
+            return;
+
+        debug("GPGME OP: registering watch %d", watch.poll_fd.fd);
+
+        watch.registered = true;
+        g_source_add_poll (watch.gsource, &watch.poll_fd);
+    }
+
+    private void unregister_watch (WatchData *watch) {
+        if (!watch.registered)
+            return;
+
+        debug("GPGME OP: unregistering watch %d", watch.poll_fd.fd);
+
+        watch.registered = false;
+        g_source_remove_poll (watch.gsource, &watch.poll_fd);
+    }
+
+    // Register a callback.
+    private gpg_error_t on_gpgme_add_watch(int fd, int dir, gpgme_io_cb_t fnc, void *fnc_data, void **tag) {
+        debug ("PGPOP: request to register watch %d", fd);
+
+        WatchData watch = g_new0 (WatchData, 1);
+        watch.registered = false;
+        watch.poll_fd.fd = fd;
+        if (dir)
+            watch.poll_fd.events = (G_IO_IN | G_IO_HUP | G_IO_ERR);
+        else
+            watch.poll_fd.events = (G_IO_OUT | G_IO_ERR);
+        watch.fnc = fnc;
+        watch.fnc_data = fnc_data;
+        watch.gsource = (GSource*)gpgme_gsource;
+
+        // If the context is busy, we already have a START event, and can
+        // register watches freely.
+        if (this.busy)
+            register_watch(watch);
+
+        this.watches.append(watch);
+        *tag = watch;
+
+        return GPG_OK;
+    }
+
+    private void on_gpgme_remove_watch (void *tag) {
+        WatchData *watch = (WatchData*)tag;
+        SeahorseGpgmeGSource *gpgme_gsource = (SeahorseGpgmeGSource*)watch.gsource;
+
+        this.watches.remove(watch);
+        unregister_watch (watch);
+    }
+
+    private void on_gpgme_event(gpgme_event_io_t type, void *type_data) {
+        switch (type) {
+
+        // Called when the GPGME context starts an operation
+        case GPGME_EVENT_START:
+            this.busy = true;
+            this.finished = false;
+            debug("PGPOP: start event");
+
+            // Since we weren't supposed to register these before, do it now
+            foreach (WatchData watch in this.watches)
+                register_watch(watch);
+            break;
+
+        // Called when the GPGME context is finished with an op
+        case GPGME_EVENT_DONE:
+            this.busy = false;
+            gpg_error_t gerr = (GPG.ErrorCode *)type_data;
+            debug ("PGPOP: done event (err: %d)", *gerr);
+
+            // Make sure we have no extra watches left over
+            foreach (WatchData watch in this.watches)
+                unregister_watch(watch);
+
+            // And try to figure out a good response
+            this.finished = true;
+            this.status = *gerr;
+            break;
+
+        case GPGME_EVENT_NEXT_KEY:
+        case GPGME_EVENT_NEXT_TRUSTITEM:
+        default:
+            // Ignore unsupported event types
+            break;
+        }
+    }
+
+    private void on_gpgme_gsource_cancelled (GCancellable *cancellable) {
+        if (this.busy)
+            gpgme_cancel (gpgme_gsource.gctx);
+    }
+}
diff --git a/pgp/gpgme-subkey.vala b/pgp/gpgme-subkey.vala
new file mode 100644
index 0000000..8ed6dd9
--- /dev/null
+++ b/pgp/gpgme-subkey.vala
@@ -0,0 +1,98 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.GpgME.SubKey : Pgp.SubKey {
+
+    /**
+     * GPGME Public Key that this subkey is on
+     */
+    public GPG.Key pubkey { get; set; }
+
+    /**
+     * GPGME Subkey
+     */
+    public GPG.SubKey subkey {
+        get { return this._subkey; }
+        set { set_subkey(value); }
+    }
+    private GPG.SubKey _subkey;
+
+    public SubKey(GPG.Key pubkey, GPG.SubKey subkey) {
+        GLib.Object(pubkey: pubkey,
+                    subkey: subkey);
+    }
+
+    public void set_subkey(GPG.SubKey subkey) {
+        /* Make sure that this subkey is in the pubkey */
+        int index = -1, i = 0;
+        for (GPG.SubKey sub = this.pubkey.subkeys; sub != null; sub = sub.next) {
+            if (sub == subkey) {
+                index = i;
+                break;
+            }
+            i++;
+        }
+
+        if (index < 0)
+            return;
+
+        /* Calculate the algorithm */
+        string? algo_type = subkey.pubkey_algo.get_name();
+        if (algo_type == null)
+            algo_type = C_("Algorithm", "Unknown");
+        else if ("Elg" == algo_type || "ELG-E" == algo_type)
+            algo_type = _("ElGamal");
+
+        /* Additional properties */
+        this._subkey = subkey;
+
+        this.index = index;
+        this.keyid = subkey.keyid;
+        this.algorithm = algo_type;
+        this.length = subkey.length;
+        this.description = calc_description(GpgME.Uid.calc_name(self.pubkey.uids), index);
+        this.fingerprint = calc_fingerprint(subkey.fpr);
+        this.created = subkey.timestamp;
+        this.expires = subkey.expires;
+
+        /* The order below is significant */
+        Seahorse.Flags flags = 0;
+        if (subkey.revoked)
+            flags |= Seahorse.Flags.REVOKED;
+        if (subkey.expired)
+            flags |= Seahorse.Flags.EXPIRED;
+        if (subkey.disabled)
+            flags |= Seahorse.Flags.DISABLED;
+        if (flags == 0 && !subkey.invalid)
+            flags |= Seahorse.Flags.IS_VALID;
+        if (subkey.can_encrypt)
+            flags |= Seahorse.Flags.CAN_ENCRYPT;
+        if (subkey.can_sign)
+            flags |= Seahorse.Flags.CAN_SIGN;
+        if (subkey.can_certify)
+            flags |= Seahorse.Flags.CAN_CERTIFY;
+        if (subkey.can_authenticate)
+            flags |= Seahorse.Flags.CAN_AUTHENTICATE;
+
+        this.flags = flags;
+
+        notify_property("subkey");
+    }
+}
diff --git a/pgp/gpgme-uid.vala b/pgp/gpgme-uid.vala
new file mode 100644
index 0000000..b65201c
--- /dev/null
+++ b/pgp/gpgme-uid.vala
@@ -0,0 +1,176 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.GpgME.Uid : Pgp.Uid {
+
+    /**
+     * GPGME Public Key that this uid is on
+     */
+    public GPG.Key? pubkey {
+        get { return this._pubkey; }
+        set { set_pubkey(value); }
+    }
+    private GPG.Key? _pubkey = null;
+
+    /**
+     * GPGME User ID
+     */
+    public GPG.UserID? userid {
+        get { return this._userid; }
+        set { set_userid(value); }
+    }
+    private GPG.UserID? _userid = null;
+
+    /**
+     * GPGME User ID Index
+     */
+    public uint gpgme_index { get; private set; default = 0; }
+
+    /**
+     * Actual GPG Index
+     */
+    public int actual_index {
+        get { return (this._actual_index > 0)? this._actual_index : this.gpgme_index + 1; }
+        set { this._actual_index = value; }
+    }
+    private int _actual_index = -1;
+
+
+    public Uid(Key parent, GPG.UserID userid) {
+        GLib.Object(parent: parent,
+                    pubkey: parent.pubkey,
+                    userid: userid);
+    }
+
+    public void set_pubkey(GPG.Key? pubkey) {
+        if (this._pubkey == pubkey || compare_pubkeys(this._pubkey, pubkey))
+            return;
+
+        this.pubkey = pubkey;
+
+        // This is expected to be set shortly along with pubkey
+        this.userid = null;
+    }
+
+    public void set_userid(GPG.UserID userid) {
+        if (userid == null || (this._userid != null && is_same(userid)))
+            return;
+
+        // Make sure that this userid is in the pubkey and get the index
+        int index = -1, i = 0;
+        for (GPG.UserID? uid = this._pubkey.uids; uid != null; ++i, uid = uid.next) {
+            if (userid == uid) {
+                index = i;
+                break;
+            }
+        }
+
+        if (index < 0)
+            return;
+
+        this._userid = userid;
+        this.gpgme_index = index;
+
+        notify_property("userid");
+        notify_property("gpgme_index");
+
+        this.comment = convert_string(userid.comment);
+        this.email = convert_string(userid.email);
+        this.name = convert_string(userid.name);
+
+        realize_signatures();
+
+        this.validity = userid.validity.to_seahorse_validity();
+    }
+
+    public string? calc_label(GPG.UserID? userid) {
+        if (userid == null)
+            return null;
+
+        return convert_string(userid.uid);
+    }
+
+    public string? calc_name(GPG.UserID? userid) {
+        if (userid == null)
+            return null;
+
+        return convert_string(userid.name);
+    }
+
+    public string? calc_markup(GPG.UserID? userid, Seahorse.Flags flags) {
+        if (userid == null)
+            return null;
+
+        ret = base.calc_markup(convert_string(userid.name),
+                               convert_string(userid.email),
+                               convert_string(userid.comment),
+                               flags);
+
+        return ret;
+    }
+
+    public bool is_same(GPG.UserID? userid) {
+        if (userid == null)
+            return false;
+
+        return compare_strings(this.userid.uid, userid.uid);
+    }
+
+    protected string convert_string(string? str) {
+        if (str == null)
+            return null;
+
+        // If not utf8 valid, assume latin 1
+        if (!str.validate())
+            return g_convert (str, -1, "UTF-8", "ISO-8859-1", null, null, null);
+
+        return str;
+    }
+
+    private void realize_signatures() {
+        if (this.pubkey == null || this.userid == null)
+            return;
+
+        // If this key was loaded without signatures, then leave them as is
+        if ((this.pubkey.keylist_mode & GPG.KeyListMode.SIGS) == 0)
+            return;
+
+        List<Pgp.Signature> sigs = new List<Pgp.Signature>();
+        for (GPG.KeySig gsig = this.userid.signatures; gsig != null; gsig = gsig.next) {
+            Pgp.Signature sig = new Pgp.Signature(gsig.keyid);
+
+            // Order of parsing these flags is important
+            Seahorse.Flags flags = 0;
+            if (gsig.revoked)
+                flags |= Seahorse.Flags.REVOKED;
+            if (gsig.expired)
+                flags |= Seahorse.Flags.EXPIRED;
+            if (flags == 0 && !gsig.invalid)
+                flags = Seahorse.Flags.IS_VALID;
+            if (gsig.exportable)
+                flags |= Seahorse.Flags.EXPORTABLE;
+
+            sig.set_flags(flags);
+            sigs.prepend(sig);
+        }
+
+        this.signatures = sigs;
+    }
+}
diff --git a/pgp/gpgme-validity.vala b/pgp/gpgme-validity.vala
new file mode 100644
index 0000000..d4dd41b
--- /dev/null
+++ b/pgp/gpgme-validity.vala
@@ -0,0 +1,79 @@
+/*
+ * Seahorse
+ *
+ * Copyright (c) 2017 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/>.
+ */
+
+/**
+ * Both Seahorse and GPGME define a Validity enum, which are similar, but not the same.
+ * This enum and its methods provide a bridge between the two.
+ * Since they map to the same values, you can use this enum and GPG.Validity interchangeably.
+ */
+public enum Seahorse.GpgME.Validity { // XXX GpgME or PGP?
+    // XXX Please let this be allowed, or we'll have to use magic numbers
+    UNKNOWN = GPG.Validity.UNKNOWN,
+    UNDEFINED = GPG.Validity.UNDEFINED,
+    NEVER = GPG.Validity.NEVER,
+    MARGINAL = GPG.Validity.MARGINAL,
+    FULL = GPG.Validity.FULL,
+    ULTIMATE = GPG.Validity.ULTIMATE;
+
+    /**
+     * Returns the Seahorse Validity that best corresponds to the given GPG Validity.
+     */
+    public Seahorse.Validity to_seahorse_validity() {
+        switch (this) {
+            case NEVER:
+                return Seahorse.Validity.NEVER;
+            case MARGINAL:
+                return Seahorse.Validity.MARGINAL;
+            case FULL:
+                return Seahorse.Validity.FULL;
+            case ULTIMATE:
+                return Seahorse.Validity.ULTIMATE;
+            case UNDEFINED:
+            case UNKNOWN:
+            default:
+                return Seahorse.Validity.UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns the GPG Validity that best corresponds to the given Seahorse Validity.
+     */
+    public static Validity from_seahorse_validity(Seahorse.Validity v) {
+        switch (v) {
+            case Seahorse.Validity.NEVER:
+                menu_choice = GPG.Validity.NEVER;
+                break;
+            case Seahorse.Validity.UNKNOWN:
+                menu_choice = GPG.Validity.UNKNOWN;
+                break;
+            case Seahorse.Validity.MARGINAL:
+                menu_choice = GPG.Validity.MARGINAL;
+                break;
+            case Seahorse.Validity.FULL:
+                menu_choice = GPG.Validity.FULL;
+                break;
+            case Seahorse.Validity.ULTIMATE:
+                menu_choice = GPG.Validity.ULTIMATE;
+                break;
+            default:
+                menu_choice = GPG.Validity.UNDEFINED;
+        }
+    }
+}
+
diff --git a/pgp/gpgme.vala b/pgp/gpgme.vala
new file mode 100644
index 0000000..4413873
--- /dev/null
+++ b/pgp/gpgme.vala
@@ -0,0 +1,182 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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/>.
+ */
+
+/* #undef G_LOG_DOMAIN */
+/* #define G_LOG_DOMAIN "operation" */
+
+/**
+ * SECTION:seahorse-gpgme
+ * @short_description: gpgme specific error and data conversion functions
+ * @include:seahorse-gpgme.h
+ *
+ **/
+
+/**
+ * seahorse_gpgme_error_domain:
+ *
+ *
+ * Returns: A Quark with the content "seahorse-gpgme-error"
+ */
+private GQuark q = 0;
+public Quark seahorse_gpgme_error_domain() {
+    if (q == 0)
+        q = g_quark_from_static_string ("seahorse-gpgme-error");
+    return q;
+}
+
+public bool seahorse_gpgme_propagate_error(GPGError.ErrorCode gerr, GError** error) {
+    gpgme_err_code_t code;
+
+    /* Make sure this is actually an error */
+    code = gpgme_err_code (gerr);
+    if (code == 0)
+        return FALSE;
+
+    /* Special case some error messages */
+    switch (code) {
+    case GPG_ERR_DECRYPT_FAILED:
+        g_set_error_literal (error, SEAHORSE_GPGME_ERROR, code,
+                     _("Decryption failed. You probably do not have the decryption key."));
+        break;
+    case GPG_ERR_CANCELED:
+        g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                             _("The operation was cancelled"));
+        break;
+    default:
+        g_set_error_literal (error, SEAHORSE_GPGME_ERROR, code,
+                             gpgme_strerror (gerr));
+        break;
+    }
+
+    return TRUE;
+}
+
+/**
+ * Acquires an additional gpgme reference for the key
+ *
+ * @key: the gpgme key
+ *
+ * Returns: the key
+ */
+private GPG.Key ref_return_key (GPG.Key key) {
+    gpgme_key_ref (key);
+    return key;
+}
+
+/**
+ * Creates a new boxed type "GPG.Key"
+ *
+ * Returns: the new boxed type
+ */
+private GType type = 0;
+public GType seahorse_gpgme_boxed_key_type() {
+    if (!type)
+        type = g_boxed_type_register_static ("GPG.Key", ref_return_key, gpgme_key_unref);
+    return type;
+}
+
+/**
+ * converts the gpgme validity to the seahorse validity
+ *
+ * @validity: the gpgme validity of a key
+ *
+ * Returns: The seahorse validity
+ */
+SeahorseValidity seahorse_gpgme_convert_validity (GPG.Validity validity) {
+    switch (validity) {
+        case GPG.Validity.NEVER:
+            return Seahorse.Validity.NEVER;
+        case GPG.Validity.MARGINAL:
+            return Seahorse.Validity.MARGINAL;
+        case GPG.Validity.FULL:
+            return Seahorse.Validity.FULL;
+        case GPG.Validity.ULTIMATE:
+            return Seahorse.Validity.ULTIMATE;
+        case GPG.Validity.UNDEFINED:
+        case GPG.Validity.UNKNOWN:
+        default:
+            return Seahorse.Validity.UNKNOWN;
+    }
+}
+
+struct SeahorseKeyTypeTable {
+       int rsa_sign;
+    int rsa_enc;
+    int dsa_sign;
+    int elgamal_enc;
+}
+
+/*
+ * Based on the values in ask_algo() in gnupg's g10/keygen.c
+ * http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/trunk/g10/keygen.c?rev=HEAD&root=GnuPG&view=log
+_ */
+const SeahorseKeyTypeTable KEYTYPES_2012 = SeahorseKeyTypeTable()
+    { rsa_sign=4, rsa_enc=6, dsa_sign=3, elgamal_enc=5 };
+const SeahorseKeyTypeTable KEYTYPES_140 = SeahorseKeyTypeTable()
+    { rsa_sign=5, rsa_enc=6, dsa_sign=2, elgamal_enc=4 };
+const SeahorseKeyTypeTable KEYTYPES_124 = SeahorseKeyTypeTable()
+    { rsa_sign=4, rsa_enc=5, dsa_sign=2, elgamal_enc=3 };
+const SeahorseKeyTypeTable KEYTYPES_120 = SeahorseKeyTypeTable()
+    { rsa_sign=5, rsa_enc=6, dsa_sign=2, elgamal_enc=3 };
+
+const Seahorse.Version VER_2012 = seahorse_util_version(2,0,12,0);
+const Seahorse.Version VER_190  = seahorse_util_version(1,9,0,0);
+const Seahorse.Version VER_1410 = seahorse_util_version(1,4,10,0);
+const Seahorse.Version VER_140  = seahorse_util_version(1,4,0,0);
+const Seahorse.Version VER_124  = seahorse_util_version(1,2,4,0);
+const Seahorse.Version VER_120  = seahorse_util_version(1,2,0,0);
+
+/**
+ * Based on the gpg version in use, sets @table
+ * to contain the numbers that gpg uses in its CLI
+ * for adding new subkeys. This tends to get broken
+ * at random by new versions of gpg, but there's no good
+ * API for this.
+ *
+ * @param table The requested keytype table
+ *
+ * @return GPG_ERR_USER_2 if gpg is too old.
+ **/
+public GPGError.ErrorCode seahorse_gpgme_get_keytype_table (out SeahorseKeyTypeTable table) {
+    GPG.EngineInfo engine;
+    GPGError.ErrorCode gerr = gpgme_get_engine_info (out engine);
+    g_return_val_if_fail (gerr.is_success(), gerr);
+
+    while (engine && engine->protocol != GPGME_PROTOCOL_OpenPGP)
+        engine = engine->next;
+    g_return_val_if_fail (engine != null, GPG_E (GPG_ERR_GENERAL));
+
+    Seahorse.Version ver = seahorse_util_parse_version (engine->version);
+
+    if (ver >= VER_2012 || (ver >= VER_1410 && ver < VER_190))
+        *table = (SeahorseKeyTypeTable)&KEYTYPES_2012;
+    else if (ver >= VER_140 || ver >= VER_190)
+        *table = (SeahorseKeyTypeTable)&KEYTYPES_140;
+    else if (ver >= VER_124)
+        *table = (SeahorseKeyTypeTable)&KEYTYPES_124;
+    else if (ver >= VER_120)
+        *table = (SeahorseKeyTypeTable)&KEYTYPES_120;
+    else    // older versions not supported
+        gerr = GPG_E (GPG_ERR_USER_2);
+
+    return gerr;
+}
diff --git a/pgp/hkp-source.vala b/pgp/hkp-source.vala
new file mode 100644
index 0000000..8af0ec6
--- /dev/null
+++ b/pgp/hkp-source.vala
@@ -0,0 +1,710 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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 {
+
+/**
+ * Implements the HKP (HTTP Keyserver protocol) Source object
+ *
+ * See: http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00
+ */
+public class HkpSource : ServerSource {
+
+    public const string PGP_KEY_BEGIN = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
+    public const string PGP_KEY_END = "-----END PGP PUBLIC KEY BLOCK-----";
+
+    public const int DEFAULT_HKP_PORT = 11371;
+
+#define SOUP_MESSAGE_IS_ERROR(msg) \
+        (SOUP_STATUS_IS_TRANSPORT_ERROR((msg).status_code) || \
+         SOUP_STATUS_IS_CLIENT_ERROR((msg).status_code) || \
+         SOUP_STATUS_IS_SERVER_ERROR((msg).status_code))
+
+#define HKP_ERROR_DOMAIN (get_hkp_error_domain())
+
+/**
+*
+* Returns The GQuark for the HKP error
+*
+**/
+static GQuark
+get_hkp_error_domain (void)
+{
+    static GQuark q = 0;
+    if(q == 0)
+        q = g_quark_from_static_string ("seahorse-hkp-error");
+    return q;
+}
+
+    static construct {
+        if (Config.WITH_HKP)
+            Seahorse.Server.register_type("hkp", _("HTTP Key Server"), is_valid_uri);
+    }
+
+    /**
+     * Creates a new key source for an HKP PGP server.
+     *
+     * @param uri The server to connect to
+     */
+    public HKPSource(string uri, string host) {
+        GLib.Object(key_server: host);
+    }
+
+    /**
+     * @param uri The uri to check
+     *
+     * @return Whether the passed uri is valid for an HKP key source
+     */
+    public private bool is_valid_uri(string? uri) {
+        if(uri == null && uri == "")
+            return false;
+
+        gboolean ret = false;
+        gchar *t;
+
+        Soup.URI? soup;
+        if (uri.has_prefix("hkp:")) {
+            // Replace 'hkp' with 'http' at the beginning of the URI
+            soup = "http:%s".printf(uri.substring(4));
+        } else {
+            soup = new Soup.URI(uri);
+        }
+
+
+        if (soup == null)
+            return fase;
+
+        /* Must be http or https, have a host. No querystring, user, path, passwd etc... */
+        return (soup.scheme == "http" || soup.scheme == "https")
+            && soup.host != null && soup.host != ""
+            && (soup.path != null ? soup.path : "/") == "/"
+            && soup.user == null && soup.password == null && soup.query == null && soup.fragment == null
+    }
+
+    /**
+     * Returns a Soup uri with server, port and paths
+     *
+     * @param src The SeahorseSource to use as server for the uri
+     * @param path The path to add to the SOUP uri
+     */
+    private Soup.URI? get_http_server_uri(string path) {
+        if (this.server == null)
+            return null;
+
+        Soup.URI uri = new Soup.URI(null);
+        uri.set_scheme("http");
+
+        // If it already has a port then use that
+        string server_port = this.server.split(":", 2);
+        if (server_port.length == 2) {
+            uri.set_host(server_port[0]);
+            uri.set_port(int.parse(server_port[1]));
+        } else {
+            uri.set_host(server_port[0]);
+            uri.set_port(DEFAULT_HKP_PORT);
+        }
+
+        uri.set_path(path);
+
+        return uri;
+    }
+
+    private Soup.Session create_hkp_soup_session () {
+        Soup.Session session = new Soup.SessionAsync.with_options(SOUP_SESSION_ADD_FEATURE_BY_TYPE,
+                                                                  SOUP_TYPE_PROXY_RESOLVER_DEFAULT);
+
+        if (Config.WITH_DEBUG) {
+            string? env = Environment.get_variable("G_MESSAGES_DEBUG");
+            if (env != null && ("seahorse" in env)) {
+                Soup.Logger logger = new Soup.Logger(SOUP_LOGGER_LOG_BODY, -1);
+                session.add_feature(logger);
+            }
+        }
+
+        return session;
+    }
+
+    /**
+     * Remove anything <between brackets> and de-urlencode in place.  Note
+     * that this requires all brackets to be closed on the same line.  It
+     * also means that the result is never larger than the input.
+     *
+     * @param line The line to remove the HTML from
+     */
+    private string? dehtmlize(string? line) {
+        if (line == null)
+            return null;
+
+        StringBuilder parsed = new StringBuilder.sized(line.length);
+        int i = 0;
+        while (i < line.length) {
+            if (line[i] == '<') {
+                while (line[i] != '>' && line[i] != '\0')
+                    i++;
+                if (i < line.length)
+                    i++;
+                continue;
+            }
+
+            if (line[i] == '&') {
+                if ((i + 4) <= line.length) {
+                    if (line[i:i+4].down() == "&lt;")
+                        parsed.append_c('<');
+                    else if (line[i:i+4].down() == "&gt;")
+                        parsed.append_c('>');
+
+                    i += 4;
+                    continue;
+                }
+
+                if ((i + 5) <= line.length && line[i:i+5].down() == "&amp;") {
+                    parsed.append_c('&');
+                    i += 5;
+                    continue;
+                }
+            }
+            // Fall through
+            parsed.append_c(line[i]);
+            i++;
+        }
+
+        // Chop off any trailing whitespace.  Note that the HKP servers have
+        // \r\n as line endings, and the NAI HKP servers have just \n.
+        if(parsedindex > 0) {
+            parsedindex--;
+            while (g_ascii_isspace(((unsigned char*)parsed)[parsedindex])) {
+                parsed[parsedindex] = '\0';
+                if(parsedindex == 0)
+                    break;
+                parsedindex--;
+            }
+        }
+
+        return parsed.str;
+    }
+
+    /**
+     * @text: The date string to parse, YYYY-MM-DD
+     *
+     * Returns: 0 on error or the timestamp
+     */
+    static uint parse_hkp_date(string text) {
+        if (text.length != 10 || text[4] != '-' || text[7] != '-')
+            return 0;
+
+        // YYYY-MM-DD
+        int year, month, day;
+        text.scanf("%4d-%2d-%2d", out year, month, day);
+
+        // some basic checks
+        if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31)
+            return 0;
+
+        struct tm tmbuf;
+        time_t stamp;
+
+        memset (&tmbuf, 0, sizeof tmbuf);
+        tmbuf.tm_mday = day;
+        tmbuf.tm_mon = month - 1;
+        tmbuf.tm_year = year - 1900;
+        tmbuf.tm_isdst = -1;
+
+        stamp = mktime (&tmbuf);
+        return stamp == (time_t)-1 ? 0 : stamp;
+    }
+
+    static string get_fingerprint_string(string line) {
+        if (line == null)
+            return null;
+
+        string chugged = line.chug();
+        string FINGERPRINT_KEY = "fingerprint=";
+        if (chugged.down().has_prefix(FINGERPRINT_KEY))
+            return chugged.substring(FINGERPRINT_KEY.length);
+
+        return null;
+    }
+
+    /**
+     * Extracts the key data from the HKP server response
+     *
+     * @param response The HKP server response to parse
+     *
+     * @return A List of keys
+     */
+    private List<Pgp.Key> parse_hkp_index (string response) {
+        // Luckily enough, both the HKP server and NAI HKP interface to their
+        // LDAP server are close enough in output so the same function can
+        // parse them both.
+
+        /* pub  2048/<a href="/pks/lookup?op=get&search=0x3CB3B415">3CB3B415</a> 1998/04/03 David M. Shaw 
&lt;<a href="/pks/lookup?op=get&search=0x3CB3B415">dshaw jabberwocky com</a>&gt; */
+
+        SeahorsePgpKey *key = null;
+        SeahorsePgpSubkey *subkey_with_id = null;
+        GList *keys = null;
+        GList *subkeys = null;
+        GList *uids = null;
+
+        string[] lines = response.split("\n");
+        foreach (string line in lines) {
+            line = dehtmlize(line);
+
+            debug("%s", line);
+
+            // Start a new key
+            if (line.ascii_ncasecmp("pub ", 4) == 0) {
+                string t = line.substring(4).chug();
+
+                string[] v = t.split_set(" ", 3);
+                if (v.length != 3) {
+                    message("Invalid key line from server: %s", line);
+                    return null;
+                }
+
+                // Cut the length and fingerprint
+                string fpr = strchr (v[0], '/');
+                if (fpr == null) {
+                    message("couldn't find key fingerprint in line from server: %s", line);
+                    fpr = "";
+                } else {
+                    *(fpr++) = 0;
+                }
+
+                // Check out the key type
+                string algo;
+                switch ((v[0][strlen(v[0]) - 1]).toupper()) {
+                case 'D':
+                    algo = "DSA";
+                    break;
+                case 'R':
+                    algo = "RSA";
+                    break;
+                default:
+                    algo = "";
+                    break;
+                };
+
+                // Format the date for our parse function
+                g_strdelimit (v[1], "/", '-');
+
+                // Cleanup the UID
+                g_strstrip (v[2]);
+
+                bool has_uid = true;
+                Seahorse.Flags flags = Seahorse.Flags.EXPORTABLE;
+
+                if (v[2].ascii_strcasecmp("*** KEY REVOKED ***") == 0) {
+                    flags |= Seahorse.Flags.REVOKED;
+                    has_uid = false;
+                }
+
+                if (key) {
+                    key.uids = uids;
+                    key.subkeys = subkeys;
+                    key.realize();
+                    uids = subkeys = null;
+                    subkey_with_id = null;
+                    key = null;
+                }
+
+                key = new Pgp.Key();
+                keys.prepend(key);
+                key.flags = flags;
+
+                // Add all the info to the key
+                Pgp.Subkey subkey = new Pgp.SubKey();
+                subkey.keyid = fpr;
+                subkey_with_id = subkey;
+
+                subkey.fingerprint = seahorse_pgp_subkey_calc_fingerprint (fpr);
+                subkey.flags = flags;
+                subkey.length = long.parse(v[0]);
+                subkey.created = parse_hkp_date(v[1]);
+                subkey.algorithm = algo;
+                subkeys.prepend(subkey);
+
+                // And the UID if one was found
+                if (has_uid) {
+                    Pgp.Uid uid = new Pgp.Uid(key, v[2]);
+                    uids.prepend(uid);
+                }
+
+            // A UID for the key
+            } else if (key && g_ascii_strncasecmp (line, "    ", 4) == 0) {
+                Pgp.Uid uid = new Pgp.Uid(key, line.strip());
+                uids.prepend(uid);
+
+            // Signatures
+            } else if (key && g_ascii_strncasecmp (line, "sig ", 4) == 0) {
+                // TODO: Implement signatures
+
+            } else if (key && subkey_with_id) {
+                string fingerprint_str = get_fingerprint_string (line);
+
+                if (fingerprint_str != null) {
+                    string pretty_fingerprint = seahorse_pgp_subkey_calc_fingerprint (fingerprint_str);
+
+                    // FIXME: we don't check that the fingerprint actually matches the key's ID.
+                    // We also don't validate the fingerprint at all; the keyserver may have returned
+                    // some garbage and we don't notice.
+
+                    if (pretty_fingerprint != "")
+                        subkey_with_id.fingerprint = pretty_fingerprint;
+                }
+            }
+        }
+
+        if (key != null) {
+            key.uids = uids.reverse();
+            key.subkeys = subkeys.reverse();
+            key.realize();
+        }
+
+        return keys;
+    }
+
+    /**
+     * Parses the response and extracts an error message
+     *
+     * @param response The server response
+     *
+     * Returns null if there was no error. The error message else
+     **/
+    private string get_send_result (string response) {
+        if (response == null || response == "")
+            return "";
+
+        string[] lines = response.split("\n");
+        string? last = null;
+        foreach (string line in lines) {
+            string l = dehtmlize(line).strip();
+
+            if (l == "")
+                continue;
+
+            // Look for the word 'error'
+            if ("error" in l.down())
+                last = l;
+        }
+
+        // Use last line as the message
+        return last;
+    }
+
+    /**
+     * Finds a key in a string
+     *
+     * @param text The ASCII armoured key
+     * @param start Returned, start of the key
+     * @param end Returned, end of the key
+     *
+     * @return false if no key is contained, true otherwise
+     */
+    private bool detect_key(string text, out string? start = null, out string? end = null) {
+        // Find the first of the headers
+        int start_pos = text.index_of(PGP_KEY_BEGIN);
+        if (start_pos == -1)
+            return false;
+
+        if (start != null)
+            start = text[start_pos:(start_pos+PGP_KEY_BEGIN.length)];
+
+        /* Find the end of that block */
+        int end_pos = text.index_of(PGP_KEY_END);
+        if (end_pos == -1)
+            return false;
+
+        if (end != null)
+            end = text[end_pos:(end_pos+PGP_KEY_END.length)];
+
+        return true;
+    }
+
+    /* -----------------------------------------------------------------------------
+     *  SEAHORSE HKP SOURCE
+     */
+
+    private bool hkp_message_propagate_error(Soup.Message *message) throws GLib.Error {
+        gchar *text, *server;
+
+        if (!SOUP_MESSAGE_IS_ERROR (message))
+            return false;
+
+        if (message.status_code == Soup.STATUS_CANCELLED) {
+            g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+                                 _("The operation was cancelled"));
+            return true;
+        }
+
+        g_object_get (self, "key-server", &server, null);
+
+        // Make the body lower case, and no tags
+        text = g_strndup (message.response_body.data, message.response_body.length);
+        if (text != null) {
+            dehtmlize (text);
+            seahorse_util_string_lower (text);
+        }
+
+        if (text && strstr (text, "no keys")) {
+            return false; /* not found is not an error */
+        } else if (text && strstr (text, "too many")) {
+            g_set_error (error, HKP_ERROR_DOMAIN, 0,
+                         _("Search was not specific enough. Server “%s” found too many keys."), server);
+        } else {
+            g_set_error (error, HKP_ERROR_DOMAIN, message.status_code,
+                         _("Couldn’t communicate with server “%s”: %s"),
+                         server, message.reason_phrase);
+        }
+
+        return true;
+    }
+
+    private void on_session_cancelled(Cancellable cancellable, Soup.Session session) {
+        session.abort();
+    }
+
+    public async void search_async(string match, Gcr.SimpleCollection results,
+                                   Cancellable cancellable) throws GLib.Error {
+        Soup.Session session = create_hkp_soup_session ();
+
+        Soup.URI? uri = get_http_server_uri("/pks/lookup");
+        if (uri == null)
+            return false;
+
+        HashTable<string, string> form = new HashTable<string, string>();
+        form.insert("op", "index");
+
+        if (is_hex_keyid (match)) {
+            gchar hexfpr[11];
+            strncpy (hexfpr, "0x", 3);
+            strncpy (hexfpr + 2, match, 9);
+            form.insert("search", hexfpr);
+        } else {
+            form.insert("search", match);
+        }
+
+        form.insert("fingerprint", "on");
+
+        uri.set_query_from_form(form);
+
+        Soup.Message message = new Soup.Message("GET", uri);
+        session.queue_message(message, on_search_message_complete);
+
+        Seahorse.Progress.prep_and_begin (cancellable, message, null);
+
+        if (cancellable != null)
+            closure.cancelled_sig = cancellable.connect(on_session_cancelled, closure.session);
+    }
+
+    private void on_search_message_complete(Soup.Session session, Soup.Message message) {
+        GError *error = null;
+        Seahorse.Progress.end(cancellable, message);
+
+        if (hkp_message_propagate_error(message, &error)) {
+            g_simple_async_result_take_error (res, error);
+
+        } else {
+            List<Pgp.Key> keys = parse_hkp_index (message.response_body.data);
+            foreach (Pgp.Key key in keys) {
+                key.place = this;
+                results.add(key);
+            }
+        }
+    }
+
+    private bool is_hex_keyid(string? text) {
+        if (text == null || text.length != 8)
+            return false;
+
+        foreach (uint8 byt in text.data) {
+            if (!((byt >= 0x30 && byt <= 0x39) ||
+                  (byt >= 0x41 && byt <= 0x46) ||
+                  (byt >= 0x61 && byt <= 0x66)))
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Imports a list of keys from the input stream to the keyserver
+     *
+     * @param input The input stream to add
+     **/
+    public List? import_async(InputStream input, Cancellable cancellable) throws GLib.Error {
+        Soup.Session session = create_hkp_soup_session ();
+
+        List<string> keydata = new List<string>();
+        for (;;) {
+            StringBuilder buf = new StringBuilder(2048);
+            uint len = Seahorse.Util.read_data_block(buf, input, PGP_KEY_BEGIN, PGP_KEY_END);
+
+            if (len > 0)
+                keydata.append(buf.str);
+            else
+                break;
+        }
+
+        if (keydata.length == 0)
+            return null;
+
+        // Figure out the URI we're sending to
+        Soup.URI? uri = get_http_server_uri("/pks/add");
+        if (uri == null)
+            return null;
+
+        // New operation and away we go
+        int requests = 0;
+        foreach (string keytext) {
+            assert(keytext != null);
+
+            HashTable<string, string> form = new HashTable<string, string>();
+
+            form.insert("keytext", keytext);
+            string key = form.encode_urlencoded();
+
+            Soup.Message message = new Soup.Message("POST", uri);
+            message.set_request("application/x-www-form-urlencoded", Soup.MemoryUse.TAKE, key);
+
+            session.queue_message(message, on_import_message_complete);
+
+            requests++;
+            Seahorse.Progress.prep_and_begin(cancellable, requests, null);
+        }
+
+        if (cancellable)
+            closure.cancelled_sig = cancellable.connect(on_session_cancelled);
+
+        // We don't know which keys got imported, so just return null
+        return null;
+    }
+
+    private void on_import_message_complete (Soup.Session session, Soup.Message message) {
+        GError *error = null;
+        gchar *errmsg;
+
+        assert (closure.requests > 0);
+        seahorse_progress_end (closure.cancellable, GUINT_TO_POINTER (closure.requests));
+        closure.requests--;
+
+        if (hkp_message_propagate_error (closure.source, message, &error)) {
+            g_simple_async_result_take_error (res, error);
+
+        } else if ((errmsg = get_send_result (message.response_body.data)) != null) {
+            g_set_error (&error, HKP_ERROR_DOMAIN, message.status_code, "%s", errmsg);
+            g_simple_async_result_take_error (res, error);
+
+        // A successful status from the server is all we want in this case
+        } else {
+            if (closure.requests == 0)
+                g_simple_async_result_complete_in_idle (res);
+        }
+    }
+
+    /**
+     * Gets data from the keyserver, writes it to the output stream
+     *
+     * @param keyids the keyids to look up
+     *
+     * @return The output stream the data will end in
+     */
+    private void* export_async(string[] keyids, Cancellable cancellable) throws GLib.Error {
+        if (keyids == null || keyids.length = 0)
+            return null;
+
+        StringBuilder data = new StringBuilder.sized(1024);
+        Soup.Session session = create_hkp_soup_session ();
+
+        Soup.URI? uri = get_http_server_uri (self, "/pks/lookup");
+        if (uri == null)
+            return null;
+
+        // prepend the hex prefix (0x) to make keyservers happy
+        strncpy (hexfpr, "0x", 3);
+
+        int requests = 0;
+        foreach (string fpr in keyids) {
+            HashTable<string, string> form = new HashTable<string, string>();
+
+            // Get the key id and limit it to 8 characters
+            if (fpr.length > 8)
+                fpr += (len - 8);
+
+            gchar hexfpr[11];
+            strncpy (hexfpr + 2, fpr, 9);
+
+            // The get key URI
+            form.insert("op", "get");
+            form.insert("search", hexfpr);
+            uri.set_query_from_form(form);
+
+            Soup.Message message = new Soup.Message.from_uri("GET", uri);
+            session.queue_message(message, on_export_message_complete);
+
+            requests++;
+            Seahorse.Progress.prep_and_begin(cancellable, message, null);
+        }
+
+        if (cancellable)
+            closure.cancelled_sig = cancellable.connect(on_session_cancelled, closure.session, null);
+
+        // XXX export_async_finish(size_t size);
+        if (size == null)
+            return null;
+
+        *size = closure.data.len;
+        return data.str;
+    }
+
+    private void on_export_message_complete(Soup.Session session, Soup.Message message) {
+        GError *error = null;
+
+        seahorse_progress_end (closure.cancellable, message);
+
+        if (hkp_message_propagate_error (closure.source, message, &error)) {
+            g_simple_async_result_take_error (res, error);
+        } else {
+            string end, text;
+            end = text = message.response_body.data;
+            uint len = message.response_body.length;
+
+            for (;;) {
+                len -= end - text;
+                text = end;
+
+                string start;
+                if (!detect_key (text, len, &start, &end))
+                    break;
+
+                data.append_len(start, end - start);
+                data.append_c('\n');
+            }
+        }
+
+        assert (closure.requests > 0);
+        closure.requests--;
+
+        if (closure.requests == 0)
+            g_simple_async_result_complete_in_idle (res);
+    }
+}
+
+}
diff --git a/pgp/keyserver-results.ui b/pgp/keyserver-results.ui
new file mode 100644
index 0000000..5a3a672
--- /dev/null
+++ b/pgp/keyserver-results.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+      <object class="GtkVBox" id="keyserver-results">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkVBox" id="menu-placeholder">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="toolbar-placeholder">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="spacing">1</property>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolledwindow1">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hscrollbar_policy">automatic</property>
+                <child>
+                  <object class="GtkTreeView" id="key_list">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="border_width">12</property>
+                    <property name="rules_hint">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkProgressBar" id="progress-bar">
+                    <property name="visible">True</property>
+                    <property name="pulse_step">0.10000000149</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkStatusbar" id="status">
+                    <property name="visible">True</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+</interface>
diff --git a/pgp/keyserver-results.vala b/pgp/keyserver-results.vala
new file mode 100644
index 0000000..0f8591d
--- /dev/null
+++ b/pgp/keyserver-results.vala
@@ -0,0 +1,265 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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/>.
+ */
+
+namespace Seahorse {
+
+public class KeyserverResults : Seahorse.Catalog {
+
+    static const GtkActionEntry GENERAL_ENTRIES[] = {
+        /* TRANSLATORS: The "Remote" menu contains key operations on remote systems. */
+        { "remote-menu", null, N_("_Remote") },
+        { "app-close", GTK_STOCK_CLOSE, null, "<control>W",
+          N_("Close this window"), G_CALLBACK (on_app_close) },
+    };
+
+    static const GtkActionEntry SERVER_ENTRIES[] = {
+        { "remote-find", GTK_STOCK_FIND, N_("_Find Remote Keys…"), "",
+          N_("Search for keys on a key server"), G_CALLBACK (on_remote_find) }
+    };
+
+    static const GtkActionEntry IMPORT_ENTRIES[] = {
+        { "key-import-keyring", GTK_STOCK_ADD, N_("_Import"), "",
+          N_("Import selected keys to local key ring"), G_CALLBACK (on_key_import_keyring) }
+    };
+
+    public string search_string { get; set; default = ""; }
+
+    private Seahorse.Predicate pred;
+    private Gtk.TreeView view;
+    private Gcr.SimpleCollection collection;
+    private Gtk.ActionGroup import_actions;
+    private Seahorse.KeyManagerStore store;
+    private GLib.Settings settings;
+
+    public KeyServerResults() {
+        this.settings = new GLib.Settings("org.gnome.seahorse.manager");
+        this.collection = new Gcr.SimpleCollection();
+
+        load_ui();
+    }
+
+    private void load_ui() {
+        string title = (this.search_string == "")? _("Remote Keys") :
+                                                   _("Remote Keys Containing 
“%s”").printf(this.search_string);
+
+        Gtk.Window window = get_window();
+        window.set_default_geometry(640, 476);
+        window.set_events(GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | 
GDK_BUTTON_RELEASE_MASK);
+        window.title = title;
+        window.visible = true;
+
+        window.delete_event.connect(on_delete_event);
+
+        Gtk.ActionGroup actions = new Gtk.ActionGroup("general");
+        actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+        actions.add_actions(GENERAL_ENTRIES, this);
+        include_actions(actions);
+
+        actions = new Gtk.ActionGroup("keyserver");
+        actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+        actions.add_actions(SERVER_ENTRIES, this);
+        include_actions(actions);
+
+        this.import_actions = new Gtk.ActionGroup("import");
+        this.import_actions.set_translation_domain(Config.GETTEXT_PACKAGE);
+        this.import_actions.add_actions(IMPORT_ENTRIES, this);
+        this.import_actions.get_action("key-import-keyring").is_important = true;
+        include_actions(this.import_actions);
+
+        /* init key list & selection settings */
+        Gtk.Builder builder = get_builder();
+        this.view = (Gtk.TreeView) builder.get_object("key_list"));
+        Gtk.TreeSelection selection = this.view.get_selection();
+        selection.set_mode(GTK_SELECTION_MULTIPLE);
+        selection.changed.connect(on_view_selection_changed);
+        this.view.row_activated.connect(on_row_activated);
+        this.view.button_press_event.connect(on_key_list_button_pressed);
+        this.view.popup_menu.connect(on_key_list_popup_menu);
+        this.view.realize();
+
+        /* Set focus to the current key list */
+        this.view.grab_focus();
+
+        /* To avoid flicker */
+        ensure_updated();
+        show();
+
+        this.store = new Seahorse.KeyManagerStore(this.collection, this.view, out this.pred, this.settings);
+        on_view_selection_changed (selection, self);
+
+        // Include actions from the backend
+        include_actions(Pgp.Backend.get().actions);
+    }
+
+    /* -----------------------------------------------------------------------------
+     * INTERNAL
+     */
+
+    /**
+    * selection:
+    * self: the results object
+    *
+    * Adds fire_selection_changed as idle function
+    *
+    **/
+    private void on_view_selection_changed (GtkTreeSelection *selection) {
+        g_idle_add ((GSourceFunc)fire_selection_changed, self);
+    }
+
+    private bool fire_selection_changed() {
+        Gtk.TreeSelection selection = this.view.get_selection();
+        int rows = selection.count_selected_rows();
+        if (this.import_actions)
+            this.import_actions.set_sensitive(rows > 0);
+        selection_changed();
+        return false;
+    }
+
+    private void on_row_activated (Gtk.TreeView view, GtkTreePath *path, GtkTreeViewColumn *column) {
+        g_return_if_fail (path != null);
+        g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (column));
+
+        GLib.Object obj = seahorse_key_manager_store_get_object_from_path (view, path);
+        if (obj != null)
+            show_properties(obj);
+    }
+
+    private bool on_key_list_button_pressed (Gtk.TreeView view, GdkEventButton* event) {
+        if (event->button == 3)
+            show_context_menu(SEAHORSE_CATALOG_MENU_OBJECT, event->button, event->time);
+        return false;
+    }
+
+    private bool on_key_list_popup_menu (Gtk.TreeView view) {
+        List<?> objects = get_selected_objects();
+        if (objects != null)
+            show_context_menu(SEAHORSE_CATALOG_MENU_OBJECT, 0, gtk_get_current_event_time ());
+        return true;
+    }
+
+    /**
+    * action: the closing action or null
+    * self: The SeahorseKeyServerResults widget to destroy
+    *
+    * destroys the widget
+    *
+    **/
+    private void on_app_close (Gtk.Action action) {
+        g_return_if_fail (action == null || GTK_IS_ACTION (action));
+        destroy();
+    }
+
+    private void on_remote_find (Gtk.Action action) {
+        g_return_if_fail (GTK_IS_ACTION (action));
+        seahorse_keyserver_search_show (get_window());
+    }
+
+    private void on_import_complete(GObject *source, GAsyncResult *result) {
+        GError *error = null;
+
+        if (!seahorse_pgp_backend_transfer_finish (SEAHORSE_PGP_BACKEND (source),
+                                                   result, &error))
+            seahorse_util_handle_error (&error, seahorse_catalog_get_window (self),
+                                        _("Couldn’t import keys"));
+    }
+
+    static List<?> objects_prune_non_exportable (List<?> objects) {
+        List<?> exportable = null;
+        List<?> l;
+
+        for (l = objects; l; l = g_list_next (l)) {
+            if (seahorse_object_get_flags (l->data) & SEAHORSE_FLAG_EXPORTABLE)
+                exportable = g_list_append (exportable, l->data);
+        }
+
+        return exportable;
+    }
+
+    private void on_key_import_keyring (Gtk.Action action) {
+        List<?> objects = get_selected_objects();
+        objects = objects_prune_non_exportable (objects);
+
+        /* No objects, nothing to do */
+        if (objects == null)
+            return;
+
+        Cancellable cancellable = new Cancellable();
+        Pgp.Backend backend = Pgp.Backend.get();
+        GpgME.Keyring keyring = backend.get_default_keyring();
+        backend.transfer_async(objects, keyring, cancellable, on_import_complete, g_object_ref (self));
+        Seahorse.Progress.show(cancellable, _ ("Importing keys from key servers"), true);
+    }
+
+    /**
+    * widget: sending widget
+    * event: ignored
+    * self: The SeahorseKeyserverResults widget to destroy
+    *
+    * When this window closes we quit seahorse
+    *
+    * Returns true on success
+    **/
+    static bool on_delete_event (GtkWidget* widget, GdkEvent* event, SeahorseKeyserverResults* self) {
+        g_return_val_if_fail (GTK_IS_WIDGET (widget), false);
+        on_app_close (null, self);
+        return true;
+    }
+
+    static List<?> get_selected_objects() {
+        return seahorse_key_manager_store_get_selected_objects (this.view);
+    }
+
+    static Seahorse.Place get_focused_place() {
+        return Pgp.Backend.get().get_default_keyring();
+    }
+
+    /**
+     * Creates a search results window and adds the operation to it's progress status.
+     *
+     * @param search_text The text to search for
+     * @param parent A GTK window as parent (or null)
+     */
+    public void show(string search_text, GtkWindow *parent) {
+        g_return_if_fail (search_text != null);
+
+        self = g_object_new (SEAHORSE_TYPE_KEYSERVER_RESULTS,
+                             "ui-name", "keyserver-results",
+                             "search", search_text,
+                             "transient-for", parent,
+                             null);
+
+        Cancellable cancellable = new Cancellable();
+
+        Pgp.Backend.get().search_remote_async.begin(null, search_text, this.collection, cancellable, (obj, 
res) => {
+            try {
+                Backend.get().search_remote_finish(res);
+            } catch (GLib.Error e) {
+                g_dbus_error_strip_remote_error (e);
+                seahorse_util_show_error (this, _("The search for keys failed."), e);
+            }
+        });
+
+        Seahorse.Progress.attach(cancellable, get_builder());
+    }
+}
+
+}
diff --git a/pgp/keyserver-search.ui b/pgp/keyserver-search.ui
new file mode 100644
index 0000000..194ef24
--- /dev/null
+++ b/pgp/keyserver-search.ui
@@ -0,0 +1,254 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkImage" id="search-image">
+    <property name="stock">gtk-find</property>
+    <property name="icon-size">4</property>
+  </object>
+  <object class="GtkDialog" id="keyserver-search">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Find Remote Keys</property>
+    <property name="resizable">False</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="gravity">center</property>
+    <signal name="delete_event" handler="on_widget_delete_event"/>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <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="orientation">vertical</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkBox" id="hbox2">
+                <property name="visible">True</property>
+                <property name="orientation">horizontal</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkImage" id="image2">
+                    <property name="visible">True</property>
+                    <property name="stock">gtk-find</property>
+                    <property name="icon-size">5</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="publish-message">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <property name="label" translatable="yes">This will find keys for others on the 
Internet. These keys can then be imported into your local key ring.</property>
+                    <property name="wrap">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="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="hbox6">
+                <property name="visible">True</property>
+                <property name="orientation">horizontal</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkLabel" id="label13">
+                    <property name="visible">True</property>
+                    <property name="xpad">4</property>
+                    <property name="label" translatable="yes">_Search for keys containing: </property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">search-text</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="search-text">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="has_focus">True</property>
+                    <accelerator key="Return" signal="activate"/>
+                    <signal name="changed" handler="on_keyserver_search_control_changed"/>
+                    <signal name="activate" handler="on_keyserver_search_ok_clicked"/>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkExpander" id="search-where">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="height_request">135</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">etched-out</property>
+                    <child>
+                      <object class="GtkViewport" id="viewport1">
+                        <property name="visible">True</property>
+                        <child>
+                          <object class="GtkBox" id="vbox2">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <child>
+                              <object class="GtkBox" id="key-server-list">
+                                <property name="visible">True</property>
+                                <property name="orientation">vertical</property>
+                                <property name="border_width">6</property>
+                                <child>
+                                  <object class="GtkLabel" id="label16">
+                                    <property name="visible">True</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">Key Servers:</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>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="shared-keys-list">
+                                <property name="visible">True</property>
+                                <property name="orientation">vertical</property>
+                                <property name="border_width">6</property>
+                                <child>
+                                  <object class="GtkLabel" id="label17">
+                                    <property name="visible">True</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label" translatable="yes">Shared Keys Near Me:</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>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label15">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Where to search:</property>
+                    <property name="use_markup">True</property>
+                    <property name="use_underline">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <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="button3">
+                <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>
+                <signal name="clicked" handler="on_widget_closed"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="search">
+                <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="image">search-image</property>
+                <property name="label" translatable="yes">_Search</property>
+                <property name="use_underline">True</property>
+                <signal name="clicked" handler="on_keyserver_search_ok_clicked"/>
+              </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>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">button3</action-widget>
+      <action-widget response="-5">search</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/keyserver-search.vala b/pgp/keyserver-search.vala
new file mode 100644
index 0000000..5a81062
--- /dev/null
+++ b/pgp/keyserver-search.vala
@@ -0,0 +1,360 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004-2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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 {
+
+/**
+ * Contains the UI and functions to start a search for keys on a keyserver.
+ **/
+public class KeyserverSearch : Gtk.Dialog {
+
+    /**
+     * A list of keyserver names
+     */
+    private string[] names;
+
+    /**
+     * A list of keyserver URIs
+     */
+    private string[] uris;
+
+    /**
+     * true if all keyservers are selected
+     */
+    private bool all;
+
+    // Widget descendants
+    private Gtk.Entry search_text;
+    private Gtk.Spinner search_where;
+    private Gtk.Box key_server_list;
+    private Gtk.Box shared_keys_list;
+
+    public KeyserverSearch(Gtk.Window parent) {
+        GLib.Object(transient_for: parent);
+    }
+
+    /**
+     * Shows a remote search window.
+     *
+     * @param parent the parent window to connect this window to
+     */
+    void show() {
+        GtkWindow *window;
+        GtkWidget *widget;
+
+        swidget = seahorse_widget_new ("keyserver-search", parent);
+        g_return_val_if_fail (swidget != null, null);
+
+        window = GTK_WINDOW (seahorse_widget_get_widget (swidget, swidget->name));
+
+        Gtk.Entry entry = seahorse_widget_get_widget (swidget, "search-text");
+        g_return_val_if_fail (entry != null, window);
+
+        string? last_search = Seahorse.Application.settings().get_string("last-search-text");
+        if (search != null) {
+            entry.text = last_search;
+            entry.select_region(0, -1);
+        }
+
+        // The key servers to list
+        GLib.Settings settings = Seahorse.Application.pgp_settings();
+        on_settings_keyservers_changed (settings, "keyservers", swidget);
+        g_signal_connect_object (settings, "changed::keyservers",
+                                 G_CALLBACK (on_settings_keyservers_changed), swidget, 0);
+
+        // Any shared keys to list
+        Discovery ssd = Pgp.Backend.get().get_discovery();
+        refresh_shared_keys(ssd, null, swidget);
+        ssd.added.connect(refresh_shared_keys);
+        ssd.removed.connect(refresh_shared_keys);
+        window.destroy.connect(cleanup_signals);
+
+        select_inital_keyservers();
+        on_keyserver_search_control_changed(null, swidget);
+
+        return window;
+    }
+
+
+    /* Selection Retrieval ------------------------------------------------------ */
+
+    /**
+     * Extracts all keyservers in the sub-widgets "key-server-list" and
+     * "shared-keys-list" and returns the selection
+     **/
+    private void get_keyserver_selection() {
+        /* Key servers */
+        Gtk.Widget widget = seahorse_widget_get_widget (swidget, "key-server-list");
+        g_return_val_if_fail (widget != null, selection);
+        gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback)get_checks, selection);
+
+        /* Shared Key */
+        widget = seahorse_widget_get_widget (swidget, "shared-keys-list");
+        g_return_val_if_fail (widget != null, selection);
+        gtk_container_foreach (GTK_CONTAINER (widget), (GtkCallback)get_checks, selection);
+
+        return selection;
+    }
+
+    /**
+     * widget: CHECK_BUTTON widget to read
+     * selection: will be updated depending on the state of the widget
+     *
+     * Adds the name/uri of the checked widget to the selection
+     *
+     **/
+    private void get_checks (GtkWidget *widget, KeyserverSelection *selection) {
+        if (!GTK_IS_CHECK_BUTTON (widget))
+            return;
+
+        /* Pull in the selected name and uri */
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
+            string value = gtk_button_get_label (GTK_BUTTON (widget));
+            g_ptr_array_add (selection->names, g_strdup (value));
+            value = g_object_get_data (G_OBJECT (widget), "keyserver-uri");
+            g_ptr_array_add (selection->uris, g_strdup (value));
+
+        /* Note that not all checks are selected */
+        } else {
+            selection->all = false;
+        }
+    }
+
+    /**
+     * returns true if at least one of the CHECK_BUTTONS in "key-server-list" or
+     * "shared-keys-list" is true
+     **/
+    private bool have_keyserver_selection() {
+        /* Key servers */
+        Gtk.Widget w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "key-server-list"));
+        g_return_val_if_fail (w != null, false);
+        foreach (child in w) {
+            Gtk.CheckButton? btn = child as Gtk.CheckButton;
+            if (btn != null && btn.active)
+                return true;
+        }
+
+        /* Shared keys */
+        w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "shared-keys-list"));
+        g_return_val_if_fail (w != null, false);
+        foreach (child in w) {
+            Gtk.CheckButton? btn = child as Gtk.CheckButton;
+            if (btn != null && btn.active)
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Enables the "search" button if the edit-field contains text and at least a
+     * server is selected
+     */
+    G_MODULE_EXPORT void on_keyserver_search_control_changed (GtkWidget *widget) {
+        bool enabled = true;
+        GtkWidget *w;
+
+        /* Need to have at least one key server selected ... */
+        if (!have_keyserver_selection (swidget))
+            enabled = false;
+
+        /* ... and some search text */
+        else {
+            w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "search-text"));
+            string text = gtk_editable_get_chars (GTK_EDITABLE (w), 0, -1);
+            if (!text || !text[0])
+                enabled = false;
+        }
+
+        w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "search"));
+        w.sensitive = enabled;
+    }
+
+    /* Initial Selection -------------------------------------------------------- */
+
+    /**
+     * Reads key servers from settings and updates the UI content.
+     */
+    private void select_inital_keyservers(SeahorseWidget *swidget) {
+        string[] names = Seahorse.Application.settings().get_strv("last-search-servers");
+
+        /* Close the expander if all servers are selected */
+        Gtk.Widget widget = seahorse_widget_get_widget (swidget, "search-where");
+        g_return_if_fail (widget != null);
+        gtk_expander_set_expanded (GTK_EXPANDER (widget), names != null && names[0] != null);
+
+        /* We do case insensitive matches */
+        for (uint i = 0; names[i] != null; i++) {
+            names[i] = g_utf8_casefold (names[i], -1);
+        }
+
+        widget = seahorse_widget_get_widget (swidget, "key-server-list");
+        g_return_if_fail (widget != null);
+        gtk_container_foreach (GTK_CONTAINER (widget), foreach_child_select_checks, names);
+
+        widget = seahorse_widget_get_widget (swidget, "shared-keys-list");
+        g_return_if_fail (widget != null);
+        gtk_container_foreach (GTK_CONTAINER (widget), foreach_child_select_checks, names);
+    }
+
+    private void foreach_child_select_checks (GtkWidget *widget, string[] names) {
+        if (GTK_IS_CHECK_BUTTON (widget)) {key_server
+            string name = g_utf8_casefold (gtk_button_get_label (GTK_BUTTON (widget)), -1);
+            bool checked = names != null && names[0] != null ? false : true;
+            for (uint i = 0; names && names[i] != null; i++) {
+                if (g_utf8_collate (names[i], name) == 0) {
+                    checked = true;
+                    break;
+                }
+            }
+            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), checked);
+        }
+    }
+
+    /* Populating Lists --------------------------------------------------------- */
+
+    /**
+     * Updates the box and adds checkboxes containing names/uris. The check-status
+     * of already existing check boxes is not changed.
+     *
+     * box: the GTK_CONTAINER with the checkboxes
+     * uris: the uri list of the keyservers
+     * names: the keyserver names
+     **/
+    private void populate_keyserver_list(Gtk.Box box, string[] uris, string[] names) {
+        // Remove all checks, and note which ones were unchecked
+        HashTable<string, ?> unchecked = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, null);
+        gtk_container_foreach (box, remove_checks, unchecked);
+
+        bool any = false;
+        // Now add the new ones back
+        for (uint i = 0; uris && uris[i] && names && names[i]; i++) {
+            any = true;
+
+            // A new checkbox with this the name as the label
+            Gtk.CheckButton check = new Gtk.CheckButton.with_label(names[i]);
+            check.active = (unchecked.lookup(names[i]) == null);
+            check.toggled.connect(on_keyserver_search_control_changed);
+            check.show();
+
+            // Save URI and set it as the tooltip
+            check.set_data<string>("keyserver-uri", uris[i]);
+            check.tooltip_text = uris[i];
+
+            box.add(check);
+        }
+
+        // Only display the container if we had some checks
+        box.visible = any;
+    }
+
+    /**
+     * widget: a check button
+     * unchecked: a hash table containing the state of the servers
+     *
+     * If the button is not checked, the hash table entry associate with it will be
+     * replaced with ""
+     *
+     **/
+    private void remove_checks(GtkWidget *widget, HashTable *unchecked) {
+        if (GTK_IS_CHECK_BUTTON (widget)) {
+
+            if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+                unchecked.replace(gtk_button_get_label (GTK_BUTTON (widget)), "");
+
+            widget.destroy();
+        }
+    }
+
+    private void on_settings_keyservers_changed (GSettings *settings, const gchar *key) {
+        GtkWidget *widget;
+
+        widget = seahorse_widget_get_widget (swidget, "key-server-list");
+        g_return_if_fail (widget != null);
+
+        string[] keyservers = seahorse_servers_get_uris ();
+        string[] names = seahorse_servers_get_names ();
+        populate_keyserver_list (swidget, widget, keyservers, names);
+    }
+
+    /**
+     * refreshes the "shared-keys-list"
+     *
+     * ssd: the SeahorseServiceDiscovery. List-data is read from there
+     * name: ignored
+     */
+    private void refresh_shared_keys(Seahorse.Discovery ssd, string name) {
+        GtkWidget widget = seahorse_widget_get_widget (swidget, "shared-keys-list");
+        g_return_if_fail (widget != null);
+
+        string[] names = ssd.list();
+        string[] keyservers = ssd.get_uris(names);
+        populate_keyserver_list(widget, keyservers, names);
+    }
+
+    /* -------------------------------------------------------------------------- */
+
+    /**
+     * Extracts data, stores it in settings and starts a search using the entered
+     * search data.
+     *
+     * This function gets the things done
+     */
+    G_MODULE_EXPORT void on_keyserver_search_ok_clicked(GtkButton *button) {
+        KeyserverSelection *selection;
+        GtkWidget *widget;
+        GtkWindow *parent;
+
+        widget = seahorse_widget_get_widget (swidget, "search-text");
+        g_return_if_fail (widget != null);
+
+        GLib.Settings app_settings = Seahorse.Application.settings();
+
+        /* Get search text and save it for next time */
+        string search = gtk_entry_get_text (GTK_ENTRY (widget));
+        g_return_if_fail (search != null && search[0] != 0);
+        app_settings.set_string("last-search-text", search);
+
+        /* The keyservers to search, and save for next time */
+        selection = get_keyserver_selection (swidget);
+        g_return_if_fail (selection->uris != null);
+        app_settings.set_strv("last-search-servers", selection->all ? null : selection->uris);
+
+        // Open the new result window; its transient parent is *our* transient
+        // parent (Seahorse's primary window), not ourselves, as *we* will
+        // disappear when "OK" is clicked.
+        seahorse_keyserver_results_show (search, this.transient_for);
+
+        free_keyserver_selection (selection);
+        swidget.destroy();
+    }
+
+    /**
+     * Disconnects the added/removed signals
+     **/
+    private void cleanup_signals (GtkWidget *widget, SeahorseWidget *swidget) {
+        Seahorse.Discovery ssd = Backend.get().get_discovery();
+        g_signal_handlers_disconnect_by_func (ssd, refresh_shared_keys, swidget);
+    }
+
+}
+
+}
diff --git a/pgp/keyserver-sync.ui b/pgp/keyserver-sync.ui
new file mode 100644
index 0000000..70aa2c9
--- /dev/null
+++ b/pgp/keyserver-sync.ui
@@ -0,0 +1,180 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkImage" id="configure-image">
+    <property name="stock">gtk-properties</property>
+  </object>
+  <object class="GtkImage" id="sync-image">
+    <property name="stock">gtk-refresh</property>
+  </object>
+  <object class="GtkDialog" id="keyserver-sync">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Sync Keys</property>
+    <property name="window_position">center</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkHBox" id="hbox2">
+                <property name="visible">True</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkImage" id="image2">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="yalign">0</property>
+                    <property name="stock">gtk-network</property>
+                    <property name="icon-size">5</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="vbox2">
+                    <property name="visible">True</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkLabel" id="publish-message">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">This will publish the keys in your key 
ring so they’re available for others to use. You’ll also get any changes others have made since you received 
their keys.</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="GtkLabel" id="sync-message">
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">This will retrieve any changes others have 
made since you received their keys. No key server has been chosen for publishing, so your keys will not be 
made available to others.</property>
+                        <property name="wrap">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="detail-message">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label"></property>
+                        <property name="use_markup">True</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="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">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="configure">
+                <property name="label" translatable="yes">_Key Servers</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="image">configure-image</property>
+                <property name="use_underline">True</property>
+                <signal name="clicked" handler="on_sync_configure_clicked"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button3">
+                <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>
+                <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>
+            <child>
+              <object class="GtkButton" id="sync-button">
+                <property name="label" translatable="yes">_Sync</property>
+                <property name="visible">True</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">sync-image</property>
+                <property name="use_underline">True</property>
+                <signal name="clicked" handler="on_sync_ok_clicked"/>
+              </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="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">configure</action-widget>
+      <action-widget response="-6">button3</action-widget>
+      <action-widget response="-5">sync-button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/keyserver-sync.vala b/pgp/keyserver-sync.vala
new file mode 100644
index 0000000..51e9409
--- /dev/null
+++ b/pgp/keyserver-sync.vala
@@ -0,0 +1,145 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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 {
+
+public class KeyserverSync : Gtk.Dialog {
+
+    static void on_transfer_upload_complete(GObject object, AsyncResult *result, Seahorse.Place place) {
+        GError *error = null;
+
+        if (!seahorse_pgp_backend_transfer_finish (SEAHORSE_PGP_BACKEND (object), result, &error)) {
+            string publish_to = Seahorse.Application.settings().get_string("server-publish-to");
+            seahorse_util_handle_error (&error, null,
+                                        _("Couldn’t publish keys to server"), publish_to);
+        }
+    }
+
+    static void on_transfer_download_complete (GObject *object, AsyncResult *result, Seahorse.Place place) {
+        GError *error = null;
+        if (!seahorse_transfer_finish (result, &error)) {
+            seahorse_util_handle_error (&error, null,
+                                        _("Couldn’t retrieve keys from server: %s"), place.key_server);
+        }
+    }
+
+    G_MODULE_EXPORT void on_sync_ok_clicked (GtkButton *button) {
+        GList *keys;
+        keys = (GList*)g_object_get_data (G_OBJECT (swidget), "publish-keys");
+        keys = g_list_copy (keys);
+
+        destroy();
+
+        seahorse_keyserver_sync (keys);
+    }
+
+    G_MODULE_EXPORT void on_sync_configure_clicked (GtkButton *button) {
+        seahorse_prefs_show (GTK_WINDOW (seahorse_widget_get_widget (swidget, swidget->name)), 
"keyserver-tab");
+    }
+
+    static void update_message() {
+        Gtk.Widget widget = seahorse_widget_get_widget (swidget, "publish-message");
+        Gtk.Widget widget2 = seahorse_widget_get_widget (swidget, "sync-message");
+        Gtk.Widget sync_button = seahorse_widget_get_widget (swidget, "sync-button");
+
+        string text = Seahorse.Application.settings().get_string("server-publish-to");
+        bool filled_in = (text != null && text != "");
+        widget.sensitive = filled_in;
+        widget2.sensitive = !filled_in;
+        sync_button.sensitive = filled_in;
+    }
+
+    static void on_settings_publish_to_changed(GSettings *settings, string key) {
+        update_message();
+    }
+
+    /**
+     * Shows a synchronize window.
+     *
+     * @keys: The keys to synchronize
+     **/
+    public void show(List<Pgp.Key> keys, GtkWindow *parent) {
+        SeahorseWidget *swidget;
+        GtkWindow *win;
+        GtkWidget *w;
+        guint n;
+
+        swidget = seahorse_widget_new_allow_multiple ("keyserver-sync", parent);
+        g_return_val_if_fail (swidget != null, null);
+
+        /* The details message */
+        string t = ngettext("<b>%d key is selected for synchronizing</b>",
+                            "<b>%d keys are selected for synchronizing</b>", keys.length), keys.length);
+
+        Gtk.Label detail_message = GTK_WIDGET (seahorse_widget_get_widget (swidget, "detail-message"));
+        detail_message.set_markup(t);
+
+        // The right help message
+        update_message();
+        g_signal_connect_object (seahorse_application_settings (null), "changed::server-publish-to",
+                                 G_CALLBACK (on_settings_publish_to_changed), swidget, 0);
+
+        keys = g_list_copy (keys);
+        g_return_val_if_fail (!keys || SEAHORSE_IS_OBJECT (keys->data), win);
+        g_object_set_data_full (G_OBJECT (swidget), "publish-keys", keys,
+                                (GDestroyNotify)g_list_free);
+    }
+
+    void seahorse_keyserver_sync(List<Pgp.Key> keys) {
+        if (keys == null)
+            return;
+
+        Cancellable cancellable = new Cancellable();
+
+        string[] keyids = new string[keyids.length];
+        foreach (Pgp.Key key in keys)
+            keyids += key.keyid;
+
+        // And now synchronizing keys from the servers
+        string[] keyservers = seahorse_servers_get_uris ();
+        foreach (string keyserver in keyservers) {
+            ServerSource? source = Pgp.Backend.get().lookup_remote(keyserver);
+
+            if (source == null) // This can happen if the URI scheme is not supported
+                continue;
+
+            GpgME.Keyring keyring = Pgp.Backend.get_default_keyring (null);
+            Seahorse.Transfer.transfer_keyids_async(source, keyring, keyids, cancellable,
+                                                    on_transfer_download_complete, source);
+        }
+
+        /* Publishing keys online */
+        string? keyserver = Seahorse.Application.settings().get_string("server-publish-to");
+        if (keyserver != null && keyserver != "") {
+            ServerSource? source = Pgp.Backend.get().lookup_remote(keyserver);
+
+            if (source == null) // This can happen if the URI scheme is not supported
+                continue;
+
+            Pgp.Backend.transfer_async(null, keys, source, cancellable,
+                                       on_transfer_upload_complete, source);
+        }
+
+        Seahorse.Progress.show(cancellable, _("Synchronizing keys…"), false);
+    }
+}
+
+}
diff --git a/pgp/ldap-source.vala b/pgp/ldap-source.vala
new file mode 100644
index 0000000..63b8647
--- /dev/null
+++ b/pgp/ldap-source.vala
@@ -0,0 +1,1008 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004 - 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.LdapSource : ServerSource {
+    /**
+     * Amount of keys to load in a batch
+     */
+    public const int DEFAULT_LOAD_BATCH = 30;
+
+    static construct {
+        if (Config.WITH_LDAP)
+            Seahorse.Server.register_type("ldap", _("LDAP Key Server"), seahorse_ldap_is_valid_uri);
+    }
+
+    /**
+     * Creates a new key source for an LDAP PGP server.
+     *
+     * @param uri The server to connect to
+     */
+    public LDAPSource(string uri, string host) {
+        GLib.Object(key_server: host, uri: uri);
+    }
+
+    /**
+     * @param uri The uri to check
+     *
+     * Returns: Whether the passed uri is valid for an ldap key source
+     */
+    public static bool is_valid_uri (string uri) {
+        if (uri == null || uri == "")
+            return false;
+
+        LDAPURLDesc *url;
+        int r;
+
+        r = ldap_url_parse (uri, &url);
+        if (r == LDAP_URL_SUCCESS) {
+
+            /* Some checks to make sure it's a simple URI */
+            if (!(url.lud_host && url.lud_host[0]) ||
+                (url.lud_dn && url.lud_dn[0]) ||
+                (url.lud_attrs || url.lud_attrs))
+                r = LDAP_URL_ERR_PARAM;
+
+            ldap_free_urldesc (url);
+        }
+
+        return r == LDAP_URL_SUCCESS;
+    }
+
+    /* -----------------------------------------------------------------------------
+     * SERVER INFO
+     */
+
+    typedef struct _LDAPServerInfo {
+        string base_dn;             /* The base dn where PGP keys are found */
+        string key_attr;            /* The attribute of PGP key data */
+        uint version;              /* The version of the PGP server software */
+    } LDAPServerInfo;
+
+    static LDAPServerInfo* get_ldap_server_info(bool force) {
+        LDAPServerInfo? sinfo = get_data("server-info");
+
+        // When we're asked to force getting the data, we fill in some defaults
+        if (sinfo == null && force) {
+            sinfo = g_new0 (LDAPServerInfo, 1);
+            sinfo.base_dn = "OU=ACTIVE,O=PGP KEYSPACE,C=US";
+            sinfo.key_attr = "pgpKey";
+            sinfo.version = 0;
+            set_data_full("server-info", sinfo, free_ldap_server_info);
+        }
+
+        return sinfo;
+    }
+
+
+    /* -----------------------------------------------------------------------------
+     *  LDAP HELPERS
+     */
+
+#define LDAP_ERROR_DOMAIN (get_ldap_error_domain())
+
+    static string[] get_ldap_values(LDAP *ld, LDAPMessage *entry, string attribute) {
+        struct berval **bv = ldap_get_values_len (ld, entry, attribute);
+        if (!bv)
+            return null;
+
+        string[] array = new string[];
+        int num = ldap_count_values_len(bv);
+        for(int i = 0; i < num; i++) {
+            string val = g_strndup (bv[i].bv_val, bv[i].bv_len);
+            array += val;
+        }
+
+        return array;
+    }
+
+#if WITH_DEBUG
+    static void dump_ldap_entry (LDAP *ld, LDAPMessage *res) {
+        string dn = ldap_get_dn (ld, res);
+        debug ("dn: %s\n", dn);
+
+        BerElement *pos;
+        for (string? t = ldap_first_attribute (ld, res, &pos); t != null; t = ldap_next_attribute (ld, res, 
pos)) {
+            string[] values = get_ldap_values (ld, res, t);
+            for (string val in values)
+                g_debug ("%s: %s\n", t, v);
+
+            g_strfreev (values);
+            ldap_memfree (t);
+        }
+
+        ber_free (pos, 0);
+    }
+#endif /* WITH_DEBUG */
+
+    static GQuark get_ldap_error_domain() {
+        static GQuark q = 0;
+        if(q == 0)
+            q = g_quark_from_static_string ("seahorse-ldap-error");
+        return q;
+    }
+
+    static string? get_string_attribute (LDAP *ld, LDAPMessage *res, string attribute) {
+        string[] vals = get_ldap_values (ld, res, attribute);
+        return (vals != null) vals[0] : null;
+    }
+
+    static bool get_boolean_attribute (LDAP* ld, LDAPMessage *res, string attribute) {
+        string[] vals = get_ldap_values (ld, res, attribute);
+        if (!vals)
+            return false;
+        bool b = vals[0] && atoi (vals[0]) == 1;
+        return b;
+    }
+
+    static long int get_int_attribute (LDAP* ld, LDAPMessage *res, string attribute) {
+        string[] vals = get_ldap_values (ld, res, attribute);
+        if (!vals)
+            return 0;
+        long int d = vals[0] ? atoi (vals[0]) : 0;
+        return d;
+    }
+
+    static long int get_date_attribute (LDAP* ld, LDAPMessage *res, string attribute) {
+        struct tm t;
+        long int d = 0;
+
+        string[] vals = get_ldap_values (ld, res, attribute);
+        if (!vals)
+            return 0;
+
+        if (vals[0]) {
+            memset(&t, 0, sizeof (t));
+
+            /* YYYYMMDDHHmmssZ */
+            sscanf(vals[0], "%4d%2d%2d%2d%2d%2d",
+                &t.tm_year, &t.tm_mon, &t.tm_mday,
+                &t.tm_hour, &t.tm_min, &t.tm_sec);
+
+            t.tm_year -= 1900;
+            t.tm_isdst = -1;
+            t.tm_mon--;
+
+            d = mktime (&t);
+        }
+
+        return d;
+    }
+
+    static string? get_algo_attribute(LDAP* ld, LDAPMessage *res, string attribute) {
+        string[] vals = get_ldap_values (ld, res, attribute);
+        if (vals == null || vals.length < 1 || vals[0] == null)
+            return null;
+
+        if (g_ascii_strcasecmp (vals[0], "DH/DSS") == 0 ||
+            g_ascii_strcasecmp (vals[0], "Elg") == 0 ||
+            g_ascii_strcasecmp (vals[0], "Elgamal") == 0 ||
+            g_ascii_strcasecmp (vals[0], "DSS/DH") == 0)
+            return "Elgamal";
+        if (g_ascii_strcasecmp (vals[0], "RSA") == 0)
+            return "RSA";
+        if (g_ascii_strcasecmp (vals[0], "DSA") == 0)
+            return "DSA";
+
+        return null;
+    }
+
+    /*
+     * Escapes a value so it's safe to use in an LDAP filter. Also trims
+     * any spaces which cause problems with some LDAP servers.
+     */
+    static string escape_ldap_value(string v) {
+        assert(v != null);
+        StringBuilder result = g_string_sized_new (strlen(v));
+
+        for (int i = 0; i < v.length; v++) {
+            switch(v[i]) {
+            case '#': case ',': case '+': case '\\':
+            case '/': case '\"': case '<': case '>': case ';':
+                result.append_c('\\');
+                result.append_c(v[i]);
+                continue;
+            };
+
+            if(v[i] < 32 || v[i] > 126) {
+                resulit.append_printf("\\%02X", *v);
+                continue;
+            }
+
+            result.append_c(v[i]);
+        }
+
+        return result.str.strip();
+    }
+
+    typedef bool (*SeahorseLdapCallback)   (LDAPMessage *result, gpointer user_data);
+
+    typedef struct {
+        GSource source;
+        LDAP *ldap;
+        int ldap_op;
+        GCancellable *cancellable;
+        bool cancelled;
+        gint cancelled_sig;
+    } SeahorseLdapGSource;
+
+    static bool seahorse_ldap_gsource_prepare (GSource *gsource, int *timeout) {
+        SeahorseLdapGSource *ldap_gsource = (SeahorseLdapGSource *)gsource;
+
+        if (ldap_gsource.cancelled)
+            return true;
+
+        /* No other way, but to poll */
+        *timeout = 50;
+        return false;
+    }
+
+    static bool seahorse_ldap_gsource_check (GSource *gsource) {
+        return true;
+    }
+
+    static bool seahorse_ldap_gsource_dispatch(GSource *gsource, GSourceFunc callback, gpointer user_data) {
+        SeahorseLdapGSource *ldap_gsource = (SeahorseLdapGSource *)gsource;
+
+        if (ldap_gsource.cancelled) {
+            ((SeahorseLdapCallback)callback) (null, user_data);
+            return false;
+        }
+
+        for (int i = 0; i < DEFAULT_LOAD_BATCH; i++) {
+
+            /* This effects a poll */
+            timeout.tv_sec = 0;
+            timeout.tv_usec = 0;
+
+            struct timeval timeout; LDAPMessage *result;
+            int rc = ldap_result (ldap_gsource.ldap, ldap_gsource.ldap_op,
+                              0, &timeout, &result);
+            if (rc == -1) {
+                g_warning ("ldap_result failed with rc = %d, errno = %s",
+                           rc, g_strerror (errno));
+                return false;
+
+            /* Timeout */
+            } else if (rc == 0) {
+                return true;
+            }
+
+            bool ret = ((SeahorseLdapCallback)callback) (result, user_data);
+            ldap_msgfree (result);
+
+            if (!ret)
+                return false;
+        }
+
+        return true;
+    }
+
+    static void seahorse_ldap_gsource_finalize (GSource *gsource) {
+        SeahorseLdapGSource *ldap_gsource = (SeahorseLdapGSource *)gsource;
+        g_cancellable_disconnect (ldap_gsource.cancellable,
+                                  ldap_gsource.cancelled_sig);
+        g_clear_object (&ldap_gsource.cancellable);
+    }
+
+    static GSourceFuncs seahorse_ldap_gsource_funcs = {
+        seahorse_ldap_gsource_prepare,
+        seahorse_ldap_gsource_check,
+        seahorse_ldap_gsource_dispatch,
+        seahorse_ldap_gsource_finalize,
+    };
+
+    static void on_ldap_gsource_cancelled (GCancellable *cancellable, gpointer user_data) {
+        SeahorseLdapGSource *ldap_gsource = user_data;
+        ldap_gsource.cancelled = true;
+    }
+
+    static GSource * seahorse_ldap_gsource_new (LDAP *ldap, int ldap_op, GCancellable *cancellable) {
+        SeahorseLdapGSource *ldap_gsource;
+
+        GSource *source = g_source_new (&seahorse_ldap_gsource_funcs,
+                                sizeof (SeahorseLdapGSource));
+
+        ldap_gsource = (SeahorseLdapGSource *)gsource;
+        ldap_gsource.ldap = ldap;
+        ldap_gsource.ldap_op = ldap_op;
+
+        if (cancellable) {
+            ldap_gsource.cancellable = g_object_ref (cancellable);
+            ldap_gsource.cancelled_sig = g_cancellable_connect (cancellable,
+                                                                 G_CALLBACK (on_ldap_gsource_cancelled),
+                                                                 ldap_gsource, null);
+        }
+
+        return gsource;
+    }
+
+    static bool seahorse_ldap_source_propagate_error(int rc, GError **error) {
+        if (rc == LDAP_SUCCESS)
+            return false;
+
+        g_set_error(error, LDAP_ERROR_DOMAIN, rc, _("Couldn’t communicate with %s: %s"),
+                     this.key_server, ldap_err2string (rc));
+
+        return true;
+    }
+
+    typedef struct {
+        GCancellable *cancellable;
+        gchar *filter;
+        LDAP *ldap;
+        GcrSimpleCollection *results;
+    } source_search_closure;
+
+    static void source_search_free (gpointer data) {
+        source_search_closure *closure = data;
+        g_clear_object (&closure.cancellable);
+        g_clear_object (&closure.results);
+        g_free (closure.filter);
+        if (closure.ldap)
+            ldap_unbind_ext (closure.ldap, null, null);
+        g_free (closure);
+    }
+
+    static string PGP_ATTRIBUTES[] = {
+        "pgpcertid",
+        "pgpuserid",
+        "pgprevoked",
+        "pgpdisabled",
+        "pgpkeycreatetime",
+        "pgpkeyexpiretime"
+        "pgpkeysize",
+        "pgpkeytype",
+        null
+    };
+
+    public async bool search_async(string match, Gcr.SimpleCollection results, Cancellable cancellable) 
throws GLib.Error {
+        source_search_closure *closure;
+
+        string text = escape_ldap_value (match);
+        closure.filter = "(pgpuserid=*%s*)".printf(text);
+
+        Seahorse.Progress.prep_and_begin(closure.cancellable, res, null);
+
+        connect_async(cancellable, on_search_connect_completed, g_object_ref (res));
+    }
+
+    static void on_search_connect_completed() {
+        source_search_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        GError *error = null;
+
+        closure.ldap = connect_finish(result, &error);
+        if (error != null) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            g_object_unref (res);
+            return;
+        }
+
+        LDAPServerInfo sinfo = get_ldap_server_info(true);
+
+        debug("Searching Server ... base: %s, filter: %s",
+                 sinfo.base_dn, closure.filter);
+
+        int ldap_op;
+        int rc = ldap_search_ext (closure.ldap, sinfo.base_dn, LDAP_SCOPE_SUBTREE,
+                              closure.filter, (char **)PGP_ATTRIBUTES, 0,
+                              null, null, null, 0, &ldap_op);
+
+        if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+
+        } else {
+            GSource *gsource = seahorse_ldap_gsource_new (closure.ldap, ldap_op,
+                                                 closure.cancellable);
+            g_source_set_callback (gsource, on_search_search_completed, g_object_ref (res), g_object_unref);
+            g_source_attach (gsource, g_main_context_default ());
+            g_source_unref (gsource);
+        }
+    }
+
+    static bool on_search_search_completed(LDAPMessage *result) {
+        if (result.type != LDAP_RES_SEARCH_ENTRY && result.type != LDAP_RES_SEARCH_RESULT)
+            return false;
+
+        source_search_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        GError *error = null;
+
+        /* An LDAP entry */
+        if (result.type == LDAP_RES_SEARCH_ENTRY) {
+            debug ("Retrieved Key Entry");
+#ifdef WITH_DEBUG
+            dump_ldap_entry (closure.ldap, result);
+#endif
+
+            search_parse_key_from_ldap_entry(closure.results, closure.ldap, result);
+            return true; /* keep calling this callback */
+
+        /* All entries done */
+        } else {
+            int code; char *message;
+            int rc = ldap_parse_result (closure.ldap, result, &code, null, &message, null, null, 0);
+            g_return_val_if_fail (rc == LDAP_SUCCESS, false);
+
+            /* Error codes that we ignore */
+            switch (code) {
+            case LDAP_SIZELIMIT_EXCEEDED:
+                code = LDAP_SUCCESS;
+                break;
+            };
+
+            /* Failure */
+            if (code != LDAP_SUCCESS)
+                g_simple_async_result_set_error (res, LDAP_ERROR_DOMAIN, code, "%s", message);
+            else if (seahorse_ldap_source_propagate_error (self, code, &error))
+                g_simple_async_result_take_error (res, error);
+
+            Seahorse.Progress.end(closure.cancellable, res);
+            g_simple_async_result_complete (res);
+            return false;
+        }
+    }
+
+    /* Add a key to the key source from an LDAP entry */
+    static void search_parse_key_from_ldap_entry(Gcr.SimpleCollection results, LDAP *ldap, LDAPMessage *res) 
{
+        g_return_if_fail (ldap_msgtype (res) == LDAP_RES_SEARCH_ENTRY);
+
+        string fpr = get_string_attribute (ldap, res, "pgpcertid");
+        string uidstr = get_string_attribute (ldap, res, "pgpuserid");
+        bool revoked = get_boolean_attribute (ldap, res, "pgprevoked");
+        bool disabled = get_boolean_attribute (ldap, res, "pgpdisabled");
+        long int timestamp = get_date_attribute (ldap, res, "pgpkeycreatetime");
+        long int expires = get_date_attribute (ldap, res, "pgpkeyexpiretime");
+        string algo = get_algo_attribute (ldap, res, "pgpkeytype");
+        int length = get_int_attribute (ldap, res, "pgpkeysize");
+
+        if (fpr && uidstr) {
+            SeahorsePgpUid *uid;
+            GList *list;
+
+            /* Build up a subkey */
+            Pgp.SubKey subkey = new Pgp.SubKey();
+            subkey.keyid = fpr;
+            subkey.fingerprint = Pgp.SubKey.calc_fingerprint(fpr);
+            subkey.created = timestamp;
+            subkey.expires = expires;
+            subkey.algorithm = algo;
+            subkey.length = length;
+
+            Seahorse.Flags flags = Seahorse.Flags.EXPORTABLE;
+            if (revoked)
+                flags |= Seahorse.Flags.REVOKED;
+            if (disabled)
+                flags |= Seahorse.Flags.DISABLED;
+            subkey.flags = flags;
+
+            Pgp.SubKey key = new Pgp.SubKey();
+
+            /* Build up a uid */
+            uid = new Pgp.Uid(key, uidstr);
+            if (revoked)
+                uid.set_validity(Seahorse.Validity.REVOKED);
+
+            /* Now build them into a key */
+            list = g_list_prepend (null, uid);
+            key.uids = list;
+            list = g_list_prepend (null, subkey);
+            key.subkeys = list;
+            g_object_set (key,
+                          "object-flags", flags,
+                          "place", self,
+                          null);
+
+            key.realize();
+            results.add(key);
+        }
+    }
+
+    static bool search_finish(GAsyncResult *result, GError **error) {
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return false;
+
+        return true;
+    }
+
+    typedef struct {
+        GPtrArray *keydata;
+        gint current_index;
+        GCancellable *cancellable;
+        LDAP *ldap;
+    } source_import_closure;
+
+    static void source_import_free (gpointer data) {
+        source_import_closure *closure = data;
+        g_ptr_array_free (closure.keydata, true);
+        g_clear_object (&closure.cancellable);
+        if (closure.ldap)
+            ldap_unbind_ext (closure.ldap, null, null);
+        g_free (closure);
+    }
+
+    public async List<Key> import_async(InputStream input, Cancellable *cancellable) {
+        source_import_closure *closure;
+
+        uint current_index = -1;
+
+        string[] keydata = new string[];
+        for (;;) {
+            GString *buf = g_string_sized_new (2048);
+            uint len = Seahorse.Util.read_data_block(buf, input, "-----BEGIN PGP PUBLIC KEY BLOCK-----",
+                                                 "-----END PGP PUBLIC KEY BLOCK-----");
+            if (len > 0) {
+                keydata += buf.str;
+                Seahorse.Progress.prep(closure.cancellable, keydata, null);
+            } else {
+                break;
+            }
+        }
+
+        connect_async(cancellable, on_import_connect_completed, g_object_ref (res));
+    }
+
+    static void on_import_connect_completed() {
+        GError *error = null;
+
+        closure.ldap = connect_finish(result, &error);
+        if (error != null) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+        } else {
+            import_send_key();
+        }
+    }
+
+    static void import_send_key () {
+        char *values[2];
+        GSource *gsource;
+        GError *error = null;
+
+        if (closure.current_index >= 0) {
+            keydata = closure.keydata.pdata[closure.current_index];
+            Seahorse.Progress.end(closure.cancellable, keydata);
+        }
+
+        closure.current_index++;
+
+        /* All done, complete operation */
+        if (closure.current_index == (gint)closure.keydata.len) {
+            g_simple_async_result_complete (res);
+            return;
+        }
+
+        string keydata = closure.keydata[current_index];
+        Seahorse.Progress.begin(closure.cancellable, keydata);
+        values[0] = keydata;
+        values[1] = null;
+
+        LDAPServerInfo sinfo = get_ldap_server_info(true);
+        LDAPMod mod;
+        memset (&mod, 0, sizeof (mod));
+        mod.mod_op = LDAP_MOD_ADD;
+        mod.mod_type = sinfo.key_attr;
+        mod.mod_values = values;
+
+        LDAPMod *attrs[2];
+        attrs[0] = &mod;
+        attrs[1] = null;
+
+        string base = "pgpCertid=virtual,%s".printf(sinfo.base_dn);
+        int ldap_op;
+        int rc = ldap_add_ext (closure.ldap, base, attrs, null, null, &ldap_op);
+
+        if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
+            g_simple_async_result_complete (res);
+            return;
+        }
+
+        gsource = seahorse_ldap_gsource_new (closure.ldap, ldap_op, closure.cancellable);
+        g_source_set_callback (gsource, on_import_add_completed);
+        g_source_attach (gsource, g_main_context_default ());
+        g_source_unref (gsource);
+    }
+
+    /* Called when results come in for a key send */
+    static bool on_import_add_completed (LDAPMessage *result) {
+        if (result.type != LDAP_RES_ADD)
+            return false;
+
+        GError *error = null;
+
+        int code; string message;
+        int rc = ldap_parse_result(closure.ldap, result, &code, null, &message, null, null, 0);
+        g_return_val_if_fail (rc == LDAP_SUCCESS, false);
+
+        /* TODO: Somehow communicate this to the user */
+        if (code == LDAP_ALREADY_EXISTS)
+            code = LDAP_SUCCESS;
+
+        if (seahorse_ldap_source_propagate_error (self, code, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            return false; /* don't call for this source again */
+        }
+
+        import_send_key();
+        return false; /* don't call for this source again */
+    }
+
+    static GList * import_finish() {
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return null;
+
+        /* We don't know the keys that were imported, since this is a server */
+        return null;
+    }
+
+    private struct ExportClosure {
+        GPtrArray *fingerprints;
+        gint current_index;
+        GString *data;
+        GCancellable *cancellable;
+        LDAP *ldap;
+    }
+
+    static void export_closure_free (gpointer data) {
+        ExportClosure *closure = data;
+        g_ptr_array_free (closure.fingerprints, true);
+        if (closure.data)
+            g_string_free (closure.data, true);
+        g_clear_object (&closure.cancellable);
+        if (closure.ldap)
+            ldap_unbind_ext (closure.ldap, null, null);
+        g_free (closure);
+    }
+
+    public async uint8[] export_async(string[] keyids, Cancellable cancellable) throws GLib.Error {
+        StringBuilder data = g_string_sized_new (1024);
+
+        string[[ fingerprints = new string[keyids.length];
+        foreach (string keyid in keyids) {
+            fingerprints += keyid;
+            Seahorse.Progress.prep(closure.cancellable, keyid, null);
+        }
+
+        int current_index = -1;
+
+        yield connect_async(cancellable);
+
+        closure.ldap = connect_finish(result, &error);
+        if (error != null) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+        } else {
+            export_retrieve_key();
+        }
+    }
+
+    static void export_retrieve_key() {
+        GSource *gsource;
+        GError *error = null;
+        int ldap_op;
+
+        string fingerprint;
+        if (current_index > 0) {
+            fingerprint = closure.fingerprints.pdata[closure.current_index];
+            Seahorse.Progress.end(closure.cancellable, fingerprint);
+        }
+
+        closure.current_index++;
+
+        /* All done, complete operation */
+        if (closure.current_index == (gint)closure.fingerprints.len) {
+            g_simple_async_result_complete (res);
+            return;
+        }
+
+        fingerprint = closure.fingerprints.pdata[closure.current_index];
+        Seahorse.Progress.begin(closure.cancellable, fingerprint);
+        int length = fingerprint.length;
+        if (length > 16)
+            fingerprint += (length - 16);
+
+        string filter = "(pgpcertid=%.16s)".printf(fingerprint);
+        LDAPServerInfo *sinfo = get_ldap_server_info (self, true);
+
+        char *attrs[2];
+        attrs[0] = sinfo.key_attr;
+        attrs[1] = null;
+
+        int rc = ldap_search_ext(closure.ldap, sinfo.base_dn, LDAP_SCOPE_SUBTREE,
+                                 filter, attrs, 0, null, null, null, 0, &ldap_op);
+
+        if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete (res);
+            return;
+        }
+
+        gsource = seahorse_ldap_gsource_new (closure.ldap, ldap_op, closure.cancellable);
+        g_source_set_callback (gsource, (GSourceFunc)on_export_search_completed, g_object_ref (res), 
g_object_unref);
+        g_source_attach (gsource, g_main_context_default ());
+    }
+
+    static bool on_export_search_completed (LDAPMessage *result) {
+        GError *error = null;
+        int type;
+
+        type = ldap_msgtype (result);
+        g_return_val_if_fail (type == LDAP_RES_SEARCH_ENTRY || type == LDAP_RES_SEARCH_RESULT, false);
+        LDAPServerInfo sinfo = get_ldap_server_info(true);
+
+        /* An LDAP Entry */
+        if (type == LDAP_RES_SEARCH_ENTRY) {
+            debug("Server Info Result");
+#ifdef WITH_DEBUG
+            dump_ldap_entry (closure.ldap, result);
+#endif
+
+            string? key = get_string_attribute (closure.ldap, result, sinfo.key_attr);
+
+            if (key == null) {
+                warning("key server missing pgp key data");
+                seahorse_ldap_source_propagate_error(LDAP_NO_SUCH_OBJECT, &error);
+                g_simple_async_result_take_error (res, error);
+                g_simple_async_result_complete (res);
+                return false;
+            }
+
+            data.append( key);
+            data.append_c('\n');
+
+            return true;
+
+        /* No more entries, result */
+        } else {
+            int code; string message;
+            int rc = ldap_parse_result (closure.ldap, result, &code, null, &message, null, null, 0);
+            g_return_val_if_fail (rc == LDAP_SUCCESS, false);
+
+            if (seahorse_ldap_source_propagate_error (self, code, &error)) {
+                g_simple_async_result_take_error (res, error);
+                g_simple_async_result_complete (res);
+                return false;
+            }
+
+            /* Process more keys if possible */
+            export_retrieve_key(res);
+            return false;
+        }
+    }
+
+    static gpointer export_finish(GAsyncResult *result, gsize *size, GError **error) {
+        ExportClosure *closure;
+        gpointer output;
+
+        g_return_val_if_fail (size != null, null);
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (source),
+                              seahorse_ldap_source_export_async), null);
+
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return null;
+
+        closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+        *size = closure.data.len;
+        output = g_string_free (closure.data, false);
+        closure.data = null;
+        return output;
+    }
+
+    typedef struct {
+        GCancellable *cancellable;
+        LDAP *ldap;
+    } source_connect_closure;
+
+    static void source_connect_free (gpointer data) {
+        source_connect_closure *closure = data;
+        g_clear_object (&closure.cancellable);
+        if (closure.ldap)
+            ldap_unbind_ext (closure.ldap, null, null);
+        g_free (closure);
+    }
+
+    static string SERVER_ATTRIBUTES[] = {
+        "basekeyspacedn",
+        "pgpbasekeyspacedn",
+        "version",
+        null
+    };
+
+    private async void connect_async(GCancellable *cancellable) {
+        if (this.key_server == null || this.key_server == "")
+            return;
+
+        source_connect_closure *closure;
+
+        string pos;
+        if ((pos = strchr (this.key_server, ':')) != null)
+            *pos = 0;
+
+        Seahorse.Progress.prep_and_begin(cancellable, res, null);
+
+        /* If we have libsoup, try and resolve asynchronously */
+#ifdef WITH_SOUP
+        Soup.Address address = new Soup.Address(this.key_server, LDAP_PORT);
+        Seahorse.Progress.update(cancellable, res, _("Resolving server address: %s"), this.key_server);
+
+        address.resolve_async(null, cancellable, (addr, status) => {
+            Seahorse.Progress.update(cancellable, res, _("Connecting to: %s"), this.key_server);
+
+            /* DNS failed */
+            if (status != Soup.Status.OK) {
+                g_simple_async_result_set_error (res, SEAHORSE_ERROR, -1, _("Couldn’t resolve address: %s"), 
address.name);
+                g_simple_async_result_complete_in_idle (res);
+
+            /* Yay resolved */
+            } else {
+                once_resolved_start_connect (self, res, soup_address_get_physical (address));
+            }
+        });
+#else /* !WITH_SOUP */
+        once_resolved_start_connect();
+#endif
+    }
+
+    static void once_resolved_start_connect() {
+        // XXX we don't actually use this.key_server, but this.key_server *without port*
+
+        if (this.key_server == null || this.key_server == "")
+            return;
+
+        source_connect_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        GError *error = null;
+
+        /* Now that we've resolved our address, connect via IP */
+        string text;
+        int port = LDAP_PORT;
+        if ((text = strchr (this.key_server, ':')) != null) {
+            *text = 0;
+            text++;
+            port = atoi (text);
+            if (port <= 0 || port >= G_MAXUINT16) {
+                warning("invalid port number: %s (using default)", text);
+                port = LDAP_PORT;
+            }
+        }
+
+        string url = "ldap://%s:%u".printf(this.key_server, port);
+        int rc = ldap_initialize(&closure.ldap, url);
+
+        if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+
+        /* Start the bind operation */
+        } else {
+            struct berval cred;
+            cred.bv_val = "";
+            cred.bv_len = 0;
+            int ldap_op;
+            rc = ldap_sasl_bind(closure.ldap, null, LDAP_SASL_SIMPLE, &cred, null, null, &ldap_op);
+            if (seahorse_ldap_source_propagate_error (self, rc, &error)) {
+                g_simple_async_result_take_error (res, error);
+                g_simple_async_result_complete_in_idle (res);
+
+            } else {
+                GSource *gsource = seahorse_ldap_gsource_new (closure.ldap, ldap_op, closure.cancellable);
+                g_source_set_callback (gsource, on_connect_bind_completed, g_object_ref (res), 
g_object_unref);
+                g_source_attach (gsource, g_main_context_default ());
+            }
+        }
+    }
+
+    static bool on_connect_bind_completed (LDAPMessage result) {
+        if (result.type != LDAP_RES_BIND)
+            return false;
+
+        source_connect_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        GError *error = null;
+
+        /* The result of the bind operation */
+        string message; int code;
+        int rc = ldap_parse_result(closure.ldap, result, &code, null, &message, null, null, 0);
+        g_return_val_if_fail (rc == LDAP_SUCCESS, false);
+
+        if (seahorse_ldap_source_propagate_error(rc, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+            return false; /* don't call this callback again */
+        }
+
+        /* Check if we need server info */
+        LDAPServerInfo *sinfo = get_ldap_server_info(false);
+        if (sinfo != null) {
+            g_simple_async_result_complete_in_idle (res);
+            Seahorse.Progress.end(closure.cancellable, res);
+            return false; /* don't call this callback again */
+        }
+
+        /* Retrieve the server info */
+        int ldap_op;
+        rc = ldap_search_ext (closure.ldap, "cn=PGPServerInfo", LDAP_SCOPE_BASE,
+                              "(objectclass=*)", (char **)SERVER_ATTRIBUTES, 0,
+                              null, null, null, 0, &ldap_op);
+
+        if (seahorse_ldap_source_propagate_error(rc, &error)) {
+            g_simple_async_result_take_error (res, error);
+            g_simple_async_result_complete_in_idle (res);
+            return false; /* don't call this callback again */
+
+        } else {
+            GSource *gsource = seahorse_ldap_gsource_new (closure.ldap, ldap_op, closure.cancellable);
+            g_source_set_callback (gsource, on_connect_server_info_completed, g_object_ref (res), 
g_object_unref);
+            g_source_attach (gsource, g_main_context_default ());
+        }
+
+        return false; /* don't call this callback again */
+    }
+
+    static bool on_connect_server_info_completed (LDAPMessage result) {
+        if (result.type != LDAP_RES_SEARCH_ENTRY && result.type != LDAP_RES_SEARCH_RESULT)
+            return false;
+
+        source_connect_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        // If we have results then fill in the server info
+        if (result.type == LDAP_RES_SEARCH_ENTRY) {
+
+            debug("Server Info Result");
+#ifdef WITH_DEBUG
+            dump_ldap_entry (closure.ldap, result);
+#endif
+
+            /* NOTE: When adding attributes here make sure to add them to kServerAttributes */
+            LDAPServerInfo sinfo = g_new0 (LDAPServerInfo, 1);
+            sinfo.version = get_int_attribute (closure.ldap, result, "version");
+            sinfo.base_dn = get_string_attribute (closure.ldap, result, "basekeyspacedn");
+            if (!sinfo.base_dn)
+                sinfo.base_dn = get_string_attribute (closure.ldap, result, "pgpbasekeyspacedn");
+            sinfo.key_attr = (sinfo.version > 1)? "pgpkeyv2" : "pgpkey";
+            set_data_full("server-info", sinfo, free_ldap_server_info);
+
+            return true; /* callback again */
+
+        } else {
+            string message;
+            int code;
+            int rc = ldap_parse_result (closure.ldap, result, out code, null, out message, null, null, 0);
+            g_return_val_if_fail (rc == LDAP_SUCCESS, false);
+
+            if (code != LDAP_SUCCESS)
+                warning("operation to get LDAP server info failed: %s", message);
+
+            g_simple_async_result_complete_in_idle (res);
+            Seahorse.Progress.end(closure.cancellable, res);
+            return false; /* don't callback again */
+        }
+    }
+
+    static LDAP* seahorse_ldap_source_connect_finish(GError **error) {
+        source_connect_closure *closure;
+
+        if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+            return null;
+
+        LDAP *ldap = closure.ldap;
+        closure.ldap = null;
+        return ldap;
+    }
+}
diff --git a/pgp/meson.build b/pgp/meson.build
index 53814f4..54af813 100644
--- a/pgp/meson.build
+++ b/pgp/meson.build
@@ -8,41 +8,49 @@ pgp_sources = [
   'seahorse-gpgme-expires.c',
   'seahorse-gpgme-exporter.c',
   'seahorse-gpgme-generate.c',
-  'seahorse-gpgme-key.c',
+  'gpgme-key.vala',
   'seahorse-gpgme-key-deleter.c',
   'seahorse-gpgme-key-op.c',
   'seahorse-gpgme-keyring.c',
-  'seahorse-gpgme-photo.c',
+  'gpgme-photo.vala',
   'seahorse-gpgme-photos.c',
   'seahorse-gpgme-revoke.c',
   'seahorse-gpgme-secret-deleter.c',
   'seahorse-gpgme-sign.c',
-  'seahorse-gpgme-subkey.c',
-  'seahorse-gpgme-uid.c',
+  'gpgme-subkey.vala',
+  'gpgme-uid.vala',
   'seahorse-gpg-op.c',
   'seahorse-gpg-options.c',
   'seahorse-pgp.c',
   'seahorse-pgp-actions.c',
   'seahorse-pgp-backend.c',
-  'seahorse-pgp-key.c',
+  'pgp-key.vala',
   'seahorse-pgp-key-properties.c',
   'seahorse-pgp-keysets.c',
-  'seahorse-pgp-photo.c',
-  'seahorse-pgp-signature.c',
-  'seahorse-pgp-subkey.c',
-  'seahorse-pgp-uid.c',
+  'pgp-photo.vala',
+  'pgp-signature.vala',
+  'pgp-subkey.vala',
+  'pgp-uid.vala',
   'seahorse-signer.c',
   'seahorse-transfer.c',
   'seahorse-unknown.c',
   'seahorse-unknown-source.c',
 ]
 
+# Also use our custom VAPI's
+gpg_vapis = [
+  valac.find_library('gpgme', dirs: join_paths(meson.source_root(), 'vapi')),
+  valac.find_library('gpg-error', dirs: join_paths(meson.source_root(), 'vapi')),
+]
+
 pgp_dependencies = [
   glib_deps,
   gcr,
   gpgme,
+  gtk,
   config,
   common_dep,
+  gpg_vapis,
 ]
 
 pgp_c_flags = [
@@ -74,7 +82,7 @@ endif
 if with_key_sharing
   pgp_dependencies += [
     avahi_client,
-    avahi_glib,
+    avahi_gobject,
   ]
 endif
 
diff --git a/pgp/pgp-actions.vala b/pgp/pgp-actions.vala
new file mode 100644
index 0000000..3088cc1
--- /dev/null
+++ b/pgp/pgp-actions.vala
@@ -0,0 +1,130 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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.Pgp.BackendActions : Seahorse.Actions {
+
+    private const Gtk.ActionEntry FIND_ACTIONS[] = {
+        { "remote-find", Gtk.Stock.FIND, _("_Find Remote Keys…"), "",
+              _("Search for keys on a key server"), on_remote_find },
+    };
+
+    private const Gtk.ActionEntry SYNC_ACTIONS[] = {
+        { "remote-sync", Gtk.Stock.REFRESH, _("_Sync and Publish Keys…"), "",
+              _("Publish and/or synchronize our keys with those online."), on_remote_sync }
+    };
+
+#if WITH_KEYSERVER
+
+    public const string BACKEND_DEFINITION = """
+      <ui>
+        <menubar>
+          <placeholder name="RemoteMenu">
+            <menu name="Remote" action="remote-menu">
+              <menuitem action="remote-find"/>
+              <menuitem action="remote-sync"/>
+            </menu>
+          </placeholder>
+        </menubar>
+      </ui>""";
+
+    private void on_remote_find(Gtk.Action action) {
+        seahorse_keyserver_search_show (seahorse_action_get_window (action));
+    }
+
+    private void on_remote_sync (Gtk.Action action) {
+        SeahorseGpgmeKeyring *keyring;
+        SeahorseCatalog *catalog;
+        GList *objects = null;
+        GList *keys = null;
+        GList *l;
+
+        catalog = seahorse_actions_get_catalog (actions);
+        if (catalog != null) {
+            objects = seahorse_catalog_get_selected_objects (catalog);
+            for (l = objects; l != null; l = g_list_next (l)) {
+                if (SEAHORSE_IS_PGP_KEY (l->data))
+                    keys = g_list_prepend (keys, l->data);
+            }
+            g_list_free (objects);
+        }
+        g_object_unref (catalog);
+
+        if (keys == null) {
+            keyring = seahorse_pgp_backend_get_default_keyring (null);
+            keys = gcr_collection_get_objects (GCR_COLLECTION (keyring));
+        }
+
+        seahorse_keyserver_sync_show (keys, seahorse_action_get_window (action));
+        g_list_free (keys);
+    }
+
+
+#endif
+
+    construct {
+        set_translation_domain(Config.GETTEXT_PACKAGE);
+        add_actions(FIND_ACTIONS, this);
+        add_actions(SYNC_ACTIONS, this);
+        register_definition(BACKEND_DEFINITION);
+    }
+
+    private static GtkActionGroup? actions = null;
+    public static Gtk.ActionGroup instance() {
+        if (actions == null) {
+            actions = g_object_new (SEAHORSE_TYPE_PGP_BACKEND_ACTIONS,
+                                    "name", "pgp-backend",
+                                    null);
+            g_object_add_weak_pointer (G_OBJECT (actions),
+                                       (gpointer *)&actions);
+        }
+
+        return actions;
+    }
+}
+
+
+public class Seahorse.Gpgme.KeyActions : Seahorse.Actions {
+
+    private void
+    seahorse_gpgme_key_actions_init (SeahorseGpgmeKeyActions *self)
+    {
+#if WITH_KEYSERVER
+        GtkActionGroup *actions = GTK_ACTION_GROUP (self);
+        gtk_action_group_set_translation_domain (actions, GETTEXT_PACKAGE);
+        gtk_action_group_add_actions (actions, SYNC_ACTIONS,
+                                      G_N_ELEMENTS (SYNC_ACTIONS), null);
+#endif
+    }
+
+    private static GtkActionGroup? actions = null;
+    public static GtkActionGroup instance() {
+        if (actions == null) {
+            actions = g_object_new (SEAHORSE_TYPE_GPGME_KEY_ACTIONS,
+                                    "name", "gpgme-key",
+                                    null);
+            g_object_add_weak_pointer (G_OBJECT (actions),
+                                       (gpointer *)&actions);
+        }
+
+        return actions;
+    }
+}
diff --git a/pgp/pgp-backend.vala b/pgp/pgp-backend.vala
new file mode 100644
index 0000000..72e8b14
--- /dev/null
+++ b/pgp/pgp-backend.vala
@@ -0,0 +1,345 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.Pgp.Backend : GLib.Object, Gcr.Collection, Seahorse.Backend {
+    private Gpgme.Keyring keyring;
+    private Discovery discovery;
+    private UnknownSource unknown;
+    private HashTable<string, ServerSource> remotes;
+
+    public string name { get { return SEAHORSE_PGP_NAME; } }
+    public string label { get { return _("PGP Keys"); } }
+    public string description { get { return _("PGP keys are for encrypting email or files"); } }
+    public Gtk.ActionGroup? actions { owned get { return Actions.instance; } }
+
+    private bool _loaded;
+    public bool loaded { get { return _loaded; } }
+
+    public static Backend pgp_backend = null;
+
+    construct {
+        this.remotes = new HashTable<string, ServerSource>();
+    }
+
+    public Backend() {
+        Gpgme.Generate.register();
+
+        this.keyring = new Seahorse.GpgME.Keyring();
+        this.keyring.load.begin (null, (obj, res) => {
+            try {
+                this.keyring.load.end(res);
+            } catch (Error e) {
+                warning ("Failed to initialize PGP backend: %s", error.message);
+            }
+            this.loaded = true;
+            this.notify_property("loaded");
+        });
+
+        this.discovery = new Seahorse.Discovery();
+        this.unknown = new Seahorse.UnknownSource();
+
+#if WITH_KEYSERVER
+        Application.pgp_settings().changed["keyservers"].connect(on_settings_keyservers_changed);
+
+        // Initial loading
+        on_settings_keyservers_changed(Application.pgp_settings(), "keyservers", self);
+#endif
+    }
+
+#if WITH_KEYSERVER
+    private void on_settings_keyservers_changed(GLib.Settings settings, string key) {
+        GHashTableIter iter;
+
+        GenericSet<string> check = new GenericSet<string>();
+        this.remotes.foreach((uri, source) => {
+            check.insert(uri);
+        });
+
+        // Load and strip names from keyserver list
+        foreach (string uri in Seahorse.Servers.get_uris()) {
+            // If we don't have a keysource then add it
+            if (this.remotes.lookup(uri) == null) {
+                ServerSource source = new ServerSource(uri);
+                if (source != null)
+                    add_remote(uri, source);
+            }
+
+            // Mark this one as present
+            check.remove(uri);
+        }
+
+        // Now remove any extras
+        check.foreach((uri) => {
+            remove_remote(uri);
+        });
+    }
+#endif
+
+    public uint get_length() {
+        return 1;
+    }
+
+    public List<GLib.Object> get_objects() {
+        List<Keyring> list = new List<GLib.Object>();
+        list.append(this.keyring);
+        return list;
+    }
+
+    public bool contains(GLib.Object object) {
+        Keyring? kr = object as Keyring;
+        return (kr != null) && (kr == this.keyring);
+    }
+
+    public Seahorse.Place? lookup_place(string uri) {
+        if (uri == "gnupg://")
+            return Backend.get_default_keyring();
+
+        return null;
+    }
+
+    public static Backend get() {
+        return pgp_backend;
+    }
+
+    public void initialize() {
+        register();
+        gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, null, null);
+    }
+
+    public GpgME.Keyring get_default_keyring() {
+        return this.keyring;
+    }
+
+    public Pgp.Key? get_default_key() {
+        Pgp.Key? key = null;
+
+        GLib.Settings? settings = Application.pgp_settings();
+        if (settings != null) {
+            string val = settings.get_string("default-key");
+            if (val != null && val[0]) {
+                string keyid;
+                if (val.has_prefix("openpgp:"))
+                    keyid = val + strlen ("openpgp:");
+                else
+                    keyid = val;
+                key = this.keyring.lookup(keyid);
+            }
+        }
+
+        return key;
+    }
+
+    public Seahorse.Discovery? get_discovery() {
+        return this.discovery;
+    }
+
+    public Seahorse.ServerSource lookup_remote(string uri) {
+        return this.remotes.lookup(uri);
+    }
+
+    public void add_remote(string uri, Seahorse.ServerSource source) {
+        if (uri == null || this.remotes.lookup(uri) != null || source == null)
+            return;
+
+        this.remotes.insert(uri, source);
+    }
+
+    public void remove_remote(string uri) {
+       if (uri == null)
+           return;
+
+        this.remotes.remove(uri);
+    }
+
+    struct search_remote_closure {
+        Cancellable cancellable;
+        gint num_searches;
+        GList *objects;
+    }
+
+    public async void search_remote_async(string search,
+                                          Gcr.SimpleCollection results,
+                                          Cancellable cancellable) {
+        search_remote_closure *closure;
+        GSimpleAsyncResult *res;
+        SeahorseServerSource *source;
+
+        // Get a list of all selected key servers
+        GenericSet<string> servers = new GenericSet<string>();
+        string[] names = Seahorse.Application.settings(null).get_strv("last-search-servers");
+        foreach (string name in names)
+            servers.add(name);
+
+        this.remotes.foreach((uri, source) => {
+            if (servers.lookup(source.uri) == null)
+                return;
+
+            Seahorse.Progress.prep_and_begin(closure.cancellable, GINT_TO_POINTER (closure.num_searches), 
null);
+            source.search_async(search, results, closure.cancellable, on_source_search_ready, g_object_ref 
(res));
+            closure.num_searches++;
+        });
+
+        if (closure.num_searches == 0)
+            g_simple_async_result_complete_in_idle (res);
+    }
+
+    private void on_source_search_ready(GObject *source, GAsyncResult *result) {
+        GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+        search_remote_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+        GError *error = null;
+
+        g_return_if_fail (closure.num_searches > 0);
+
+        if (!seahorse_server_source_search_finish (SEAHORSE_SERVER_SOURCE (source),
+                                                   result, &error))
+            g_simple_async_result_take_error (res, error);
+
+        closure.num_searches--;
+        Seahorse.Progress.end(closure.cancellable, GINT_TO_POINTER (closure.num_searches));
+
+        if (closure.num_searches == 0)
+            g_simple_async_result_complete (res);
+    }
+
+    bool seahorse_pgp_backend_search_remote_finish(AsyncResult *result, GError **error) {
+        g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
+                              seahorse_pgp_backend_search_remote_async), false);
+
+        res = G_SIMPLE_ASYNC_RESULT (result);
+
+        if (g_simple_async_result_propagate_error (res, error))
+            return false;
+
+        return true;
+    }
+
+    struct transfer_closure {
+        Cancellable cancellable;
+        int num_transfers;
+    }
+
+    public async void transfer_async(List keys, SeahorsePlace to, Cancellable cancellable) throws GLib.Error 
{
+        SeahorseObject *object;
+        GSimpleAsyncResult *res;
+        SeahorsePlace *from;
+        GList *next;
+
+        if (cancellable)
+            closure.cancellable = g_object_ref (cancellable);
+
+        keys = g_list_copy (keys);
+
+        // Sort by key place
+        keys = Util.objects_sort_by_place(keys);
+
+        foreach (Key key in keys) {
+            // break off one set (same keysource)
+            next = Util.objects_splice_by_place(keys);
+
+            g_assert (SEAHORSE_IS_OBJECT (keys.data));
+            object = SEAHORSE_OBJECT (keys.data);
+
+            // Export from this key place
+            from = seahorse_object_get_place (object);
+            g_return_if_fail (SEAHORSE_IS_PLACE (from));
+
+            if (from != to) {
+                // Start a new transfer operation between the two places
+                Seahorse.Progress.prep_and_begin(cancellable, GINT_TO_POINTER (closure.num_transfers), null);
+                Transfer.transfer_keys_async.begin(from, to, keys, cancellable, (obj, res) => {
+                    if (num_transfer <= 0)
+                        return;
+                    GError *error = null;
+
+                    try {
+                        Transfer.transfer_keys_async.end(res);
+                    } catch (Error e) {
+                        g_simple_async_result_take_error (res, error);
+                    }
+
+                    closure.num_transfers--;
+                    Seahorse.Progress.end(closure.cancellable, GINT_TO_POINTER (closure.num_transfers));
+                });
+                closure.num_transfers++;
+            }
+
+            keys = next;
+        }
+    }
+
+    public List<Key> discover_keys(string[] keyids, Cancellable cancellable) {
+        // Our return value
+        List<Key> robjects = new List<Key>();
+
+        // Check all the ids
+        string[] todiscover = new string[keyids.length];
+        foreach (string keyid in keyids) {
+            // Do we know about this object?
+            Key? key = this.keyring.lookup(keyid);
+
+            // No such object anywhere, discover it
+            if (key == null) {
+                todiscover.add(keyid);
+                continue;
+            }
+
+            robjects.prepend(key);
+        }
+
+        if (todiscover.length > 0) {
+            // Start a discover process on all todiscover
+            if (Config.WITH_KEYSERVER && Seahorse.Application.settings().get_boolean("server-auto-retrieve"))
+                retrieve_async(todiscover, this.keyring, cancellable, null, null);
+
+            // Add unknown objects for all these
+            // XXX watch out, you're using keyid twice
+            foreach (string keyid in todiscover) {
+                SeahorseObject object = this.unknown.add_object(keyid[i], cancellable);
+                robjects = robjects.prepend(object);
+            }
+        }
+
+        return robjects;
+    }
+
+    public async void retrieve_async(string[] keyids, Seahorse.Place to, Cancellable cancellable) throws 
GLib.Error {
+        int num_transfers = 0;
+        this.remotes.foreach((uri, place) => {
+            // Start a new transfer operation between the two places
+            Seahorse.Progress.prep_and_begin(cancellable, num_transfers, null);
+            Seahorse.Transfer.transfer_keyids_async.begin(place, to, keyids, cancellable, (obj, res) => {
+                if (num_transfer <= 0)
+                    return;
+                GError *error = null;
+
+                try {
+                    Transfer.transfer_keys_async.end(res);
+                } catch (Error e) {
+                    g_simple_async_result_take_error (res, error);
+                }
+
+                closure.num_transfers--;
+                Seahorse.Progress.end(closure.cancellable, GINT_TO_POINTER (closure.num_transfers));
+            });
+            num_transfers++;
+        });
+    }
+}
diff --git a/pgp/pgp-key-properties.vala b/pgp/pgp-key-properties.vala
new file mode 100644
index 0000000..a397790
--- /dev/null
+++ b/pgp/pgp-key-properties.vala
@@ -0,0 +1,1197 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2003 Jacob Perkins
+ * Copyright (C) 2005 Jim Pharis
+ * Copyright (C) 2005-2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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/>.
+ */
+
+// XXX make a subclass for public/private?
+// XXX there was a printf_gtkbuilder_widget() here, I removed it but did not use .text first
+public class Seahorse.Pgp.KeyProperties : Gtk.Dialog {
+
+    private Key key;
+
+    private GpgME.Photo? current_photo_id; // XXX Dunno if this is only in private or public
+
+    private Gtk.Box expired_area;
+    private Gtk.Label expired_message;
+    private Gtk.Box revoked_area;
+    // Owner shizzle
+    private Gtk.Label owner_name_label;
+    private Gtk.Label owner_email_label;
+    private Gtk.Label owner_comment_label;
+    private Gtk.Label owner_keyid_label;
+    private Gtk.Button owner_photo_previous_button;
+    private Gtk.Button owner_photo_next_button;
+    // Details shizzle
+    private Gtk.Label details_id_label;
+    private Gtk.Label details_algo_label;
+    private Gtk.Label details_strength_label;
+    private Gtk.Label details_fingerprint_label;
+    private Gtk.Label details_created_label;
+    private Gtk.Label details_expires_label;
+    private Gtk.ComboBox details_trust_combobox;
+    private Gtk.TreeView details_subkey_tree;
+
+    // ONLY PUBLIC KEY
+    private Gtk.Box? trust_page;
+    private Gtk.Expander? uids_area;
+    private Gtk.TreeView? signatures_tree;
+    private Gtk.TreeView? owner_userid_tree;
+    private Gtk.CheckButton? trust_marginal_check;
+    private Gtk.Label? trust_sign_label;
+    private Gtk.Label? trust_revoke_label;
+    private Gtk.Label? manual_trust_area;
+    private Gtk.Box? manage_trust_area;
+    private Gtk.Box? sign_area;
+    private Gtk.Box? revoke_area;
+    private Gtk.Image? sign_image;
+    private Gtk.CheckButton? signatures_toggle;
+
+    // ONLY PRIVATE KEY
+    private Gtk.Button? passphrase_button;
+    private Gtk.Frame? owner_photo_frame;
+    private Gtk.Button? owner_photo_add_button;
+    private Gtk.Button? owner_photo_delete_button;
+    private Gtk.Button? owner_photo_primary_button;
+    private Gtk.TreeView? names_tree;
+    private Gtk.Button? names_primary_button;
+    private Gtk.Button? names_delete_button;
+    private Gtk.Button? names_sign_button;
+    private Gtk.Button? names_revoke_button;
+    private Gtk.Button? details_export_button;
+    private Gtk.Button? details_add_button;
+    private Gtk.Button? details_date_button;
+    private Gtk.Button? details_revoke_button;
+    private Gtk.Button? details_delete_button;
+
+    // NOT FOUND?
+    private Gtk.Image? image_good1;
+    private Gtk.Image? image_good2;
+
+
+    public KeyProperties(Key key, Gtk.Window parent) {
+        // XXX Copypasta from the SSH key properties
+        /* GLib.Object(border_width: 5, */
+        /*             title: _("Key Properties"), */
+        /*             resizable: false, */
+        /*             transient_for: parent); */
+        this.key = key;
+        // This causes the key source to get any specific info about the key
+        if (pkey is GpgME.Key) {
+            key.refresh();
+            key.ensure_signatures();
+        }
+
+        load_ui();
+
+        seahorse_bind_objects (null, pkey, (SeahorseTransfer)key_notify, swidget);
+    }
+
+    // FIXME: normally we would do this using GtkTemplate, but make is abdandoning me :(
+    private void load_ui() {
+        string basename = (this.key.usage == Seahorse.Usage.PUBLIC_KEY)?
+                            "pgp-public-key-properties" : "pgp-private-key-properties";
+        Gtk.Builder builder = new Gtk.Builder();
+        try {
+            string path = "/org/gnome/Seahorse/%s-key-properties.ui".printf(basename);
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.critical("%s", err.message);
+        }
+        Gtk.Container content = (Gtk.Container) builder.get_object(basename);
+        ((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.expired_area = (Gtk.Box) builder.get_object("expired-area");
+        this.revoked_area = (Gtk.Box) builder.get_object("revoked-area");
+        this.owner_name_label = (Gtk.Label) builder.get_object("owner-name-label");
+        this.owner_email_label = (Gtk.Label) builder.get_object("owner-email-label");
+        this.owner_comment_label = (Gtk.Label) builder.get_object("owner-comment-label");
+        this.owner_keyid_label = (Gtk.Label) builder.get_object("owner-keyid-label");
+        this.owner_photo_previous_button = (Gtk.Button) builder.get_object("owner-photo-previous-button");
+        this.owner_photo_next_button = (Gtk.Button) builder.get_object("owner-photo-next-button");
+
+        if (this.key.usage == Seahorse.Usage.PUBLIC_KEY) { // ONLY PUBLIC KEY
+        } else {
+        }
+
+        // Signals
+        /* this.comment_entry.activate.connect(on_ssh_comment_activate); */
+    }
+
+    private gpointer get_selected_object(Seahorse.Widget swidget, string objectid, uint column) {
+        Gtk.TreeView tree_view = swidget.get_widget(objectid);
+        Gtk.TreeModel model = tree_view.get_model();
+        Gtk.TreeSelection selection = tree_view.get_selection ();
+        assert(selection.get_mode () == GTK_SELECTION_SINGLE);
+
+        List<Gtk.TreePath> rows = selection.get_selected_rows(null);
+
+        if (rows.length() > 0) {
+            Gtk.TreeIter iter;
+            model.get_iter(&iter, rows.data);
+            gpointer object = null;
+            model.get(&iter, column, &object, -1);
+        }
+
+        return object;
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_signature_row_activated(Gtk.TreeView treeview, Gtk.TreePath path, Gtk.TreeView? 
Column *arg2) {
+        Gtk.TreeModel model = treeview.get_model();
+
+        if (GTK_IS_TREE_MODEL_FILTER (model))
+            model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
+
+        Gtk.TreeIter iter;
+        if (model.get_iter(out iter, path))
+            return;
+
+        GLib.Object object = seahorse_object_model_get_row_key (SEAHORSE_OBJECT_MODEL (model), &iter);
+        if (object != null && SEAHORSE_IS_PGP_KEY (object)) {
+            seahorse_pgp_key_properties_show (SEAHORSE_PGP_KEY (object), 
swidget.get_toplevel().get_parent());
+        }
+    }
+
+    private void unique_strings (string[] keyids) {
+        g_ptr_array_sort (keyids, (GCompareFunc)g_ascii_strcasecmp);
+        for (uint i = 0; i + 1 < keyids.len; ) {
+            if (g_ascii_strcasecmp (keyids.pdata[i], keyids.pdata[i + 1]) == 0)
+                g_ptr_array_remove_index (keyids, i);
+            else
+                i++;
+        }
+    }
+
+    /* -----------------------------------------------------------------------------
+     * NAMES PAGE (PRIVATE KEYS)
+     */
+    private enum UidSigColumn {
+        OBJECT,
+        ICON,
+        NAME,
+        KEYID,
+        N_COLUMNS;
+    }
+
+    static Type uidsig_columns[] = {
+        typeof(GLib.Object),  // index
+        typeof(Icon),         // icon
+        typeof(string),       // name
+        typeof(string)        // keyid
+    };
+
+    private Uid names_get_selected_uid () {
+        return get_selected_object("names-tree", UidSigColumn.OBJECT);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_names_add_clicked (Gtk.Widget widget) {
+        GObject *obj = SEAHORSE_OBJECT_WIDGET (swidget).object;
+        g_return_if_fail (SEAHORSE_IS_GPGME_KEY (obj));
+        seahorse_gpgme_add_uid_new (SEAHORSE_GPGME_KEY (obj),
+                                    GTK_WINDOW (seahorse_widget_get_widget (swidget, swidget.name)));
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_names_primary_clicked (Gtk.Widget widget) {
+        GpgME.Uid? uid = names_get_selected_uid() as GpgME.Uid;
+        if (uid == null)
+            return;
+
+        GPG.Error gerr = GpgME.KeyOperation.primary_uid(uid);
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t change primary user ID"), err.strerror());
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_names_delete_clicked (Gtk.Widget widget) {
+        GpgME.Uid? uid = names_get_selected_uid() as GpgME.Uid;
+        if (uid == null)
+            return;
+
+        string message = _("Are you sure you want to permanently delete the “%s” user 
ID?").printf(uid.label);
+        if (!Seahorse.DeleteDialog.prompt(this, message))
+            return;
+
+        GPG.Error gerr = GpgME.KeyOperation.del_uid(uid);
+        if (!gerr.is_success() || gerr.is_cancelled())
+            Util.show_error(null, _("Couldn’t delete user ID"), gerr.strerror());
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_names_sign_clicked (Gtk.Widget widget) {
+        GpgME.Uid? uid = names_get_selected_uid() as GpgME.Uid;
+        if (uid == null)
+            return;
+
+        Sign.prompt_uid(uid, this);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_names_revoke_clicked (Gtk.Widget widget, gpointer user_data) {
+        /* TODO: */
+    /*    Seahorse.Object skey;
+        int index;
+        Glist *keys = null;
+
+        skey = SEAHORSE_OBJECT_WIDGET (swidget).skey;
+        index = names_get_selected_uid (swidget);
+
+        if (index >= 1) {
+            seahorse_revoke_show (SEAHORSE_PGP_KEY (skey), index - 1);
+
+#ifdef WITH_KEYSERVER
+            if (g_settings_get_boolean(AUTOSYNC_KEY) == true) {
+                keys = g_list_append (keys, skey);
+                seahorse_keyserver_sync (keys);
+                g_list_free(keys);
+            }
+#endif
+        }*/
+    }
+
+    private void update_names(Gtk.TreeSelection selection) {
+        Uid uid = names_get_selected_uid();
+
+        int index = -1;
+        if (uid && SEAHORSE_IS_GPGME_UID (uid))
+            index = ((GpgME.Uid) uid).get_gpgme_index();
+
+        this.names_primary_button.sensitive = (index > 0);
+        this.names_delete_button.sensitive = (index >= 0);
+        this.names_sign_button.sensitive = (index >= 0);
+        this.names_revoke_button.visible = false;
+    }
+
+    /* Is called whenever a signature key changes, to update row */
+    private void names_update_row (Seahorse.ObjectModel skmodel, Seahorse.Object object, Gtk.TreeIter iter, 
Seahorse.Widget swidget) {
+        Icon icon = new ThemedIcon((object is Pgp.Key)? Seahorse.ICON_SIGN
+                                                      : Gtk.Stock.DIALOG_QUESTION);
+
+        /* TRANSLATORS: [Unknown] signature name */
+        skmodel.set(iter, UidSigColumn.OBJECT, null,
+                          UidSigColumn.ICON, icon,
+                          UidSigColumn.NAME, object.get_markup()?? _("[Unknown]"),
+                          UidSigColumn.KEYID, object.get_identifier(), -1);
+    }
+
+    private void names_populate(Seahorse.ObjectModel store, Key pkey) {
+        // Insert all the fun-ness
+        foreach (Uid uid in pkey.uids) {
+            Icon icon = new ThemedIcon(Seahorse.ICON_PERSON);
+
+            Gtk.TreeIter uiditer, sigiter;
+            store.append(store, out uiditer);
+            store.set(uiditer, UidSigColumn.OBJECT, uid,
+                               UidSigColumn.ICON, icon,
+                               UidSigColumn.NAME, uid.markup, -1);
+
+            // Build a list of all the keyids
+            string[] keyids = {};
+            foreach (Signature sig in uid.signatures)
+                if (!this.key.has_keyid(sig.keyid)) // Never show self signatures, they're implied
+                    keyids += sig.keyid;
+
+            // Pass it to 'DiscoverKeys' for resolution/download,
+            List<Pgp.Key> keys = Pgp.Backend.discover_keys(null, keyids, new Cancellable());
+
+            /* Add the keys to the store */
+            foreach (Pgp.Key key in keys) {
+                store.append(&sigiter, &uiditer);
+
+                // This calls the 'update-row' callback, to set the values for the key */
+                store.set_row_object(&sigiter, key);
+            }
+        }
+    }
+
+    private void do_names() {
+        if (this.key.usage != Seahorse.Usage.PRIVATE_KEY)
+            return;
+
+        // Clear/create table store
+        if (this.names_tree == null)
+            return;
+
+        Gtk.TreeStore? store = this.names_tree.get_model();
+        if (store != null) {
+            store.clear();
+        } else {
+            assert(UidSigColumn.N_COLUMNS == G_N_ELEMENTS (uidsig_columns));
+            uidsig_columns[UidSigColumn.ICON] = G_TYPE_ICON;
+
+            // This is our first time so create a store
+            store = GTK_TREE_STORE (new Seahorse.ObjectModel(UidSigColumn.N_COLUMNS, uidsig_columns));
+            store.update_row.connect(names_update_row);
+
+            /* Icon column */
+            Gtk.CellRenderer icon_renderer = new Gtk.CellRendererPixbuf();
+            icon_renderer.stock_size = Gtk.IconSize.LARGE_TOOLBAR;
+            this.names_tree.insert_column_with_attributes(-1, "", icon_renderer,
+                                                             "gicon", UidSigColumn.ICON, null);
+
+            /* TRANSLATORS: The name and email set on the PGP key */
+            Gtk.CellRenderer name_renderer = new Gtk.CellRendererText();
+            name_renderer.yalign = 0.0;
+            name_renderer.xalign = 0.0;
+            this.names_tree.insert_column_with_attributes(-1, _("Name/Email"), name_renderer,
+                                                             "markup", UidSigColumn.NAME, null);
+
+            /* The signature ID column */
+            Gtk.CellRenderer signature_renderer = new Gtk.CellRendererText();
+            signature_renderer.yalign = 0.0;
+            signature_renderer.xalign = 0.0;
+            this.names_tree.insert_column_with_attributes(-1, _("Signature ID"), signature_renderer,
+                                                             "text", UidSigColumn.KEYID, null);
+        }
+
+        names_populate(store, this.key);
+
+        this.names_tree.set_model(store);
+        this.names_tree.expand_all();
+
+        update_names();
+    }
+
+    private void do_names_signals() {
+        if (this.key.usage != Seahorse.Usage.PRIVATE_KEY)
+            return;
+        if (this.names_tree != null) // Doesn't this contradict with previous condition?
+            this.names_tree.get_selection().changed.connect(update_names);
+    }
+
+    /* -----------------------------------------------------------------------------
+     * PHOTO ID AREA
+     */
+    /* drag-n-drop uri data */
+    private enum DndTarget {
+        TARGET_URI,
+    }
+
+    static GtkTargetEntry target_list[] = {
+        { "text/uri-list", 0, TARGET_URI } };
+
+    static uint n_targets = G_N_ELEMENTS (target_list);
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photo_drag_received (Gtk.Widget widget, GdkDragContext *context, int x, int y, 
GtkSelectionData *sel_data, uint target_type, uint time) {
+        g_return_if_fail (SEAHORSE_IS_GPGME_KEY (this.key));
+
+        bool dnd_success = false;
+
+        // This needs to be improved, support should be added for remote images
+        // and there has to be a better way to get rid of the trailing \r\n appended
+        // to the end of the path after the call to g_filename_from_uri
+        int len = 0;
+        if ((sel_data != null) && (sel_data.get_length() >= 0)) {
+            g_return_if_fail (target_type == TARGET_URI);
+
+            string[] uri_list = gtk_selection_data_get_uris (sel_data);
+            while (uri_list && uri_list[len]) {
+
+                string uri = g_filename_from_uri (uri_list[len], null, null);
+                if (!uri)
+                    continue;
+
+                dnd_success = seahorse_gpgme_photo_add (this.key, GTK_WINDOW (seahorse_widget_get_toplevel 
(swidget)), uri);
+
+                if (!dnd_success)
+                    break;
+                len++;
+            }
+        }
+
+        gtk_drag_finish (context, dnd_success, false, time);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photo_add_button (Gtk.Widget widget) {
+        g_return_if_fail (SEAHORSE_IS_GPGME_KEY (this.key));
+
+        if (seahorse_gpgme_photo_add (SEAHORSE_GPGME_KEY (this.key), GTK_WINDOW 
(seahorse_widget_get_toplevel (swidget)), null))
+            this.current_photoid = null;
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photo_delete_button (Gtk.Widget widget) {
+        g_return_if_fail (SEAHORSE_IS_GPGME_PHOTO (this.current_photoid));
+
+        if (KeyOperation.photo_delete(this.current_photoid))
+            this.current_photoid = null;
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photo_primary_button (Gtk.Widget widget) {
+        g_return_if_fail (SEAHORSE_IS_GPGME_PHOTO (this.current_photoid));
+
+        GPG.Error gerr = KeyOperation.photo_primary(this.current_photoid);
+        if (!gerr.is_success() || gerr.is_cancelled())
+            Util.show_error(null, _("Couldn’t change primary photo"), gerr.strerror());
+    }
+
+    private void set_photoid_state() {
+        if (this.current_photoid == null)
+            return;
+
+        bool is_gpgme = this.key is GpgME.Key;
+        bool is_private_key = this.key.usage == Seahorse.Usage.PRIVATE_KEY;
+
+        this.owner_photo_add_button.visible = (is_gpgme && is_private_key);
+        this.owner_photo_primary_button.visible = (is_gpgme && is_private_key && this.key.photos && 
this.key.photos.next);
+
+        // Display this when there are any photo ids
+        this.owner_photo_delete_button.visible = (is_gpgme && is_private_key && this.current_photoid != 
null);
+
+        // Sensitive when not the first photo id
+        this.owner_photo_previous_button.sensitive = (this.current_photoid != null && this.key.photos && 
this.current_photoid != this.key.photos.first().data);
+        this.owner_photo_next_button.sensitive = (this.current_photoid != null && this.key.photos && 
this.current_photoid != this.key.photos.last().data);
+
+        // Display *both* of these when there are more than one photo id
+        this.owner_photo_previous_button.visible = this.key.photos && this.key.photos.next();
+        this.owner_photo_next_button.visible = this.key.photos && this.key.photos.next();
+
+        Gtk.Image photo_image = swidget.get_widget ("photoid");
+        g_return_if_fail (photo_image);
+        if (this.current_photoid.pixbuf != null)
+            photo_image.set_from_pixbuf(this.current_photoid.pixbuf);
+        else if (is_private_key)
+            photo_image.set_from_icon_name(Seahorse.ICON_SECRET, Gtk.IconSize.DIALOG);
+        else
+            photo_image.set_from_icon_name(Seahorse.ICON_KEY, Gtk.IconSize.DIALOG);
+    }
+
+    private void do_photo_id() {
+        this.current_photoid = (this.key.photos != null)? this.key.photos.data : null;
+        set_photoid_state();
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photoid_next(Gtk.Widget widget) {
+        if (this.current_photoid != null && (this.current_photoid in this.key.photos) && 
this.key.photos.next != null)
+            this.current_photoid = this.key.photos.next;
+
+        set_photoid_state();
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photoid_prev (Gtk.Widget widget) {
+        if (this.current_photoid != null && (this.current_photoid in this.key.photos) && 
this.key.photos.prev != null)
+            this.current_photoid = this.key.photos.prev;
+
+        set_photoid_state();
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_photoid_button (Gtk.Widget widget, Gdk.Event event) {
+        if(event.type == GDK_SCROLL) {
+            Gdk.EventScroll event_scroll = (Gdk.EventScroll) event;
+
+            if (event_scroll.direction == GDK_SCROLL_UP)
+                on_pgp_owner_photoid_prev (widget);
+            else if (event_scroll.direction == GDK_SCROLL_DOWN)
+                on_pgp_owner_photoid_next (widget);
+        }
+    }
+
+    /* -----------------------------------------------------------------------------
+     * OWNER PAGE
+     */
+    /* owner uid list */
+    private enum UidColumn {
+        OBJECT,
+        ICON,
+        MARKUP,
+        N_COLUMNS
+    }
+
+    static Type uid_columns[] = {
+        typeof(GLib.Object),  /* object */
+        0 /* later */,  /* icon */
+        typeof(string),  /* name */
+        typeof(string),  /* email */
+        typeof(string)   /* comment */
+    };
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_owner_passphrase_button_clicked (Gtk.Widget widget) {
+        if (this.key.usage == Seahorse.Usage.PRIVATE_KEY && (this.key is GpgME.Key))
+            KeyOperation.change_pass((GpgME.Key) this.key);
+    }
+
+    private void do_owner_signals() {
+        if (this.key.usage == Seahorse.Usage.PRIVATE_KEY ) {
+            Gtk.Frame frame = GTK_WIDGET (seahorse_widget_get_widget (swidget, "owner-photo-frame"));
+            gtk_drag_dest_set (frame, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT |
+                               GTK_DEST_DEFAULT_DROP, target_list, n_targets, GDK_ACTION_COPY);
+        } else {
+           if (this.owner-photo-add-button != null) this.owner-photo-add-button.visible = false;;
+           if (this.owner-photo-delete-button != null) this.owner-photo-delete-button.visible = false;;
+           if (this.owner-photo-primary-button != null) this.owner-photo-primary-button.visible = false;;
+           if (this.passphrase-button != null) this.passphrase-button.visible = false;;
+        }
+    }
+
+    private void do_owner() {
+        // Display appropriate warnings
+        this.expired_area.visible = (Seahorse.Flags.EXPIRED in this.key.flags);
+        this.revoked_area.visible = (Seahorse.Flags.REVOKED in this.key.flags);
+        /* this.disabled_area.visible =  Seahorse.Flags.DISABLED in this.key.flags); XXX this doesn't exist? 
*/
+
+        /* Update the expired message */
+        if (Seahorse.Flags.EXPIRED in this.key.flags) {
+            ulong expires_date = this.key.expires;
+            string t;
+            if (expires_date == 0)
+                /* TRANSLATORS: (unknown) expiry date */
+                t = _("(unknown)");
+            else
+                t = seahorse_util_get_display_date_string (expires_date);
+            this.expired_message.text = _("This key expired on: %s").printf(t);
+        }
+
+        // Hide trust page when above
+        if (this.trust-page != null)
+            this.trust-page.visible = !((Seahorse.Flags.EXPIRED in this.key.flags)
+                                       || (Seahorse.Flags.REVOKED in this.key.flags)
+                                       || (Seahorse.Flags.DISABLED in this.key.flags));
+
+        // Hide or show the uids area
+        if (this.uids-area != null)
+            this.uids-area.visible = uids != null;
+
+        if (this.key.uids != null) {
+            Uid uid = this.key.uids.first();
+
+            this.title = uid.name;
+            this.owner_name_label.text = uid.name;
+            this.owner_email_label.text = uid.email;
+            this.owner_comment_label.text = uid.comment;
+            this.owner_keyid_label.text = uid.identifier; // XXX or keyid? identifier is from SeahorseObject
+        }
+
+        // Clear/create table store
+        if (this.owner_userid_tree != null) {
+            Gtk.ListStore store = this.owner_userid_tree.get_model();
+
+            if (store != null) {
+                store.clear();
+            } else {
+                assert(UidColumn.N_COLUMNS != G_N_ELEMENTS (uid_columns));
+                uid_columns[1] = G_TYPE_ICON;
+
+                // This is our first time so create a store
+                store = new Gtk.ListStore.newv(UidColumn.N_COLUMNS, uid_columns);
+
+                // Make the columns for the view
+                Gtk.CellRenderer renderer = new Gtk.CellRendererPixbuf();
+                renderer.stock_size = Gtk.IconSize.LARGE_TOOLBAR;
+                this.owner_user_id_tree.insert_column_with_attributes(-1, "", renderer, "gicon", 
UidColumn.ICON, null);
+                this.owner_user_id_tree.insert_column_with_attributes(-1, _("Name"), new 
Gtk.CellRendererText(),
+                                                                      "markup", UidColumn.MARKUP, null);
+            }
+
+            foreach (Uid uid in this.key.uids) {
+                Icon icon = new ThemedIcon(Seahorse.ICON_PERSON);
+                Gtk.TreeIter iter;
+                store.append(ref iter);
+                store.set(iter, UidColumn.OBJECT, uid,
+                                 UidColumn.ICON, icon,
+                                 UidColumn.MARKUP, uid.markup, -1);
+            }
+
+            this.owner_userid_tree.set_model(store);
+        }
+
+        do_photo_id();
+    }
+
+    /* -----------------------------------------------------------------------------
+     * DETAILS PAGE
+     */
+    /* details subkey list */
+    private enum SubKeyColumn {
+        OBJECT,
+        ID,
+        TYPE,
+        USAGE,
+        CREATED,
+        EXPIRES,
+        STATUS,
+        LENGTH,
+        N_COLUMNS
+    }
+
+    const Type subkey_columns[] = {
+        typeof(GLib.Object),  /* SubkeyColumn.OBJECT */
+        typeof(string),  /* SubkeyColumn.ID */
+        typeof(string),  /* SubkeyColumn.TYPE */
+        typeof(string),  /* SubkeyColumn.USAGE */
+        typeof(string),  /* SubkeyColumn.CREATED */
+        typeof(string),  /* SubkeyColumn.EXPIRES  */
+        typeof(string),  /* SubkeyColumn.STATUS */
+        typeof(uint)     /* SubkeyColumn.LENGTH */
+    };
+
+    /* trust combo box list */
+    private enum TrustColumn {
+        LABEL,
+        VALIDITY,
+        N_COLUMNS;
+    }
+
+    const Type trust_columns[] = {
+        typeof(string),  /* label */
+        typeof(int)      /* validity */
+    };
+
+    private Subkey? get_selected_subkey() {
+        return get_selected_object(swidget, "details-subkey-tree", SubkeyColumn.OBJECT);
+    }
+
+    private void details_subkey_selected(GtkTreeSelection *selection) {
+        SubKey? subkey = get_selected_subkey (swidget);
+
+        if (this.key.usage == Seahorse.Usage.PRIVATE_KEY) {
+            this.details_date_button.sensitive = (subkey != null);
+            this.details_revoke_button.sensitive = (subkey != null && !(Seahorse.Flags.REVOKED in 
subkey.flags));
+            this.details_delete_button.sensitive = (subkey != null);
+        }
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_add_subkey_button (Gtk.Button button) {
+        if (!(this.key is GpgME.Key))
+            return;
+
+        Gtk.Dialog dialog = new AddSubkey((GpgME.Key) key, this);
+        dialog.run();
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_del_subkey_button (Gtk.Button button) {
+        GpgME.SubKey? subkey = get_selected_subkey() as GpgME.SubKey;
+        if (subkey == null)
+            return;
+
+        string message = _("Are you sure you want to permanently delete subkey %d of %s?")
+                            .printf(subkey.index, this.key.label);
+        if (!Seahorse.DeleteDialog.prompt(this.get_toplevel(), message))
+            return;
+
+        GPG.Error err = KeyOperation.del_subkey(subkey);
+        if (!err.is_success() || err.is_cancelled())
+            Util.show_error(null, _("Couldn’t delete subkey"), err.strerror());
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_revoke_subkey_button (Gtk.Button button) {
+        GpgME.SubKey? subkey = get_selected_subkey() as GpgME.SubKey;
+        if (subkey == null)
+            return;
+
+        seahorse_gpgme_revoke_new (subkey, this);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_trust_changed (GtkComboBox *selection) {
+        Gtk.TreeIter iter;
+        bool set = selection.get_active_iter(out iter);
+        if (!set)
+            return;
+
+        GpgME.Key? gpg_key = this.key as GpgME.Key;
+        if (gpg_key == null)
+            return;
+
+        Seahorse.Validity trust;
+        selection.get_model().get(iter, TrustColumn.VALIDITY, out trust, -1);
+
+        if (gpg_key.trust != trust) {
+            GPG.Error err = GpgME.KeyOperation.set_trust(gpg_key, trust);
+            if (!err.is_success() || err.is_cancelled())
+                Util.show_error(null, _("Unable to change trust"), err.strerror());
+        }
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_export_button(Gtk.Widget widget) {
+        GpgME.Exporter exporter = new GpgME.Exporter(this.key, true, true);
+
+        List<Exporter> exporters = new List<Exporter>();
+        exporters.append(exporter);
+
+        string directory;
+        File file;
+        // Is the exporter from the out argument? (i.e. did I initialize wrong?)
+        if (seahorse_exportable_prompt(exporters, this, out directory, out file, &exporter)) {
+            exporter.export_to_file.begin(file, true, null, (obj, res) => {
+                try {
+                    exporter.export_to_file.finish(res);
+                } catch (GLib.Error e) {
+                    Seahorse.Util.handle_error(e, this, _("Couldn’t export key"));
+                }
+            });
+        }
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_expires_button(Gtk.Widget widget) {
+        List<SubKey>? subkeys = this.key.subkeys;
+        g_return_if_fail (subkeys);
+
+        seahorse_gpgme_expires_new (SEAHORSE_GPGME_SUBKEY (subkeys.data), this);
+    }
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_details_expires_subkey (Gtk.Widget widget) {
+        SubKey? subkey = get_selected_subkey() ?? this.key.subkeys.data;
+
+        GpgME.SubKey? gpg_subkey = subkey as GpgME.SubKey;
+        if (subkey != null)
+            seahorse_gpgme_expires_new (gpg_subkey, this);
+    }
+
+    private void setup_details_trust() {
+        debug("KeyProperties: Setting up Trust Combo Box Store");
+
+        this.details_trust_combobox.clear();
+
+        Gtk.CellRendererText text_cell = new Gtk.CellRendererText();
+        this.details_trust_combobox.pack_start(text_cell, false);
+        this.details_trust_combobox.set_attributes(text_cell, "text", TrustColumn.LABEL, null);
+
+        // Initialize the store
+        Gtk.ListStore store = new Gtk.ListStore.newv(TrustColumn.N_COLUMNS);
+
+        Gtk.TreeIter iter;
+        if (this.key.usage != Seahorse.Usage.PRIVATE_KEY) {
+            store.append(ref iter);
+            store.set(iter, TrustColumn.LABEL, C_("Validity", "Unknown"),
+                            TrustColumn.VALIDITY,  Seahorse.Validity.UNKNOWN, -1);
+            store.append(ref iter);
+            store.set(iter, TrustColumn.LABEL, C_("Validity","Never"),
+                            TrustColumn.VALIDITY,  Seahorse.Validity.NEVER, -1);
+        }
+
+        store.append(ref iter);
+        store.set(iter, TrustColumn.LABEL, _("Marginal"),
+                        TrustColumn.VALIDITY,  Seahorse.Validity.MARGINAL, -1);
+        store.append(ref iter);
+        store.set(iter, TrustColumn.LABEL, _("Full"),
+                        TrustColumn.VALIDITY,  Seahorse.Validity.FULL, -1);
+
+        if (this.key.usage == Seahorse.Usage.PRIVATE_KEY) {
+            store.append(ref iter);
+            store.set(iter, TrustColumn.LABEL, _("Ultimate"),
+                            TrustColumn.VALIDITY,  Seahorse.Validity.ULTIMATE, -1);
+        }
+
+        this.details_trust_combobox.set_model(store);
+
+        debug ("KeyProperties: Finished Setting up Trust Combo Box Store");
+    }
+
+    private void do_details_signals()  {
+        // if not the key owner, disable most everything
+        // if key owner, add the callbacks to the subkey buttons
+        if (this.key.usage == Seahorse.Usage.PUBLIC_KEY) {
+            // XXX Doesn't exist or isn't part of the UI file
+             /*if (this.details-actions-label != null) this.details-actions-label.visible = false;; */
+             /*if (this.details-export-button != null) this.details-export-button.visible = false;; */
+             /*if (this.details-add-button != null) this.details-add-button.visible = false;; */
+             /*if (this.details-date-button != null) this.details-date-button.visible = false;; */
+             /*if (this.details-revoke-button != null) this.details-revoke-button.visible = false;; */
+             /*if (this.details-delete-button != null) this.details-delete-button.visible = false;; */
+             /*if (this.details-calendar-button != null) this.details-calendar-button.visible = false;; */
+        } else {
+            // Connect so we can enable and disable buttons as subkeys are selected
+            GtkWidget widget = GTK_WIDGET (seahorse_widget_get_widget (swidget, "details-subkey-tree"));
+            g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)),
+                              "changed", G_CALLBACK (details_subkey_selected), swidget);
+            details_subkey_selected (null, swidget);
+        }
+    }
+
+    private void do_details() {
+        if (this.key.subkeys == null || this.key.subkeys.data == null)
+            return;
+
+        SubKey subkey = this.key.subkeys.data;
+
+        this.details_id_label.text = this.key.identifier;
+        this.details_fingerprint_label.text = this.key.fingerprint;
+            /* if (strlen (fp_label) > 24 && g_ascii_isspace (fp_label[24])) */
+            /*     fp_label[24] = '\n'; */
+        this.details_algo_label.text = this.key.algo;
+        this.details_created_label.text = Seahorse.Util.get_display_date_string(subkey.created);
+        this.details_strength_label.text = subkey.length.to_string();
+
+        // Expires
+        if (!(this.key is GpgME.Key))
+            this.details_expires_label.text = "";
+        else if (subkey.expires == 0)
+            this.details_expires_label.text = C_("Expires", "Never");
+        else
+            this.details_expires_label.text = Seahorse.Util.get_display_date_string(subkey.expires);
+
+        Gtk.TreeIter iter;
+        if (this.details_trust_combobox != null) {
+            this.details_trust_combobox.visible = (this.key is GpgME.Key);
+            this.details_trust_combobox.sensitive = !(Seahorse.Flags.DISABLED in this.key.flags);
+            Gtk.TreeModel model = this.details_trust_combobox.get_model();
+
+            bool valid = model.get_iter_first(&iter);
+            while (valid) {
+                int trust;
+                model.get(&iter, TrustColumn.VALIDITY, &trust, -1);
+
+                if (trust == pkey.trust) {
+                    g_signal_handlers_block_by_func(this.details_trust_combobox, 
on_pgp_details_trust_changed);
+                    this.details_trust_combobox.set_active_iter(&iter);
+                    g_signal_handlers_unblock_by_func(this.details_trust_combobox, 
on_pgp_details_trust_changed);
+                    break;
+                }
+                valid = model.iter_next(&iter);
+            }
+        }
+
+        /* Clear/create table store */
+        Gtk.TreeView widget = GTK_WIDGET (seahorse_widget_get_widget (swidget, "details-subkey-tree"));
+        Gtk.ListStore store = GTK_LIST_STORE (widget.model);
+
+        if (store != null) {
+            store.clear();
+        } else {
+            /* This is our first time so create a store */
+            store = new Gtk.ListStore.newv(subkey_columns);
+            this.details_subkey_tree.set_model(store);
+
+            /* Make the columns for the view */
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("ID"), new Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.ID, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Type"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.TYPE, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Usage"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.USAGE, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Created"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.CREATED, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Expires"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.EXPIRES, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Status"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.STATUS, null);
+            this.details_subkey_tree.insert_column_with_attributes (-1, _("Strength"), new 
Gtk.CellRendererText(),
+                                                         "text", SubkeyColumn.LENGTH, null);
+        }
+
+        foreach (SubKey subkey in this.key.subkeys) {
+            string status = "";
+            if (Seahorse.Flags.REVOKED in subkey.flags)
+                status = _("Revoked");
+            else if (Seahorse.Flags.EXPIRED in subkey.flags)
+                status = _("Expired");
+            else if (Seahorse.Flags.DISABLED in subkey.flags)
+                status = _("Disabled");
+            else if (Seahorse.Flags.IS_VALID in subkey.flags)
+                status = _("Good");
+
+            string expiration_date;
+            if (this.key.expires == 0)
+                expiration_date = C_("Expires", "Never");
+            else
+                expiration_date = Seahorse.Util.get_display_date_string(this.key.expires);
+
+            store.append(out iter);
+            store.set(iter, SubkeyColumn.OBJECT, subkey,
+                            SubkeyColumn.ID, subkey.keyid,
+                            SubkeyColumn.TYPE, subkey.algo,
+                            SubkeyColumn.USAGE, subkey.usage,
+                            SubkeyColumn.CREATED, Seahorse.Util.get_display_date_string(subkey.created),
+                            SubkeyColumn.EXPIRES, subkey.expires,
+                            SubkeyColumn.STATUS, status,
+                            SubkeyColumn.LENGTH, subkey.length,
+                            -1);
+        }
+    }
+
+    /* -----------------------------------------------------------------------------
+     * TRUST PAGE (PUBLIC KEYS)
+     */
+    private enum SignColumn {
+        ICON,
+        NAME,
+        KEYID,
+        TRUSTED,
+        N_COLUMNS;
+    }
+
+    static Type sign_columns[] = {
+        typeof(Icon),
+        typeof(string),
+        typeof(string),
+        typeof(bool)
+    };
+
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_trust_marginal_toggled (Gtk.ToggleButton toggle) {
+        if (!(this.key is Gpg.Key))
+            return;
+
+        Seahorse.Validity trust = (toggle.active)? Seahorse.Validity.MARGINAL
+                                                 : Seahorse.Validity.UNKNOWN;
+        if (this.key.trust != trust) {
+            GPG.Error err = GpgME.KeyOperation.set_trust((GpgME.Key) key, trust);
+            if (!err.is_success() || err.is_cancelled())
+                Util.show_error(null, _("Unable to change trust"), err.strerror());
+        }
+    }
+
+    /* Is called whenever a signature key changes */
+    private void trust_update_row (Seahorse.ObjectModel skmodel, Seahorse.Object object, Gtk.TreeIter iter) {
+        bool trusted = (object.usage == Seahorse.Usage.PRIVATE_KEY
+                        || (Seahorse.Flags.TRUSTED in object.flags));
+
+        Icon icon = new ThemedIcon((object is Pgp.Key)? Seahorse.ICON_SIGN : Gtk.Stock.DIALOG_QUESTION);
+
+        /* TRANSLATORS: [Unknown] signature name */
+        skmodel.set(iter, SignColumn.ICON, icon,
+                          SignColumn.NAME, object.label ?? _("[Unknown]"),
+                          SignColumn.KEYID, object.id,
+                          SignColumn.TRUSTED, trusted,
+                          -1);
+    }
+
+    private void signatures_populate_model(Seahorse.ObjectModel skmodel) {
+        Gtk.Widget? widget = swidget.get_widget("signatures-tree");
+        if (widget == null)
+            return;
+
+        /* Build a list of all the keyids */
+        string[] rawids = g_ptr_array_new ();
+        bool have_sigs = false;
+        foreach (Uid uid in this.key.uids) {
+            foreach (Signature sig in ui.signatures) {
+                /* Never show self signatures, they're implied */
+                if (!this.key.has_keyid(sig)) {
+                    have_sigs = true;
+                    rawids += sig.keyid;
+                }
+            }
+        }
+
+        /* Strip out duplicates */
+        unique_strings (rawids);
+
+        /* Only show signatures area when there are signatures */
+        swidget.set_visible("signatures-area", have_sigs);
+
+        if (skmodel != null) {
+            // Pass it to 'DiscoverKeys' for resolution/download. cancellable ties
+            // search scope together
+            Cancellable cancellable = new Cancellable();
+            List<Key> keys = Pgp.Backend.get().discover_keys(null, rawids, cancellable);
+
+            /* Add the keys to the store */
+            foreach (Key key in keys) {
+                Gtk.TreeIter iter;
+                gtk_tree_store_append(GTK_TREE_STORE (skmodel), &iter, null);
+                // This calls the 'update-row' callback, to set the values for the key
+                seahorse_object_model_set_row_object (SEAHORSE_OBJECT_MODEL (skmodel), &iter, object);
+            }
+        }
+    }
+
+    // Refilter when the user toggles the 'only show trusted' checkbox
+    private void on_pgp_trusted_toggled (Gtk.ToggleButton toggle, Gtk.TreeModelFilter filter) {
+        filter.get_model().set_data("only-trusted", toggle.active); // Set flag on the store
+        filter.refilter();
+    }
+
+    // Add a signature
+    // XXX G_MODULE_EXPORT
+    private void on_pgp_trust_sign (Gtk.Widget widget) {
+        GpgME.Key? gpg_key = this.key as GpgME.Key;
+        if (gpg_key == null)
+            return;
+
+        Sign.prompt(gpg_key, this);
+    }
+
+    private void do_trust_signals() {
+        if (this.key.usage != Seahorse.Usage.PUBLIC_KEY)
+            return;
+
+       if (this.image_good1 != null) this.image_good1.set_from_icon_name(seahorse-sign-ok);;
+       if (this.image_good2 != null) this.image_good2.set_from_icon_name(seahorse-sign-ok);;
+
+        /* TODO: Hookup revoke handler */
+
+        if (this.key.usage == Seahorse.Usage.PUBLIC_KEY ) {
+           if (this.signatures-revoke-button != null) this.signatures-revoke-button.visible = false;;
+           if (this.signatures-delete-button != null) this.signatures-delete-button.visible = false;;
+           if (this.signatures-empty-label != null) this.signatures-empty-label.visible = false;;
+
+            /* Fill in trust labels with name .This only happens once, so it sits here. */
+            string user = this.key.label;
+            this.trust_marginal_check.text = user;
+            this.trust_sign_label.text = user;
+            this.trust_revoke_label.text = user;
+        }
+    }
+
+    /* When the 'only display trusted' check is checked, hide untrusted rows */
+    private bool trust_filter (Gtk.TreeModel model, Gtk.TreeIter iter, gpointer userdata) {
+        /* Read flag on the store */
+        bool trusted = false;
+        model.get(iter, SignColumn.TRUSTED, out trusted, -1);
+        return !model.get_data("only-trusted") || trusted;
+    }
+
+    private bool key_have_signatures (Key pkey, uint types) {
+        foreach (Uid uid in this.key.uids)
+            foreach (Signature sig in uid.sigs)
+                if ((sig.get_sigtype() & types) != 0)
+                    return true;
+
+        return false;
+    }
+
+    private void do_trust() {
+        if (this.key.usage != Seahorse.Usage.PUBLIC_KEY)
+            return;
+
+        /* Remote keys */
+        if (!(this.key is GpgME.Key)) {
+            this.manual_trust_area.visible = false;
+            this.manage_trust_area.visible = true;
+            this.sign_area.visible = false;
+            this.revoke_area.visible = false;
+            this.trust_marginal_check.sensitive = false;
+            this.sign_image.set_from_icon_name(Seahorse.ICON_SignColumn.UNKNOWN);
+
+        /* Local keys */
+        } else {
+            bool managed = (Seahorse.Validity.FULL in this.key.trust)
+                           || (Seahorse.Validity.MARGINAL in this.key.trust)
+                           || (Seahorse.Validity.UNKNOWN in this.key.trust);
+
+            string? icon = null;
+
+            switch (this.key.trust) {
+                case Seahorse.Validity.ULTIMATE: // Trust is specified manually
+                case Seahorse.Validity.FULL:
+                case Seahorse.Validity.MARGINAL:
+                    icon = Seahorse.ICON_SignColumn.OK;
+                    break;
+
+                case Seahorse.Validity.NEVER: // Trust is specified manually
+                    icon = Seahorse.ICON_SignColumn.BAD;
+                    break;
+
+                case Seahorse.Validity.UNKNOWN: // We manage the trust through this page
+                    icon = Seahorse.ICON_SIGN;
+                    break;
+
+                case Seahorse.Validity.REVOKED:  // We shouldn't be seeing this page with these trusts
+                case Seahorse.Validity.DISABLED:
+                    return;
+
+                default:
+                    warning("unknown trust value: %d", this.key.trust);
+                    assert_not_reached();
+                    return;
+            }
+
+            /* Managed and unmanaged areas */
+            this.manual_trust_area.visible = !managed;
+            this.manage_trust_area.visible = managed;
+
+            /* Managed check boxes */
+            if (managed) {
+                this.trust_marginal_check.sensitive = true;
+
+                g_signal_handlers_block_by_func (widget, on_pgp_trust_marginal_toggled);
+                this.trust_marginal_check.active = (trust != Seahorse.Validity.UNKNOWN);
+                g_signal_handlers_unblock_by_func (widget, on_pgp_trust_marginal_toggled);
+            }
+
+            /* Signing and revoking */
+            bool sigpersonal = key_have_signatures(this.key, SKEY_PGPSIG_PERSONAL);
+            this.sign_area.visible = !sigpersonal;
+            this.revoke_area.visible = sigpersonal;
+
+            /* The image */
+            this.sign_image.set_from_icon_name(icon);
+        }
+
+        /* The actual signatures listing */
+        Gtk.TreeModelFilter? filter = this.signatures_tree.get_model();
+        if (filter != null) {
+            filter.get_model().clear();
+        } else { // First time create the store
+            /* Create a new SeahorseObjectModel store.... */
+            Gtk.TreeStore store = new Seahorse.ObjectModel(SignColumn.N_COLUMNS, sign_columns);
+            store.update_row.connect(trust_update_row);
+
+            /* .... and a filter to go ontop of it */
+            filter = new Gtk.TreeModelFilter(store, null);
+            filter.set_visible_func(trust_filter);
+
+            /* Make the colunms for the view */
+            Gtk.CellRendererPixbuf renderer = new Gtk.CellRendererPixbuf();
+            renderer.stock_size = Gtk.IconSize.LARGE_TOOLBAR;
+            this.signatures_tree.insert_column_with_attributes(-1, "", renderer,
+                                                               "gicon", SignColumn.ICON, null);
+            /* TRANSLATORS: The name and email set on the PGP key */
+            this.signatures_tree.insert_column_with_attributes(-1, _("Name/Email"), 
gtk_cell_renderer_text_new (),
+                                                               "text", SignColumn.NAME, null);
+            this.signatures_tree.insert_column_with_attributes(-1, _("Key ID"), gtk_cell_renderer_text_new 
(),
+                                                              "text", SignColumn.KEYID, null);
+
+            this.signatures_tree.set_model(filter);
+            this.signatures_toggle.toggled.connect(on_pgp_trusted_toggled);
+            this.signatures_toggle.active = true;
+        }
+
+        signatures_populate_model (swidget, SEAHORSE_OBJECT_MODEL (store));
+    }
+
+    /* -----------------------------------------------------------------------------
+     * GENERAL
+     */
+
+    private void key_notify (Seahorse.Object object) {
+        do_owner();
+        do_names();
+        do_trust();
+        do_details();
+    }
+
+    public override void response(int response) {
+        if (response == Gtk.ResponseType.HELP) {
+            seahorse_widget_show_help(swidget);
+            return;
+        }
+
+        destroy();
+    }
+}
diff --git a/pgp/pgp-key.vala b/pgp/pgp-key.vala
new file mode 100644
index 0000000..64af14a
--- /dev/null
+++ b/pgp/pgp-key.vala
@@ -0,0 +1,288 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ * Copyright (C) 2017 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.Pgp.Key : Object, Viewable {
+    private string keyid;
+
+    /**
+     * PGP User Ids
+     */
+    public List<Uid> uids { get; set; }
+
+    /**
+     * PGP subkeys
+     */
+    public List<SubKey> subkeys {
+        get { return this._subkeys; }
+        set { set_subkeys(value); }
+    }
+    private List<SubKey> _subkeys;
+
+    /**
+     * Photos for the key
+     */
+    public List<Photo> photos { get; set; }
+
+    /**
+     * Description for key
+     */
+    public string description {
+        get { return this.usage == Seahorse.Usage.PRIVATE_KEY ? _("Personal PGP key") : _("PGP key"); }
+    }
+
+    /**
+     * Validity of this key
+     */
+    public Seahorse.Validity validity {
+        get { return Seahorse.Validity.UNKNOWN; }
+    }
+
+    /**
+     * Trust in this key
+     */
+    public Seahorse.Validity trust {
+        get { return Seahorse.Validity.UNKNOWN; }
+    }
+
+    /**
+     * Unique fingerprint for this key
+     */
+    public string? fingerprint {
+        get { return (this._subkeys != null)? this._subkeys.data.fingerprint : ""; }
+    }
+
+    /**
+     * Date this key expires on (0 if never)
+     */
+    public ulong expires {
+        get { return (this._subkeys != null)? this._subkeys.data.expires : 0; }
+    }
+
+    /**
+     * The length of this key
+     */
+    public uint length {
+        get { return (this._subkeys != null)? this._subkeys.data.length : 0; }
+    }
+
+    /**
+     * The algorithm of this key
+     */
+    public string algo {
+        get { return (this._subkeys != null)? this._subkeys.data.algorithm : 0; }
+    }
+
+    private void set_subkeys(List<SubKey> subkeys) {
+        if (subkeys == null)
+            return;
+
+        string? keyid = this.subkeys.data.keyid;
+        if (keyid == null)
+            return;
+
+        // The keyid can't change
+        if (this.keyid != null) {
+            if (this.keyid != keyid) {
+                warning("The keyid of a PgpKey changed by setting a different subkey on it: %s != %s",
+                        this.keyid, keyid);
+                return;
+            }
+
+        } else {
+            this.keyid = keyid;
+        }
+
+        this._subkeys = subkeys.copy();
+
+        notify_property("subkeys");
+    }
+
+    public void realize() {
+        string identifier;
+        string identifier = has_subkeys()? Key.calc_identifier(this.subkeys.data.keyid) : "";
+
+        // The type
+        string icon_name;
+        if (this.usage == Seahorse.Usage.PRIVATE_KEY) {
+            icon_name = GCR_ICON_KEY_PAIR;
+        } else {
+            icon_name = GCR_ICON_KEY;
+            if (this.usage == Seahorse.Usage.NONE)
+                this.usage = Seahorse.Usage.PUBLIC_KEY;
+        }
+
+        this.label = calc_name();
+        this.markup = calc_markup();
+        this.nickname = calc_short_name();
+        this.identifier = identifier;
+        this.icon = new ThemedIcon(icon_name);
+    }
+
+    public Gtk.Window create_viewer(Gtk.Window parent) {
+        return KeyProperties.show(this, parent);
+    }
+
+    string calc_identifier(string keyid) {
+        size_t len = keyid.length;
+        if (len > 8)
+            keyid += len - 8;
+
+        return keyid;
+    }
+
+    public string? get_keyid() {
+        if (!has_subkeys())
+            return null;
+
+        return this.subkeys.data.keyid;
+    }
+
+    public bool has_keyid(string match) {
+        if (match == null || this.subkeys == null)
+            return false;
+
+        uint n_match = match.length;
+        foreach (SubKey subkey in this.subkeys) {
+            string? keyid = subkey.get_keyid();
+            if (keyid == null)
+                return false;
+
+            uint n_keyid = keyid.length;
+            if (n_match <= n_keyid) {
+                keyid += (n_keyid - n_match);
+                if (strncmp (keyid, match, n_match) == 0)
+                    return true;
+            }
+        }
+
+        return false;
+    }
+
+    /*
+     * PGP key ids can be of varying lengths. Shorter keyids are the last
+     * characters of the longer ones. When hashing, match on the last 8
+     * characters.
+     */
+    public static uint hash(void* v) {
+        string keyid = v;
+        size_t len = keyid.length;
+        if (len > 8)
+            keyid += len - 8;
+        return keyid.hash();
+    }
+
+    public static bool keyid_equal(void* v1, void* v2) {
+        string keyid_1 = (string) v1;
+        string keyid_2 = (string) v2;
+        size_t len_1 = keyid_1.length;
+        size_t len_2 = keyid_2.length;
+
+        if (len_1 != len_2 && len_1 >= 8 && len_2 >= 8) {
+            keyid_1 += len_1 - 8;
+            keyid_2 += len_2 - 8;
+        }
+        return keyid_1 == keyid_2;
+    }
+
+    public bool has_subkeys() {
+        return this.subkeys != null;
+    }
+
+    public bool has_uids() {
+        return this.uids != null;
+    }
+
+    private string calc_short_name() {
+        return has_uids()? this.uids.data.name : null;
+    }
+
+    private string calc_name() {
+        if (!has_uids())
+            return "";
+
+        return this.uids.data.calc_label(this.uids.data.name,
+                                         this.uids.data.email,
+                                         this.uids.data.comment);
+    }
+
+    private string calc_markup() {
+        StringBuilder result = new StringBuilder("<span");
+
+        if (Seahorse.Flags.EXPIRED in this.object_flags
+            || Seahorse.Flags.REVOKED in this.object_flags
+            || Seahorse.Flags.DISABLED in this.object_flags)
+            result.append(" strikethrough='true'");
+
+        if (!(Seahorse.Flags.TRUSTED in this.object_flags))
+            result.append("  foreground='#555555'");
+        result.append_c('>');
+
+        // The first name is the key name
+        string? primary = null, name = null;
+        if (has_uids()) {
+            primary = name = this.uids.data.name;
+            result.append(Markup.escape_text(name));
+        }
+
+        result.append("<span size='small' rise='0'>");
+
+        if (this.uids == null || this.uids.data == null) {
+            result.append("</span></span>");
+            return result.str;
+        }
+
+        string? email = uids.data.email;
+        if (email == "")
+            email = null;
+        string? comment = uids.data.comment;
+        if (comment == "")
+            comment = null;
+        string text = Markup.printf_escaped("\n%s%s%s%s%s",
+                                            email ?? "",
+                                            (email != null)? " " : "",
+                                            (comment != null)? "'" : "",
+                                            comment ?? "",
+                                            (comment != null)? "'" : "");
+        result.append(text);
+
+        if (uids.next != null) {
+            foreach(Uid uid in uids.next) {
+                result.append_c('\n');
+                // XXX Markup_printf_escaped()
+
+                string? name = uid.name;
+                if (name != null && name != "" && name != primary)
+                    result.append(name + ": ");
+
+                string? email = uid.email;
+                if (email != null && email != "")
+                    result.append(email + " ");
+
+                string? comment = uid.comment;
+                if (comment != null && comment == "")
+                    result.append("'" + comment + "'");
+            }
+        }
+
+        result.append("</span></span>");
+
+        return result.str;
+    }
+}
diff --git a/pgp/pgp-keysets.vala b/pgp/pgp-keysets.vala
new file mode 100644
index 0000000..a2289ab
--- /dev/null
+++ b/pgp/pgp-keysets.vala
@@ -0,0 +1,54 @@
+/* 
+ * Seahorse
+ * 
+ * Copyright (C) 2008 Stefan Walter
+ * 
+ * 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/>.
+ */
+
+namespace Seahorse {
+
+public Gcr.Collection seahorse_keyset_pgp_signers_new() {
+       Seahorse.Predicate predicate = Seahorse.Predicate() {
+        type = typeof(GpgME.Key),
+        usage = Seahorse.Usage.PRIVATE_KEY,
+        flags = Seahorse.Flags.CAN_SIGN,
+        nflags = Seahorse.Flags.EXPIRED | Seahorse.Flags.REVOKED | Seahorse.Flags.DISABLED,
+        custom = pgp_signers_match
+    };
+
+       Keyring keyring = Pgp.Backend.get().get_default_keyring();
+       Seahorse.Collection collection = new Seahorse.Collection.for_predicate(keyring, predicate, g_free);
+
+       Application.pgp_settings().changed["default-key"].connect(() => {
+        collection.refresh();
+    });
+
+       return collection;
+}
+
+private bool pgp_signers_match(GLib.Object obj, void* data) {
+       if (!SEAHORSE_IS_PGP_KEY (obj))
+               return false;
+
+       // Default key overrides all, and becomes the only signer available
+       Pgp.Key? defkey = Pgp.Backend.get().get_default_key();
+       if (defkey != null && (defkey.keyid != (Pgp.Key) obj))
+               return false;
+
+       return true;
+}
+
+}
diff --git a/pgp/pgp-photo.vala b/pgp/pgp-photo.vala
new file mode 100644
index 0000000..45382e0
--- /dev/null
+++ b/pgp/pgp-photo.vala
@@ -0,0 +1,30 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.Pgp.Photo : GLib.Object {
+    /**
+     * Photo Pixbuf
+     */
+    public Gdk.Pixbuf pixbuf { get; set; }
+
+    public Photo(Gdk.Pixbuf pixbuf) {
+        this.pixbuf = pixbuf;
+    }
+}
diff --git a/pgp/pgp-private-key-properties.ui b/pgp/pgp-private-key-properties.ui
new file mode 100644
index 0000000..f0e3c4a
--- /dev/null
+++ b/pgp/pgp-private-key-properties.ui
@@ -0,0 +1,1813 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkListStore" id="model1">
+    <columns>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Unknown</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Never</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Marginal</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Full</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Ultimate</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkDialog" id="pgp-private-key-properties">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Key Properties</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkNotebook" id="notebook">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="border_width">5</property>
+            <child>
+              <object class="GtkBox" id="vbox7">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkBox" id="revoked-area">
+                    <property name="spacing">12</property>
+                    <property name="orientation">horizontal</property>
+                    <child>
+                      <object class="GtkImage" id="image40">
+                        <property name="visible">True</property>
+                        <property name="stock">gtk-dialog-error</property>
+                        <property name="icon-size">6</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox31">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label22225">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">This key has been revoked</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="alignment43">
+                            <property name="visible">True</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkLabel" id="label22226">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">The owner of the key revoked the 
key. It can no longer be used.</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</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="GtkBox" id="expired-area">
+                    <property name="spacing">12</property>
+                    <property name="orientation">horizontal</property>
+                    <child>
+                      <object class="GtkImage" id="image41">
+                        <property name="visible">True</property>
+                        <property name="stock">gtk-dialog-error</property>
+                        <property name="icon-size">6</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox32">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label22227">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">This key has expired</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="alignment44">
+                            <property name="visible">True</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkLabel" id="expired-message">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label">This key expired on: (placeholder)</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="hbox5">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkBox" id="hbox31">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkFrame" id="owner-photo-frame">
+                            <property name="visible">True</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">none</property>
+                            <signal name="drag_data_received" handler="on_pgp_owner_photo_drag_received"/>
+                            <child>
+                              <object class="GtkAlignment" id="alignment45">
+                                <property name="visible">True</property>
+                                <property name="bottom_padding">6</property>
+                                <property name="left_padding">6</property>
+                                <property name="right_padding">6</property>
+                                <child>
+                                  <object class="GtkBox" id="vbox33">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">vertical</property>
+                                    <property name="spacing">6</property>
+                                    <child>
+                                      <object class="GtkEventBox" id="image_eventbox">
+                                        <property name="visible">True</property>
+                                        <signal name="scroll_event" handler="on_pgp_owner_photoid_button"/>
+                                        <child>
+                                          <object class="GtkImage" id="photoid">
+                                            <property name="width_request">150</property>
+                                            <property name="height_request">160</property>
+                                            <property name="visible">True</property>
+                                          </object>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkBox" id="hbox61">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">6</property>
+                                        <child>
+                                          <object class="GtkButton" id="owner-photo-add-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <child internal-child="accessible">
+                                              <object class="AtkObject" 
id="owner-photo-add-button-atkobject">
+                                                <property name="AtkObject::accessible-description" 
translatable="yes">Add a photo to this key</property>
+                                              </object>
+                                            </child>
+                                            <signal name="clicked" handler="on_pgp_owner_photo_add_button"/>
+                                            <child>
+                                              <object class="GtkImage" id="image46">
+                                                <property name="visible">True</property>
+                                                <property name="stock">gtk-add</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkButton" id="owner-photo-delete-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <child internal-child="accessible">
+                                              <object class="AtkObject" 
id="owner-photo-delete-button-atkobject">
+                                                <property name="AtkObject::accessible-description" 
translatable="yes">Remove this photo from this key</property>
+                                              </object>
+                                            </child>
+                                            <signal name="clicked" 
handler="on_pgp_owner_photo_delete_button"/>
+                                            <child>
+                                              <object class="GtkImage" id="image45">
+                                                <property name="visible">True</property>
+                                                <property name="stock">gtk-delete</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">1</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkButton" id="owner-photo-primary-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <child internal-child="accessible">
+                                              <object class="AtkObject" 
id="owner-photo-primary-button-atkobject">
+                                                <property name="AtkObject::accessible-description" 
translatable="yes">Make this photo the primary photo</property>
+                                              </object>
+                                            </child>
+                                            <signal name="clicked" 
handler="on_pgp_owner_photo_primary_button"/>
+                                            <child>
+                                              <object class="GtkImage" id="image47">
+                                                <property name="visible">True</property>
+                                                <property name="stock">gtk-go-up</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">2</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label22232">
+                                            <property name="visible">True</property>
+                                          </object>
+                                          <packing>
+                                            <property name="position">3</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkButton" id="owner-photo-previous-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <property name="focus_on_click">False</property>
+                                            <child internal-child="accessible">
+                                              <object class="AtkObject" 
id="owner-photo-previous-button-atkobject">
+                                                <property name="AtkObject::accessible-description" 
translatable="yes">Go to previous photo</property>
+                                              </object>
+                                            </child>
+                                            <signal name="clicked" handler="on_pgp_owner_photoid_prev"/>
+                                            <child>
+                                              <object class="GtkImage" id="image43">
+                                                <property name="visible">True</property>
+                                                <property name="stock">gtk-go-back</property>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">4</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkButton" id="owner-photo-next-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <child internal-child="accessible">
+                                              <object class="AtkObject" 
id="owner-photo-next-button-atkobject">
+                                                <property name="AtkObject::accessible-description" 
translatable="yes">Go to next photo</property>
+                                              </object>
+                                            </child>
+                                            <signal name="clicked" handler="on_pgp_owner_photoid_next"/>
+                                            <child>
+                                              <object class="GtkAlignment" id="alignment46">
+                                                <property name="visible">True</property>
+                                                <property name="xscale">0</property>
+                                                <property name="yscale">0</property>
+                                                <child>
+                                                  <object class="GtkBox" id="hbox62">
+                                                    <property name="visible">True</property>
+                                                    <property name="orientation">horizontal</property>
+                                                    <property name="spacing">2</property>
+                                                    <child>
+                                                      <object class="GtkImage" id="image48">
+                                                        <property name="visible">True</property>
+                                                        <property name="stock">gtk-go-forward</property>
+                                                      </object>
+                                                      <packing>
+                                                        <property name="expand">False</property>
+                                                        <property name="fill">False</property>
+                                                        <property name="position">0</property>
+                                                      </packing>
+                                                    </child>
+                                                    <child>
+                                                      <object class="GtkLabel" id="label22233">
+                                                        <property name="visible">True</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>
+                                                </child>
+                                              </object>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">5</property>
+                                          </packing>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label22231">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="yalign">0</property>
+                                <property name="label" translatable="yes" comments="A photograph">Photo 
</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">12</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox36">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkTable" id="table12">
+                            <property name="visible">True</property>
+                            <property name="border_width">3</property>
+                            <property name="n_rows">6</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">12</property>
+                            <property name="row_spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label22240">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="yalign">0</property>
+                                <property name="label" translatable="yes">Key ID:</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="top_attach">5</property>
+                                <property name="bottom_attach">6</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22234">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="yalign">0</property>
+                                <property name="label" translatable="yes" context="name-of-key" 
comments="Name of key, usually a persons name">Name:</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="label22235">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Email:</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="label22238">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Comment:</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="owner-keyid-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</property>
+                                <property name="selectable">True</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">5</property>
+                                <property name="bottom_attach">6</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="owner-comment-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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="owner-email-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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="GtkLabel" id="owner-name-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22263">
+                                <property name="visible">True</property>
+                                <property name="xalign">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>
+                            <child>
+                              <object class="GtkLabel" id="label22264">
+                                <property name="visible">True</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Type:</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="top_attach">4</property>
+                                <property name="bottom_attach">5</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22265">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Decrypt files and email sent to 
you.</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="label22266">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Private PGP Key</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">4</property>
+                                <property name="bottom_attach">5</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkAlignment" id="alignment51">
+                            <property name="visible">True</property>
+                            <property name="xalign">1</property>
+                            <property name="yalign">1</property>
+                            <property name="xscale">0</property>
+                            <property name="yscale">0</property>
+                            <child>
+                              <object class="GtkButton" id="passphrase-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <signal name="clicked" handler="on_pgp_owner_passphrase_button_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment9">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox20">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image11">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-edit</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label47">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Change 
_Passphrase</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="padding">6</property>
+                    <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">Owner</property>
+              </object>
+              <packing>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox10">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="border_width">12</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label22261">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Key Names and Signatures</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="alignment52">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkBox" id="hbox8">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="border_width">6</property>
+                        <child>
+                          <object class="GtkTable" id="table5">
+                            <property name="visible">True</property>
+                            <property name="n_rows">5</property>
+                            <child>
+                              <object class="GtkButton" id="names-primary-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_names_primary_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment5">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox13">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image7">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-go-up</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label36">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Primary</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </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="GtkButton" id="names-sign-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_names_sign_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment6">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox14">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image8">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-index</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label37">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Sign</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </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="GtkButton" id="names-delete-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_names_delete_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment2">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox9">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image4">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-delete</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label32">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Delete</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </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="GtkButton" id="names-add-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_names_add_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment53">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox66">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image50">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-add</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label22262">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes" comments="Add another 
name to the PGP key.">_Add Name</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="names-revoke-button">
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_names_revoke_clicked"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment13">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox29">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image15">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-cancel</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label58">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Revoke</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="top_attach">4</property>
+                                <property name="bottom_attach">5</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="scrolledwindow1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="hscrollbar_policy">automatic</property>
+                            <property name="vscrollbar_policy">automatic</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTreeView" id="names-tree">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="padding">6</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="padding">6</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="labelsig">
+                <property name="visible">True</property>
+                <property name="xpad">3</property>
+                <property name="label" translatable="yes" comments="List of names and signatures on the PGP 
key.">Names and Signatures</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox8">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkBox" id="hbox63">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">12</property>
+                    <property name="homogeneous">True</property>
+                    <child>
+                      <object class="GtkBox" id="vbox34">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkFrame" id="frame9">
+                            <property name="visible">True</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">none</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment47">
+                                <property name="visible">True</property>
+                                <property name="top_padding">6</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkTable" id="table13">
+                                    <property name="visible">True</property>
+                                    <property name="n_rows">3</property>
+                                    <property name="n_columns">2</property>
+                                    <property name="column_spacing">12</property>
+                                    <property name="row_spacing">6</property>
+                                    <child>
+                                      <object class="GtkLabel" id="label22242">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" translatable="yes">Key ID:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-id-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="right_attach">2</property>
+                                        <property name="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label22244">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" translatable="yes">Strength:</property>
+                                      </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="label22245">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" translatable="yes">Type:</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="GtkLabel" id="details-algo-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</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="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-strength-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</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="y_options"></property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label22248">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Technical Details</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFrame" id="frame10">
+                            <property name="visible">True</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">none</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment48">
+                                <property name="visible">True</property>
+                                <property name="top_padding">6</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkLabel" id="details-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="use_markup">True</property>
+                                    <property name="selectable">True</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label22250">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Fingerprint</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </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="GtkBox" id="vbox35">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkFrame" id="frame11">
+                            <property name="visible">True</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">none</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment49">
+                                <property name="visible">True</property>
+                                <property name="top_padding">6</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkTable" id="table14">
+                                    <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="GtkLabel" id="label22251">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="label" translatable="yes">Created:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-created-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="right_attach">2</property>
+                                        <property name="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkBox" id="hbox65">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">6</property>
+                                        <child>
+                                          <object class="GtkLabel" id="details-expires-label">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="xalign">0</property>
+                                            <property name="yalign">0.25</property>
+                                            <property name="selectable">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="details_expires_button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <signal name="clicked" handler="on_pgp_details_expires_button"/>
+                                            <child>
+                                              <object class="GtkImage" id="image49">
+                                                <property name="visible">True</property>
+                                                <property name="icon_name">stock_calendar</property>
+                                              </object>
+                                            </child>
+                                          </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="top_attach">1</property>
+                                        <property name="bottom_attach">2</property>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options">GTK_FILL</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label22252">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0.25</property>
+                                        <property name="label" translatable="yes">Expires:</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">GTK_FILL</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label22255">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Dates</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkFrame" id="frame12">
+                            <property name="visible">True</property>
+                            <property name="label_xalign">0</property>
+                            <property name="shadow_type">none</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment50">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="yalign">0</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
+                                <property name="top_padding">6</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkTable" id="table15">
+                                    <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="GtkComboBox" id="details-trust-combobox">
+                                        <property name="visible">True</property>
+                                        <property name="model">model1</property>
+                                        <signal name="changed" handler="on_pgp_details_trust_changed"/>
+                                        <child>
+                                          <object class="GtkCellRendererText" id="renderer1"/>
+                                          <attributes>
+                                            <attribute name="text">0</attribute>
+                                          </attributes>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="right_attach">2</property>
+                                        <property name="y_options">GTK_FILL</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label22256">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">Override Owner 
_Trust:</property>
+                                        <property name="use_underline">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options"></property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label22260">
+                                        <property name="visible">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="label" translatable="yes">_Export Secret 
Key:</property>
+                                        <property name="use_underline">True</property>
+                                        <property name="mnemonic_widget">details-export-button</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="GtkButton" id="details-export-button">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">False</property>
+                                        <signal name="clicked" handler="on_pgp_details_export_button"/>
+                                        <child>
+                                          <object class="GtkAlignment" id="alignment8">
+                                            <property name="visible">True</property>
+                                            <property name="xscale">0</property>
+                                            <property name="yscale">0</property>
+                                            <child>
+                                              <object class="GtkBox" id="hbox19">
+                                                <property name="visible">True</property>
+                                                <property name="orientation">horizontal</property>
+                                                <property name="spacing">2</property>
+                                                <child>
+                                                  <object class="GtkImage" id="image10">
+                                                    <property name="visible">True</property>
+                                                    <property name="stock">gtk-save</property>
+                                                  </object>
+                                                  <packing>
+                                                    <property name="expand">False</property>
+                                                    <property name="fill">False</property>
+                                                    <property name="position">0</property>
+                                                  </packing>
+                                                </child>
+                                                <child>
+                                                  <object class="GtkLabel" id="label46">
+                                                    <property name="visible">True</property>
+                                                    <property name="label" 
translatable="yes">Export</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>
+                                            </child>
+                                          </object>
+                                        </child>
+                                      </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>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label22257">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Actions</property>
+                                <attributes>
+                                 <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHSeparator" id="hseparator2">
+                    <property name="visible">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkExpander" id="subkey-expander">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="expanded">True</property>
+                    <child>
+                      <object class="GtkBox" id="hbox10">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkTable" id="table6">
+                            <property name="visible">True</property>
+                            <property name="n_rows">4</property>
+                            <child>
+                              <object class="GtkButton" id="details-add-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_details_add_subkey_button"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment3">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox11">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image5">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-add</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label34">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Add</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"></property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="details-date-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_details_expires_subkey"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment18">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox48">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image30">
+                                            <property name="visible">True</property>
+                                            <property name="icon_name">stock_calendar</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label89">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Expire</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </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="GtkButton" id="details-delete-button">
+                                <property name="label">gtk-delete</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <property name="use_stock">True</property>
+                                <signal name="clicked" handler="on_pgp_details_del_subkey_button"/>
+                              </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="GtkButton" id="details-revoke-button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="border_width">6</property>
+                                <signal name="clicked" handler="on_pgp_details_revoke_subkey_button"/>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment14">
+                                    <property name="visible">True</property>
+                                    <property name="xscale">0</property>
+                                    <property name="yscale">0</property>
+                                    <child>
+                                      <object class="GtkBox" id="hbox30">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">horizontal</property>
+                                        <property name="spacing">2</property>
+                                        <child>
+                                          <object class="GtkImage" id="image16">
+                                            <property name="visible">True</property>
+                                            <property name="stock">gtk-close</property>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">0</property>
+                                          </packing>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" id="label59">
+                                            <property name="visible">True</property>
+                                            <property name="label" translatable="yes">Revoke</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>
+                                    </child>
+                                  </object>
+                                </child>
+                              </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>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="padding">12</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkScrolledWindow" id="scrolledwindow2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="hscrollbar_policy">automatic</property>
+                            <property name="vscrollbar_policy">automatic</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <object class="GtkTreeView" id="details-subkey-tree">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="label">
+                      <object class="GtkLabel" id="label33">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">_Subkeys</property>
+                        <property name="use_underline">True</property>
+                        <attributes>
+                         <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="xpad">3</property>
+                <property name="label" translatable="yes">Details</property>
+              </object>
+              <packing>
+                <property name="position">2</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">
+            <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>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-7">closebutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/pgp-public-key-properties.ui b/pgp/pgp-public-key-properties.ui
new file mode 100644
index 0000000..25b37b1
--- /dev/null
+++ b/pgp/pgp-public-key-properties.ui
@@ -0,0 +1,1666 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.0 on Tue Apr  8 11:14:30 2014 -->
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkListStore" id="model1">
+    <columns>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Unknown</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Never</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Marginally</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Fully</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes" context="Validity">Ultimately</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkDialog" id="pgp-public-key-properties">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Key Properties</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <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">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>
+              </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>
+              </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="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkNotebook" id="notebook">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="border_width">5</property>
+            <child>
+              <object class="GtkBox" id="vbox7">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkBox" id="revoked-area">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkImage" id="image36">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="stock">gtk-dialog-error</property>
+                        <property name="icon_size">6</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox28">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label114">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">This key has been revoked</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="alignment39">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkLabel" id="label115">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">The owner of the key revoked the 
key. It can no longer be used.</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <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">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="expired-area">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkImage" id="image37">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="stock">gtk-dialog-error</property>
+                        <property name="icon_size">6</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox29">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label116">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">This key has expired</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="alignment40">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkLabel" id="expired-message">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label">This key expired on: (placeholder)</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <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>
+                <child>
+                  <object class="GtkBox" id="hbox5">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkBox" id="hbox31">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkBox" id="vbox12">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <child>
+                              <object class="GtkFrame" id="photo-id">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label_xalign">0</property>
+                                <property name="shadow_type">none</property>
+                                <child>
+                                  <object class="GtkAlignment" id="alignment18">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="bottom_padding">6</property>
+                                    <property name="left_padding">6</property>
+                                    <property name="right_padding">6</property>
+                                    <child>
+                                      <object class="GtkBox" id="vbox19">
+                                        <property name="visible">True</property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="spacing">6</property>
+                                        <child>
+                                          <object class="GtkEventBox" id="image_eventbox">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">False</property>
+                                            <signal name="scroll-event" 
handler="on_pgp_owner_photoid_button" swapped="no"/>
+                                            <child>
+                                              <object class="GtkImage" id="photoid">
+                                                <property name="width_request">150</property>
+                                                <property name="height_request">160</property>
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</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="GtkBox" id="key-controls">
+                                            <property name="visible">True</property>
+                                            <property name="orientation">horizontal</property>
+                                            <property name="can_focus">False</property>
+                                            <property name="spacing">6</property>
+                                            <child>
+                                              <object class="GtkLabel" id="add-photo-help">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="xalign">1</property>
+                                              </object>
+                                              <packing>
+                                                <property name="expand">True</property>
+                                                <property name="fill">True</property>
+                                                <property name="position">0</property>
+                                              </packing>
+                                            </child>
+                                            <child>
+                                              <object class="GtkButton" id="owner-photo-previous-button">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">True</property>
+                                                <property name="receives_default">False</property>
+                                                <property name="focus_on_click">False</property>
+                                                <signal name="clicked" handler="on_pgp_owner_photoid_prev" 
swapped="no"/>
+                                                <child>
+                                                  <object class="GtkImage" id="image27">
+                                                    <property name="visible">True</property>
+                                                    <property name="can_focus">False</property>
+                                                    <property name="stock">gtk-go-back</property>
+                                                  </object>
+                                                </child>
+                                              </object>
+                                              <packing>
+                                                <property name="expand">False</property>
+                                                <property name="fill">False</property>
+                                                <property name="position">1</property>
+                                              </packing>
+                                            </child>
+                                            <child>
+                                              <object class="GtkButton" id="owner-photo-next-button">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">True</property>
+                                                <property name="receives_default">False</property>
+                                                <signal name="clicked" handler="on_pgp_owner_photoid_next" 
swapped="no"/>
+                                                <child>
+                                                  <object class="GtkAlignment" id="alignment17">
+                                                    <property name="visible">True</property>
+                                                    <property name="can_focus">False</property>
+                                                    <property name="xscale">0</property>
+                                                    <property name="yscale">0</property>
+                                                    <child>
+                                                      <object class="GtkBox" id="hbox44">
+                                                        <property name="visible">True</property>
+                                                        <property name="orientation">horizontal</property>
+                                                        <property name="can_focus">False</property>
+                                                        <property name="spacing">2</property>
+                                                        <child>
+                                                          <object class="GtkImage" id="image24">
+                                                            <property name="visible">True</property>
+                                                            <property name="can_focus">False</property>
+                                                            <property name="stock">gtk-go-forward</property>
+                                                          </object>
+                                                          <packing>
+                                                            <property name="expand">False</property>
+                                                            <property name="fill">False</property>
+                                                            <property name="position">0</property>
+                                                          </packing>
+                                                        </child>
+                                                        <child>
+                                                          <object class="GtkLabel" id="label85">
+                                                            <property name="visible">True</property>
+                                                            <property name="can_focus">False</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>
+                                                    </child>
+                                                  </object>
+                                                </child>
+                                              </object>
+                                              <packing>
+                                                <property name="expand">False</property>
+                                                <property name="fill">False</property>
+                                                <property name="position">2</property>
+                                              </packing>
+                                            </child>
+                                          </object>
+                                          <packing>
+                                            <property name="expand">False</property>
+                                            <property name="fill">False</property>
+                                            <property name="position">1</property>
+                                          </packing>
+                                        </child>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child type="label">
+                                  <object class="GtkLabel" id="label89">
+                                    <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" comments="A photograph">Photo 
</property>
+                                    <attributes>
+                                      <attribute name="weight" value="bold"/>
+                                    </attributes>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">12</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox38">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkTable" id="table9">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="border_width">3</property>
+                            <property name="n_rows">6</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">12</property>
+                            <property name="row_spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label22227">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22228">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Encrypt files and email to the 
key’s owner</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label93">
+                                <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">Key ID:</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="top_attach">5</property>
+                                <property name="bottom_attach">6</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="owner-keyid-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</property>
+                                <property name="selectable">True</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">5</property>
+                                <property name="bottom_attach">6</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label72">
+                                <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" context="name-of-key" 
comments="Name of key, usually a persons name">Name:</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label74">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Email:</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label91">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Comment:</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22229">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">1</property>
+                                <property name="label" translatable="yes">Type:</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="top_attach">4</property>
+                                <property name="bottom_attach">5</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="owner-name-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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="y_options"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="owner-email-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="owner-comment-label">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="xalign">0</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"/>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="label22230">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Public PGP Key</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">4</property>
+                                <property name="bottom_attach">5</property>
+                                <property name="x_options">GTK_FILL</property>
+                                <property name="y_options"/>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkExpander" id="uids-area">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child>
+                              <object class="GtkAlignment" id="alignment23">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="top_padding">3</property>
+                                <child>
+                                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                                    <property name="height_request">130</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="hscrollbar_policy">never</property>
+                                    <property name="shadow_type">in</property>
+                                    <child>
+                                      <object class="GtkTreeView" id="owner-userid-tree">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="headers_visible">False</property>
+                                        <child internal-child="selection">
+                                          <object class="GtkTreeSelection" id="treeview-selection1"/>
+                                        </child>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child type="label">
+                              <object class="GtkLabel" id="label28">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes" comments="Names set on the PGP 
key.">_Other Names:</property>
+                                <property name="use_underline">True</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <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="can_focus">False</property>
+                <property name="xpad">3</property>
+                <property name="label" translatable="yes">Owner</property>
+              </object>
+              <packing>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="trust-page">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkBox" id="hbox59">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">12</property>
+                    <child>
+                      <object class="GtkImage" id="sign-image">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="yalign">0</property>
+                        <property name="stock">gtk-dialog-info</property>
+                        <property name="icon_size">6</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox36">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label22225">
+                            <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">Your trust of this key</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="alignment43">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkBox" id="vbox37">
+                                <property name="visible">True</property>
+                                <property name="orientation">vertical</property>
+                                <property name="can_focus">False</property>
+                                <property name="spacing">6</property>
+                                <child>
+                                  <object class="GtkLabel" id="manual-trust-area">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="xalign">0</property>
+                                    <property name="xpad">4</property>
+                                    <property name="label" translatable="yes">Your trust is manually 
specified on the &lt;i&gt;Details&lt;/i&gt; tab.</property>
+                                    <property name="use_markup">True</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">False</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox" id="manage-trust-area">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">vertical</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="spacing">6</property>
+                                    <child>
+                                      <object class="GtkCheckButton" id="trust-marginal-check">
+                                        <property name="label" translatable="yes">I trust signatures from 
“%s” on other keys</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">False</property>
+                                        <property name="xalign">0</property>
+                                        <property name="draw_indicator">True</property>
+                                        <signal name="toggled" handler="on_pgp_trust_marginal_toggled" 
swapped="no"/>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">0</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox" id="sign-area">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">horizontal</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="spacing">12</property>
+                                    <child>
+                                      <object class="GtkLabel" id="trust-sign-label">
+                                        <property name="width_request">270</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="xalign">0</property>
+                                        <property name="label" translatable="yes">If you believe that the 
person that owns this key is “%s”, &lt;i&gt;sign&lt;/i&gt; this key:</property>
+                                        <property name="use_markup">True</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="alignment45">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="xalign">1</property>
+                                        <property name="yalign">0</property>
+                                        <property name="xscale">0</property>
+                                        <property name="yscale">0</property>
+                                        <child>
+                                          <object class="GtkButton" id="sign-button">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <signal name="clicked" handler="on_pgp_trust_sign" swapped="no"/>
+                                            <child>
+                                              <object class="GtkAlignment" id="alignment22">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="xscale">0</property>
+                                                <property name="yscale">0</property>
+                                                <child>
+                                                  <object class="GtkBox" id="hbox49">
+                                                    <property name="visible">True</property>
+                                                    <property name="orientation">horizontal</property>
+                                                    <property name="can_focus">False</property>
+                                                    <property name="spacing">2</property>
+                                                    <child>
+                                                      <object class="GtkImage" id="image30">
+                                                        <property name="visible">True</property>
+                                                        <property name="can_focus">False</property>
+                                                        <property name="stock">gtk-index</property>
+                                                      </object>
+                                                      <packing>
+                                                        <property name="expand">False</property>
+                                                        <property name="fill">False</property>
+                                                        <property name="position">0</property>
+                                                      </packing>
+                                                    </child>
+                                                    <child>
+                                                      <object class="GtkLabel" id="label101">
+                                                        <property name="visible">True</property>
+                                                        <property name="can_focus">False</property>
+                                                        <property name="label" translatable="yes">_Sign this 
Key</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>
+                                                </child>
+                                              </object>
+                                            </child>
+                                          </object>
+                                        </child>
+                                      </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="fill">True</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkBox" id="revoke-area">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">horizontal</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="spacing">12</property>
+                                    <child>
+                                      <object class="GtkLabel" id="trust-revoke-label">
+                                        <property name="width_request">270</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="xalign">0</property>
+                                        <property name="label" translatable="yes">If you no longer trust 
that “%s” owns this key, &lt;i&gt;revoke&lt;/i&gt; your signature:</property>
+                                        <property name="use_markup">True</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="alignment44">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="xalign">1</property>
+                                        <property name="yalign">0</property>
+                                        <property name="xscale">0</property>
+                                        <property name="yscale">0</property>
+                                        <child>
+                                          <object class="GtkButton" id="revoke-button">
+                                            <property name="visible">True</property>
+                                            <property name="sensitive">False</property>
+                                            <property name="can_focus">True</property>
+                                            <property name="receives_default">False</property>
+                                            <child>
+                                              <object class="GtkAlignment" id="alignment25">
+                                                <property name="visible">True</property>
+                                                <property name="can_focus">False</property>
+                                                <property name="xscale">0</property>
+                                                <property name="yscale">0</property>
+                                                <child>
+                                                  <object class="GtkBox" id="hbox51">
+                                                    <property name="visible">True</property>
+                                                    <property name="orientation">horizontal</property>
+                                                    <property name="can_focus">False</property>
+                                                    <property name="spacing">2</property>
+                                                    <child>
+                                                      <object class="GtkImage" id="image32">
+                                                        <property name="visible">True</property>
+                                                        <property name="can_focus">False</property>
+                                                        <property name="stock">gtk-remove</property>
+                                                      </object>
+                                                      <packing>
+                                                        <property name="expand">False</property>
+                                                        <property name="fill">False</property>
+                                                        <property name="position">0</property>
+                                                      </packing>
+                                                    </child>
+                                                    <child>
+                                                      <object class="GtkLabel" id="label104">
+                                                        <property name="visible">True</property>
+                                                        <property name="can_focus">False</property>
+                                                        <property name="label" translatable="yes">_Revoke 
Signature</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>
+                                                </child>
+                                              </object>
+                                            </child>
+                                          </object>
+                                        </child>
+                                      </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="fill">False</property>
+                                    <property name="position">3</property>
+                                  </packing>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <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">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="signatures-area">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label99">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_People who have signed this 
key:</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">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkAlignment" id="alignment20">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="left_padding">12</property>
+                        <child>
+                          <object class="GtkBox" id="vbox22">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkScrolledWindow" id="scrolledwindow4">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="shadow_type">in</property>
+                                <child>
+                                  <object class="GtkTreeView" id="signatures-tree">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <signal name="row-activated" handler="on_pgp_signature_row_activated" 
swapped="no"/>
+                                    <child internal-child="selection">
+                                      <object class="GtkTreeSelection" id="treeview-selection2"/>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkCheckButton" id="signatures-toggle">
+                                <property name="label" translatable="yes">_Only display the signatures of 
people I trust</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="xalign">0.5</property>
+                                <property name="draw_indicator">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="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label96">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xpad">3</property>
+                <property name="label" translatable="yes">Trust</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="vbox8">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">12</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkBox" id="hbox53">
+                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">12</property>
+                    <property name="homogeneous">True</property>
+                    <child>
+                      <object class="GtkBox" id="vbox27">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">18</property>
+                        <child>
+                          <object class="GtkBox" id="vbox32">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label108">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Technical Details:</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="alignment29">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkTable" id="table10">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="n_rows">3</property>
+                                    <property name="n_columns">2</property>
+                                    <property name="column_spacing">12</property>
+                                    <property name="row_spacing">6</property>
+                                    <child>
+                                      <object class="GtkLabel" id="label24">
+                                        <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">Key ID:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-id-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="right_attach">2</property>
+                                        <property name="y_options"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label25">
+                                        <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">Strength:</property>
+                                      </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"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label109">
+                                        <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">Type:</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"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-algo-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</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="y_options"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-strength-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="yalign">0</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="y_options"/>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <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">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="vbox33">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label113">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Fingerprint:</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="alignment32">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkLabel" id="details-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">PLAC EHOL DER0 9023 435F
+B4C3 4349 0932 7854 3452</property>
+                                    <property name="use_markup">True</property>
+                                    <property name="selectable">True</property>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <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>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="vbox26">
+                        <property name="visible">True</property>
+                        <property name="orientation">vertical</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">18</property>
+                        <child>
+                          <object class="GtkBox" id="vbox34">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label110">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Dates:</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="alignment30">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkTable" id="table11">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</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="GtkLabel" id="label17">
+                                        <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">Created:</property>
+                                      </object>
+                                      <packing>
+                                        <property name="x_options">GTK_FILL</property>
+                                        <property name="y_options"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="label18">
+                                        <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">Expires:</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"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-created-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</property>
+                                        <property name="selectable">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="right_attach">2</property>
+                                        <property name="y_options"/>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel" id="details-expires-label">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="xalign">0</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="y_options"/>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <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">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox" id="vbox35">
+                            <property name="visible">True</property>
+                            <property name="orientation">vertical</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label111">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Indicate 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="alignment31">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="left_padding">12</property>
+                                <child>
+                                  <object class="GtkBox" id="hbox54">
+                                    <property name="visible">True</property>
+                                    <property name="orientation">horizontal</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="spacing">12</property>
+                                    <child>
+                                      <object class="GtkLabel" id="label112">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="label" translatable="yes">You _Trust the 
Owner:</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="GtkComboBox" id="details-trust-combobox">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="model">model1</property>
+                                        <signal name="changed" handler="on_pgp_details_trust_changed" 
swapped="no"/>
+                                        <child>
+                                          <object class="GtkCellRendererText" id="renderer1"/>
+                                          <attributes>
+                                            <attribute name="text">0</attribute>
+                                          </attributes>
+                                        </child>
+                                      </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="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>
+                      <packing>
+                        <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">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkExpander" id="subkey-expander">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child>
+                      <object class="GtkBox" id="hbox10">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkAlignment" id="alignment28">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="top_padding">6</property>
+                            <property name="left_padding">12</property>
+                            <child>
+                              <object class="GtkScrolledWindow" id="scrolledwindow2">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="shadow_type">in</property>
+                                <child>
+                                  <object class="GtkTreeView" id="details-subkey-tree">
+                                    <property name="height_request">100</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <child internal-child="selection">
+                                      <object class="GtkTreeSelection" id="treeview-selection3"/>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="label">
+                      <object class="GtkLabel" id="label33">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">_Subkeys</property>
+                        <property name="use_underline">True</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label22222">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xpad">3</property>
+                <property name="label" translatable="yes">Details</property>
+              </object>
+              <packing>
+                <property name="position">2</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-11">helpbutton1</action-widget>
+      <action-widget response="-7">closebutton1</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/pgp-signature.vala b/pgp/pgp-signature.vala
new file mode 100644
index 0000000..0e6f42e
--- /dev/null
+++ b/pgp/pgp-signature.vala
@@ -0,0 +1,54 @@
+/*
+ * 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 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.Pgp.Signature : GLib.Object {
+
+    /**
+     * GPG Key ID
+     */
+    [Notify]
+    public string keyid { get; set; }
+
+    /**
+     * PGP signature type
+     */
+    public uint sigtype {
+        get {
+            Pgp.Key? key = Backend.get_default_keyring(null).lookup(this.keyid);
+
+            if (key != null) {
+                if (key.usage == Seahorse.Usage.PRIVATE_KEY)
+                    return SKEY_PGPSIG_TRUSTED | SKEY_PGPSIG_PERSONAL;
+                if (Seahorse.Flags.TRUSTED in key.object_flags)
+                    return SKEY_PGPSIG_TRUSTED;
+            }
+
+            return 0;
+        }
+    }
+
+
+    public Signature(string keyid) {
+        this.keyid = keyid;
+
+        this.notify["keyid"].connect(() => notify_property("sigtype"));
+        this.notify["flags"].connect(() => notify_property("sigtype"));
+    }
+}
diff --git a/pgp/pgp-subkey.vala b/pgp/pgp-subkey.vala
new file mode 100644
index 0000000..2b8d804
--- /dev/null
+++ b/pgp/pgp-subkey.vala
@@ -0,0 +1,135 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2017 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.Pgp.SubKey : GLib.Object {
+
+    // XXX notify properties?
+
+    /**
+     * PGP subkey index
+     */
+    public uint index { get; set; }
+
+    /**
+     * GPG Key ID
+     */
+    public string keyid { get; set; }
+
+    /**
+     * PGP subkey flags
+     */
+    public Seahorse.Flags flags { get; set; }
+
+    /**
+     * PGP key length
+     */
+    public uint length { get; set; }
+
+    /**
+     * GPG Algorithm
+     */
+    public string algorithm { get; set; }
+
+    /**
+     * Date this key was created on
+     */
+    public ulong created { get; set; }
+
+    /**
+     * Date this key expires on
+     */
+    public ulong expires { get; set; }
+
+    /**
+     * Key Description
+     */
+    public string description { get; set; }
+
+    /**
+     * PGP Key Fingerprint
+     */
+    public string fingerprint { get; set; }
+
+    public SubKey() {
+    }
+
+    private struct FlagName {
+        public uint flag;
+        public string name;
+    }
+
+    private const FlagName[] flag_names = {
+        { Seahorse.Flags.CAN_ENCRYPT,      _("Encrypt")      },
+        { Seahorse.Flags.CAN_SIGN,         _("Sign")         },
+        { Seahorse.Flags.CAN_CERTIFY,      _("Certify")      },
+        { Seahorse.Flags.CAN_AUTHENTICATE, _("Authenticate") }
+    };
+
+    public string get_usage() {
+        StringBuilder str = new StringBuilder();
+
+        bool previous = false;
+        foreach (FlagName flag_name in flag_names) {
+            if (flag_name.flag in this.flags) {
+                if (previous)
+                    str.append(", ");
+
+                previous = true;
+                str.append(_(flag_name.name));
+            }
+        }
+
+        return str.str;
+    }
+
+    public string calc_description(string? name, uint index) {
+        if (name == null)
+            name = _("Key");
+
+        if (index == 0)
+            return name;
+
+        return _("Subkey %u of %s").printf(index, name);
+    }
+
+    /**
+     * Takes runs of hexadecimal digits, possibly with whitespace among them, and
+     * formats them nicely in groups of four digits.
+     */
+    public string? calc_fingerprint(string? raw_fingerprint) {
+        if (raw_fingerprint == null)
+            return null;
+
+        StringBuilder result = new StringBuilder("");
+
+        uint num_digits = 0;
+        for (uint i = 0; i < raw_fingerprint.length; i++) {
+            if (raw_fingerprint[i].isxdigit()) {
+                result.append_c(raw_fingerprint[i].toupper());
+                num_digits++;
+
+                if (num_digits > 0 && (num_digits % 4 == 0))
+                    result.append(" ");
+            }
+        }
+
+        return result.str.chomp();
+    }
+}
diff --git a/pgp/pgp-uid.vala b/pgp/pgp-uid.vala
new file mode 100644
index 0000000..65893c9
--- /dev/null
+++ b/pgp/pgp-uid.vala
@@ -0,0 +1,262 @@
+/*
+ * 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 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.Pgp.Uid : Object {
+    /**
+     * Parent key
+     */
+    public Key parent { get; set construct; }
+
+    /**
+     * Signatures on this UID
+     */
+    public List<Pgp.Signature> signatures {
+        get { return this._signatures; }
+        set { set_signatures(value); }
+    }
+    private List<Pgp.Signature> _signatures;
+
+    /**
+     * Validity of this identity
+     */
+    public Seahorse.Validity validity { get; set; }
+
+    /**
+     * User ID name
+     */
+    public string name {
+        get { return this._name; }
+        set { set_name(value); }
+    }
+    private string _name = "";
+
+    /**
+     * User ID email
+     */
+    public string email {
+        get { return this._email; }
+        set { set_email(value); }
+    }
+    private string _email = "";
+
+    /**
+     * User ID comment (usually the email address)
+     */
+    public string comment {
+        get { return this._comment; }
+        set { set_comment(value); }
+    }
+    private string _comment = "";
+
+    private bool realized; //XXX
+
+    /**
+     * Creates a new OpenPGP Uid.
+     *
+     * @parent: The {@link PGP.Key} this Uid belongs to.
+     * @uid_string: The Uid in textual form.
+     */
+    public Uid(Key parent, string? uid_string) {
+        this.parent = parent;
+
+        if (uid_string != null)
+            parse_user_id(uid_string, out this._name, out this._email, out this._comment);
+
+        this.icon = null;
+        this.usage = Seahorse.Usage.IDENTITY;
+
+        realize();
+
+        return uid;
+    }
+
+    public void realize() {
+        // Don't realize if no name present
+        if (!this.name)
+            return;
+
+        this.realized = true;
+
+        this.label = calc_label(this.name, this.email, this.comment);
+        this.markup = calc_markup(this.name, this.email, this.comment, 0);
+    }
+
+    public void set_signatures(List<Signature> signatures) {
+        this._signatures = signatures.copy();
+        notify_property("signatures"); 
+    }
+
+    public void set_name(string name) {
+        this._name = convert_string(name);
+
+        if (!this.realized)
+            realize();
+        notify_property("name");
+    }
+
+    public void set_comment(string comment) {
+        this._comment = convert_string(comment);
+
+        if (!this.realized)
+            realize();
+        notify_property("comment");
+    }
+
+    public void set_email(string email) {
+        this._email = convert_string(email);
+
+        if (!this.realized)
+            realize();
+        notify_property("email");
+    }
+
+    public string calc_label(string? name, string? email, string? comment) {
+        if (name == null)
+            return null;
+
+        StringBuilder label = new StringBuilder(name);
+        if (email != null && email != "") {
+            label.append(" <");
+            label.append(email);
+            label.append(">");
+        }
+
+        if (comment != null && comment != "") {
+            label.append(" (");
+            label.append(comment);
+            label.append(")");
+        }
+
+        return label.str;
+    }
+
+    public string calc_markup (string? name, string? email, string? comment, Seahorse.Flags flags) {
+        if (name == null)
+            return null;
+
+        bool strike = (Seahorse.Flags.EXPIRED in flags
+                       || Seahorse.Flags.REVOKED in flags
+                       || Seahorse.Flags.DISABLED in flags);
+
+        bool grayed = !(Seahorse.Flags.TRUSTED in flags);
+
+        string format;
+        if (strike && grayed)
+            format = "<span strikethrough='true' foreground='#555555'>%s<span size='small' 
rise='0'>%s%s%s%s%s</span></span>";
+        else if (grayed)
+            format = "<span foreground='#555555'>%s<span size='small' rise='0'>%s%s%s%s%s</span></span>";
+        else if (strike)
+            format = "<span strikethrough='true'>%s<span foreground='#555555' size='small' 
rise='0'>%s%s%s%s%s</span></span>";
+        else
+            format = "%s<span foreground='#555555' size='small' rise='0'>%s%s%s%s%s</span>";
+
+        return Markup.printf_escaped(format, name,
+                      email && email[0] ? "  " : "",
+                      email && email[0] ? email : "",
+                      comment && comment[0] ? "  '" : "",
+                      comment && comment[0] ? comment : "",
+                      comment && comment[0] ? "'" : "");
+    }
+
+    public Quark calc_id (Quark key_id, uint index) {
+        string str = "%s:%u".printf(key_id.to_string(), index + 1);
+        Quark id = Quark.from_string(str);
+
+        return id;
+    }
+
+    // Copied from GPGME, later turned into Vala
+    // Parses a user id, such as "Niels De Graef (optional random comment) <nielsdegraef gmail com>"
+    private void parse_user_id (string uid, out string name, out string email, out string comment) {
+        string src = uid;
+        string tail = uid;
+        string x = uid;
+        bool in_name = false;
+        int in_email = 0;
+        int in_comment = 0;
+
+        while(src != null) {
+            if (in_email) {
+                if (src[0] == '<')
+                    // Not legal but anyway.
+                    in_email++;
+                else if (src[0] == '>') {
+                    if (!--in_email && !*email) {
+                        *email = tail;
+                        src[0] = 0;
+                        tail = src + 1;
+                    }
+                }
+            } else if (in_comment) {
+                if (src[0] == '(')
+                    in_comment++;
+                else if (src[0] == ')') {
+                    if (!--in_comment && !*comment) {
+                        *comment = tail;
+                        src[0] = 0;
+                        tail = src + 1;
+                    }
+                }
+            } else if (src[0] == '<') {
+                if (in_name) {
+                    if (!*name) {
+                        *name = tail;
+                        src[0] = 0;
+                        tail = src + 1;
+                    }
+                    in_name = false;
+                } else
+                    tail = src + 1;
+
+                in_email = 1;
+            } else if (src[0] == '(') {
+                if (in_name) {
+                    if (!*name) {
+                        *name = tail;
+                        src[0] = 0;
+                        tail = src + 1;
+                    }
+                    in_name = false;
+                }
+                in_comment = 1;
+            } else if (!in_name && src[0] != ' ' && src[0] != '\t') {
+                in_name = true;
+            }
+            src++;
+        }
+
+        if (in_name) {
+            if (!*name) {
+                *name = tail;
+                src[0] = 0;
+                tail = src + 1;
+            }
+        }
+
+        // Let unused parts point to an EOS.
+        name = name ?? "";
+        email = email ?? "";
+        comment = comment ?? "";
+
+        name._strip();
+        email._strip();
+        comment._strip();
+    }
+}
diff --git a/pgp/pgp.vala b/pgp/pgp.vala
new file mode 100644
index 0000000..01d8b0e
--- /dev/null
+++ b/pgp/pgp.vala
@@ -0,0 +1,28 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * 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/>.
+ */
+
+namespace Seahorse.Pgp {
+
+public const string SEAHORSE_PGP_NAME = "openpgp";
+
+/* #ifdef WITH_PGP */
+/* void       seahorse_pgp_backend_initialize    (void); */
+/* #endif */
+}
diff --git a/pgp/seahorse-gpg-options.c b/pgp/seahorse-gpg-options.c
index a5ade38..c9c8ee9 100644
--- a/pgp/seahorse-gpg-options.c
+++ b/pgp/seahorse-gpg-options.c
@@ -179,62 +179,6 @@ free_string_array (GArray *array)
 
 #define HOME_PREFIX "\nHome: "
 
-/* Discovers .gnupg home directory by running gpg */
-static gboolean
-parse_home_directory (gpgme_engine_info_t engine, GError **err)
-{
-    gboolean found = FALSE;
-    gchar *sout = NULL;
-    gchar *serr = NULL;
-    gchar *t;
-    gchar *x;
-    gint status;
-    gboolean b;
-
-    g_assert (engine);
-    g_assert (engine->file_name);
-
-    /* We run /usr/bin/gpg --version */
-    t = g_strconcat (engine->file_name, " --version", NULL);
-    b = g_spawn_command_line_sync (t, &sout, &serr, &status, err);
-    g_free (t);
-
-    if (b) {
-        if (sout && WIFEXITED (status) && WEXITSTATUS (status) == 0) {
-            /* Look for Home: */
-            t = strstr (sout, HOME_PREFIX);
-            if (t != NULL) {
-                t += strlen (HOME_PREFIX);
-                x = strchr (t, '\n');
-                if (x != NULL && x != t) {
-                    *x = 0;
-                    g_strstrip (t);
-
-                    g_free (gpg_homedir);
-
-                    /* If it's not a rooted path then expand */
-                    if (t[0] == '~') {
-                        gpg_homedir = g_strconcat (g_get_home_dir(), ++t, NULL);
-                    } else {
-                        gpg_homedir = g_strdup (t);
-                    }
-                    found = TRUE;
-                }
-            }
-        }
-
-        if (!found)
-            b = FALSE;
-    }
-
-    if (sout)
-        g_free (sout);
-    if (serr)
-        g_free (serr);
-
-    return b;
-}
-
 /* Initializes the gpg-options static info */
 static gboolean
 gpg_options_init (GError **err)
@@ -265,8 +209,10 @@ gpg_options_init (GError **err)
        }
 
        /* Now run the binary and read in the home directory */
-       if (!parse_home_directory (engine, err))
-               return FALSE;
+       if (engine->homedir)
+        gpg_homedir = engine->home_dir;
+    else
+               gpg_homedir = gpgmet_get_dirinfo("homedir");
 
        gpg_options_inited = TRUE;
        return TRUE;
diff --git a/pgp/server-source.vala b/pgp/server-source.vala
new file mode 100644
index 0000000..bcbf2c7
--- /dev/null
+++ b/pgp/server-source.vala
@@ -0,0 +1,173 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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 {
+// XXX correct namespace?
+
+/**
+ * Objects for handling of internet sources of keys (hkp/ldap)
+ */
+public abstract class ServerSource : GLib.Object, Gcr.Collection, Seahorse.Place {
+
+    /**
+     * Key Server to search on
+     */
+    public string server { get; construct; }
+
+    /**
+     * Key Server full URI
+     */
+    public string uri { get; set; }
+
+    public Icon icon {
+        owned get { return new ThemedIcon(Gtk.STOCK.DIALOG_QUESTION); }
+    }
+
+    public Gtk.ActionGroup? actions {
+        owned get { return null; }
+    }
+
+    /**
+     * Creates a #SeahorseServerSource object out of @server. Depending
+     * on the defines at compilation time other sources are supported
+     * (ldap, hkp)
+     *
+     * XXX This is a factory method, not a constructor
+     *
+     * @param server The server uri to create an object for
+     */
+    public ServerSource? create(string? server) {
+        if(server != null && server != "")
+            return null;
+
+
+        string scheme, host;
+        if (!parse_keyserver_uri (server, out scheme, out host)) {
+            warning ("invalid uri passed: %s", server);
+            return null;
+        }
+
+        switch (scheme.down()) {
+
+            case "ldap":
+                if (Config.WITH_LDAP)
+                    return new LdapSource(server, host);
+                break;
+
+            case "hkp":
+                if (Config.WITH_HKP)
+                    return new HkpSource(server, host);
+                break;
+
+            case "http":
+            case "https":
+                if (Config.WITH_HKP) {
+                    // If there's already a port, use that
+                    if (':' in server)
+                        return new HkpSource(server, host);
+
+                    // Otherwise, use default ports
+                    int port = 80 : 443;
+                    return new HkpSource("%s:%d".printf(server, port), host);
+                }
+                break;
+
+            default:
+                message("Unsupported key server uri scheme: %s", scheme);
+        }
+
+        return null;
+    }
+
+    /* public async bool load(GLib.Cancellable? cancellable) throws GLib.Error { */
+    /*     return false; */
+    /* } */
+
+    public uint get_length() {
+        return 0;
+    }
+
+    public List<GLib.Object> get_objects() {
+        return null;
+    }
+
+    public bool contains(GLib.Object object) {
+        return false;
+    }
+
+    /* --------------------------------------------------------------------------
+     * METHODS
+     */
+
+    /**
+     * Code adapted from GnuPG (file g10/keyserver.c)
+     *
+     * @param uri the uri to parse
+     * @param scheme the scheme ("http") of this uri
+     * @param host the host part of the uri
+     *
+     * @return false if the separation failed
+     */
+    private bool parse_keyserver_uri(string uri, out string scheme, out string host) {
+        assert(uri != null);
+        assert(scheme != null && host != null);
+
+        int assume_ldap = 0;
+
+        *scheme = NULL;
+        *host = NULL;
+
+        /* Get the scheme */
+        *scheme = strsep(&uri, ":");
+        if (uri == NULL) {
+            /* Assume LDAP if there is no scheme */
+            assume_ldap = 1;
+            uri = (char*)*scheme;
+            *scheme = "ldap";
+        }
+
+        if (assume_ldap || (uri[0] == '/' && uri[1] == '/')) {
+            // Two slashes means network path.
+
+            // Skip over the "//", if any
+            if (!assume_ldap)
+                uri += 2;
+
+            // Get the host
+            *host = strsep (&uri, "/");
+            if (*host[0] == '\0')
+                return false;
+        }
+
+        if (*scheme[0] == '\0')
+            return false;
+
+        return true;
+    }
+
+    public abstract async bool search_async(string match, Gcr.SimpleCollection results, Cancellable 
cancellable) throws GLib.Error;
+
+    public abstract async void* export_async(string[] keyids, Cancellable cancellable) throws GLib.Error;
+
+    public abstract async List import_async(GLib.InputStream input, Cancellable cancellable) throws 
GLib.Error;
+}
+
+}
diff --git a/pgp/signer.ui b/pgp/signer.ui
new file mode 100644
index 0000000..3a2e7fc
--- /dev/null
+++ b/pgp/signer.ui
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkDialog" id="signer">
+    <property name="border_width">5</property>
+    <property name="title">Choose Signing Key</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="type_hint">normal</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Sign message with key:</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="GtkHBox" id="sign_key_place">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkComboBox" id="signer-select">
+                    <property name="visible">True</property>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</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">gtk-ok</property>
+                <property name="visible">True</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="use_stock">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>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel</action-widget>
+      <action-widget response="-5">ok</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/pgp/signer.vala b/pgp/signer.vala
new file mode 100755
index 0000000..c8dc3da
--- /dev/null
+++ b/pgp/signer.vala
@@ -0,0 +1,86 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004-2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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.Pgp.Signer : Gtk.Dialog {
+
+    private Gtk.ComboBox signer_select;
+
+    public Pgp.Key seahorse_signer_get (GtkWindow *parent) {
+        Gcr.Collection collection = seahorse_keyset_pgp_signers_new ();
+        uint nkeys = collection.get_length (collection);
+
+        // If no signing keys then we can't sign
+        if (nkeys == 0) {
+            // TODO: We should be giving an error message that allows them to generate or import a key
+            Seahorse.Util.show_error(null, _("No keys usable for signing"),
+                    _("You have no personal PGP keys that can be used to sign a document or message."));
+            return null;
+        }
+
+        // If only one key (probably default) then return it immediately
+        if (nkeys == 1)
+            return collection.get_objects().data;
+
+        Seahorse.Widget swidget = seahorse_widget_new ("signer", parent);
+
+        Gtk.ComboBox combo = GTK_WIDGET (seahorse_widget_get_widget (swidget, "signer-select"));
+        g_return_val_if_fail (combo != null, null);
+        ComboKeys.attach(combo, collection, null);
+
+        GLib.Settings settings = Application.pgp_settings();
+
+        // Select the last key used
+        string id = settings.get_string("last-signer");
+        string keyid;
+        if (!id || !id[0])
+            keyid = null;
+        else if (id.has_prefix("openpgp:"))
+            keyid = id + strlen ("openpgp:");
+        else
+            keyid = id;
+        ComboKeys.set_active_id(combo, keyid);
+
+        show();
+
+        bool done = false, ok = false;
+        while (!done) {
+            switch (run()) {
+                case Gtk.ResponseType.HELP:
+                    break;
+                case Gtk.ResponseType.OK:
+                    ok = true;
+                default:
+                    done = true;
+                    break;
+            }
+        }
+
+        Pgp.Key? key = null;
+        if (ok) {
+            key = ComboKeys.get_active(combo);
+
+            // Save this as the last key signed with
+            settings.set_string("last-signer", (key != null)? key.keyid : null);
+        }
+
+        destroy();
+        return key;
+    }
+}
diff --git a/pgp/transfer.vala b/pgp/transfer.vala
new file mode 100644
index 0000000..e10c8c7
--- /dev/null
+++ b/pgp/transfer.vala
@@ -0,0 +1,127 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ * Copyright (C) 2017 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/>.
+ */
+
+// XXX class or namespace?
+// Also, maybe should be in GpgME.Keyring/ServerSource classes?
+namespace Seahorse.Transfer {
+
+public void transfer_keys_async(Place from, Place to, List keys, Cancellable? cancellable) {
+    if (keys == null)
+        return;
+
+    string[] keyids = new string[keys.length];
+
+    if (from is GpgME.Keyring) {
+        keys = seahorse_object_list_copy (keys);
+    } else {
+        int i = 0;
+        foreach (Pgp.Key key in keys) {
+            keyids[i] = key.keyid;
+            i++;
+        }
+    }
+
+    Seahorse.Progress.prep(cancellable, from,
+                           (from is GpgME.Keyring)? _("Exporting data") : _("Retrieving data"));
+    Seahorse.Progress.prep(cancellable, to,
+                           (to is GpgME.Keyring)? _("Importing data") : _("Sending data"));
+
+    debug("starting export");
+
+    // We delay and continue from a callback
+    g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 0,
+                                on_timeout_start_transfer,
+                                g_object_ref (res), g_object_unref);
+}
+
+public void transfer_keyids_async(ServerSource from, Place to, string[] keyids,
+                                  Cancellable? cancellable) throws GLib.Error {
+    if (keyids == null || keyids.length == 0)
+        return;
+
+    Seahorse.Progress.prep(cancellable, from,
+                           (from is GpgME.Keyring)? _("Exporting data") : _("Retrieving data"));
+    Seahorse.Progress.prep(cancellable, to,
+                           (to is GpgME.Keyring)? _("Importing data") : _("Sending data"));
+
+    debug("starting export");
+
+    /* We delay and continue from a callback */
+    // XXX do this with Thread.run()
+    g_timeout_add_seconds_full (G_PRIORITY_DEFAULT, 0,
+                                on_timeout_start_transfer,
+                                g_object_ref (res), g_object_unref);
+}
+
+private async void on_timeout_start_transfer() {
+    GError *error = null;
+
+    Seahorse.Progress.begin (closure.cancellable, from);
+
+    uint8[] stream_data;
+    if (from is ServerSource) {
+        stream_data = yield ((ServerSource) from).export_async(keyids, cancellable);
+    } else if (from is GpgME.Keyring) {
+        Seahorse.Exporter exporter = new GpgME.Exporter.multiple(keys, true);
+        stream_data = yield exporter.export(cancellable);
+    } else {
+        warning("unsupported source for transfer: %s", from.get_type().name());
+        return;
+    }
+
+    debug("export done");
+    Seahorse.Progress.end(closure.cancellable, from);
+
+    if (error == null) // XXX correct?
+        g_cancellable_set_error_if_cancelled (cancellable, &error);
+
+    if (error != null) {
+        debug("stopped after export");
+        g_simple_async_result_take_error (res, error);
+        return; // XXX or throw?
+    }
+
+    Seahorse.Progress.begin(cancellable, to);
+
+    if (stream_data.length == 0) {
+        debug("nothing to import");
+        Seahorse.Progress.end(cancellable, to);
+        return;
+    }
+
+    debug("starting import");
+    InputStream input = new MemoryInputStream.from_data(stream_data);
+    if (to is GpgMe.Keyring)
+        yield ((GpgMe.Keyring) to).import_async(input, cancellable);
+    else
+        yield ((ServerSource) to).import_async(input, cancellable);
+    debug("import done");
+
+    Seahorse.Progress.end(closure.cancellable, &closure.to);
+
+    if (results != null)
+        g_cancellable_set_error_if_cancelled (closure.cancellable, &error);
+
+    if (error != null)
+        g_simple_async_result_take_error (res, error);
+}
+
+}
diff --git a/pgp/unknown-source.vala b/pgp/unknown-source.vala
new file mode 100644
index 0000000..5d5c34e
--- /dev/null
+++ b/pgp/unknown-source.vala
@@ -0,0 +1,83 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * 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.UnknownSource : GLib.Object, Gcr.Collection, Seahorse.Place {
+
+    GLib.Object parent;
+    HashTable<string, Object> keys;
+
+    public string label {
+        owned get { return ""; }
+    }
+
+    public string description {
+        owned get { return null; }
+    }
+
+    public string uri {
+        owned get { return null; }
+    }
+
+    public Icon icon {
+        owned get { return null; }
+    }
+
+    public Gtk.ActionGroup? actions {
+        owned get { return null; }
+    }
+
+    public UnknownSource() {
+        this.keys = new HashTable.full(seahorse_pgp_keyid_hash,
+                                       seahorse_pgp_keyid_equal);
+    }
+
+    public async void load(Cancellable cancellable) {
+        return;
+    }
+
+    static uint get_length() {
+        return this.keys.length;
+    }
+
+    static GList get_objects() {
+        return this.keys.get_values();
+    }
+
+    static bool contains(GLib.Object object) {
+        Object seahorse_obj = object as Object;
+        if (seahorse_obj == null)
+            return false;
+
+        return this.keys.lookup(seahorse_obj.identifier) == seahorse_obj;
+    }
+
+    public Seahorse.Object add_object(string? keyid, Cancellable cancellable) {
+        if (keyid != null)
+            return null;
+
+        Seahorse.Object object = this.keys.lookup(keyid);
+        if (object == null)
+            this.keys.insert(keyid, new Unknown(this, keyid, null));
+
+        // TODO on the cancellable gone, change icon
+
+        return object;
+    }
+}
diff --git a/pgp/unknown.vala b/pgp/unknown.vala
new file mode 100644
index 0000000..97eb482
--- /dev/null
+++ b/pgp/unknown.vala
@@ -0,0 +1,27 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2006 Stefan Walter
+ * Copyright (C) 2017 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.Unknown : Object {
+    public Unknown(UnknownSource source, string keyid, string? display) {
+        GLib.Object(place: source,
+                    label: display ?? _("Unavailable"),
+                    identifier: Pgp.Key.calc_identifier(keyid));
+    }
+}
diff --git a/pgp/xloadimage.c b/pgp/xloadimage.c
new file mode 100644
index 0000000..8b2b912
--- /dev/null
+++ b/pgp/xloadimage.c
@@ -0,0 +1,55 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2005 Stefan Walter
+ *
+ * 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/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define BUF_SIZE 4096
+
+int main (int argc, char* argv[])
+{
+    unsigned char buf[BUF_SIZE];
+       const char* fname;
+    size_t len;
+    FILE* f;
+
+    fname = getenv ("SEAHORSE_IMAGE_FILE");
+    if (!fname) {
+        fprintf (stderr, "seahorse-xloadimage: specify output file in $SEAHORSE_IMAGE_FILE\n");
+        exit (2);
+    }
+
+    f = fopen (fname, "wb");
+    if (!f) {
+        fprintf (stderr, "seahorse-xloadimage: couldn't open output file: %s", fname);
+        exit (1);
+    }
+
+    do {
+        len = fread (buf, sizeof(unsigned char), BUF_SIZE, stdin);
+    } while (fwrite (buf, sizeof(unsigned char), len, f) == BUF_SIZE);
+
+    if (ferror (f)) {
+        fprintf (stderr, "seahorse-xloadimage: couldn't write to output file: %s", fname);
+        exit (1);
+    }
+        
+    fclose(f);
+    return 0;
+}
diff --git a/vapi/gpg-error.vapi b/vapi/gpg-error.vapi
new file mode 100644
index 0000000..fb75161
--- /dev/null
+++ b/vapi/gpg-error.vapi
@@ -0,0 +1,429 @@
+/* gpg-error.vapi
+ *
+ * Copyright (C) 2009 Sebastian Reichel <sre ring0 de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+[CCode (cheader_filename = "gpg-error.h")]
+namespace GPGError {
+
+  [CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_")]
+  public enum ErrorCode {
+    NO_ERROR,
+    GENERAL,
+    UNKNOWN_PACKET,
+    UNKNOWN_VERSION,
+    PUBKEY_ALGO,
+    DIGEST_ALGO,
+    BAD_PUBKEY,
+    BAD_SECKEY,
+    BAD_SIGNATURE,
+    NO_PUBKEY,
+    CHECKSUM,
+    BAD_PASSPHRASE,
+    CIPHER_ALGO,
+    KEYRING_OPEN,
+    INV_PACKET,
+    INV_ARMOR,
+    NO_USER_ID,
+    NO_SECKEY,
+    WRONG_SECKEY,
+    BAD_KEY,
+    COMPR_ALGO,
+    NO_PRIME,
+    NO_ENCODING_METHOD,
+    NO_ENCRYPTION_SCHEME,
+    NO_SIGNATURE_SCHEME,
+    INV_ATTR,
+    NO_VALUE,
+    NOT_FOUND,
+    VALUE_NOT_FOUND,
+    SYNTAX,
+    BAD_MPI,
+    INV_PASSPHRASE,
+    SIG_CLASS,
+    RESOURCE_LIMIT,
+    INV_KEYRING,
+    TRUSTDB,
+    BAD_CERT,
+    INV_USER_ID,
+    UNEXPECTED,
+    TIME_CONFLICT,
+    KEYSERVER,
+    WRONG_PUBKEY_ALGO,
+    TRIBUTE_TO_D_A,
+    WEAK_KEY,
+    INV_KEYLEN,
+    INV_ARG,
+    BAD_URI,
+    INV_URI,
+    NETWORK,
+    UNKNOWN_HOST,
+    SELFTEST_FAILED,
+    NOT_ENCRYPTED,
+    NOT_PROCESSED,
+    UNUSABLE_PUBKEY,
+    UNUSABLE_SECKEY,
+    INV_VALUE,
+    BAD_CERT_CHAIN,
+    MISSING_CERT,
+    NO_DATA,
+    BUG,
+    NOT_SUPPORTED,
+    INV_OP,
+    TIMEOUT,
+    INTERNAL,
+    EOF_GCRYPT,
+    INV_OBJ,
+    TOO_SHORT,
+    TOO_LARGE,
+    NO_OBJ,
+    NOT_IMPLEMENTED,
+    CONFLICT,
+    INV_CIPHER_MODE,
+    INV_FLAG,
+    INV_HANDLE,
+    TRUNCATED,
+    INCOMPLETE_LINE,
+    INV_RESPONSE,
+    NO_AGENT,
+    AGENT,
+    INV_DATA,
+    ASSUAN_SERVER_FAULT,
+    ASSUAN,
+    INV_SESSION_KEY,
+    INV_SEXP,
+    UNSUPPORTED_ALGORITHM,
+    NO_PIN_ENTRY,
+    PIN_ENTRY,
+    BAD_PIN,
+    INV_NAME,
+    BAD_DATA,
+    INV_PARAMETER,
+    WRONG_CARD,
+    NO_DIRMNGR,
+    DIRMNGR,
+    CERT_REVOKED,
+    NO_CRL_KNOWN,
+    CRL_TOO_OLD,
+    LINE_TOO_LONG,
+    NOT_TRUSTED,
+    CANCELED,
+    BAD_CA_CERT,
+    CERT_EXPIRED,
+    CERT_TOO_YOUNG,
+    UNSUPPORTED_CERT,
+    UNKNOWN_SEXP,
+    UNSUPPORTED_PROTECTION,
+    CORRUPTED_PROTECTION,
+    AMBIGUOUS_NAME,
+    CARD,
+    CARD_RESET,
+    CARD_REMOVED,
+    INV_CARD,
+    CARD_NOT_PRESENT,
+    NO_PKCS15_APP,
+    NOT_CONFIRMED,
+    CONFIGURATION,
+    NO_POLICY_MATCH,
+    INV_INDEX,
+    INV_ID,
+    NO_SCDAEMON,
+    SCDAEMON,
+    UNSUPPORTED_PROTOCOL,
+    BAD_PIN_METHOD,
+    CARD_NOT_INITIALIZED,
+    UNSUPPORTED_OPERATION,
+    WRONG_KEY_USAGE,
+    NOTHING_FOUND,
+    WRONG_BLOB_TYPE,
+    MISSING_VALUE,
+    HARDWARE,
+    PIN_BLOCKED,
+    USE_CONDITIONS,
+    PIN_NOT_SYNCED,
+    INV_CRL,
+    BAD_BER,
+    INV_BER,
+    ELEMENT_NOT_FOUND,
+    IDENTIFIER_NOT_FOUND,
+    INV_TAG,
+    INV_LENGTH,
+    INV_KEYINFO,
+    UNEXPECTED_TAG,
+    NOT_DER_ENCODED,
+    NO_CMS_OBJ,
+    INV_CMS_OBJ,
+    UNKNOWN_CMS_OBJ,
+    UNSUPPORTED_CMS_OBJ,
+    UNSUPPORTED_ENCODING,
+    UNSUPPORTED_CMS_VERSION,
+    UNKNOWN_ALGORITHM,
+    INV_ENGINE,
+    PUBKEY_NOT_TRUSTED,
+    DECRYPT_FAILED,
+    KEY_EXPIRED,
+    SIG_EXPIRED,
+    ENCODING_PROBLEM,
+    INV_STATE,
+    DUP_VALUE,
+    MISSING_ACTION,
+    MODULE_NOT_FOUND,
+    INV_OID_STRING,
+    INV_TIME,
+    INV_CRL_OBJ,
+    UNSUPPORTED_CRL_VERSION,
+    INV_CERT_OBJ,
+    UNKNOWN_NAME,
+    LOCALE_PROBLEM,
+    NOT_LOCKED,
+    PROTOCOL_VIOLATION,
+    INV_MAC,
+    INV_REQUEST,
+    UNKNOWN_EXTN,
+    UNKNOWN_CRIT_EXTN,
+    LOCKED,
+    UNKNOWN_OPTION,
+    UNKNOWN_COMMAND,
+    UNFINISHED,
+    BUFFER_TOO_SHORT,
+    SEXP_INV_LEN_SPEC,
+    SEXP_STRING_TOO_LONG,
+    SEXP_UNMATCHED_PAREN,
+    SEXP_NOT_CANONICAL,
+    SEXP_BAD_CHARACTER,
+    SEXP_BAD_QUOTATION,
+    SEXP_ZERO_PREFIX,
+    SEXP_NESTED_DH,
+    SEXP_UNMATCHED_DH,
+    SEXP_UNEXPECTED_PUNC,
+    SEXP_BAD_HEX_CHAR,
+    SEXP_ODD_HEX_NUMBERS,
+    SEXP_BAD_OCT_CHAR,
+    ASS_GENERAL,
+    ASS_ACCEPT_FAILED,
+    ASS_CONNECT_FAILED,
+    ASS_INV_RESPONSE,
+    ASS_INV_VALUE,
+    ASS_INCOMPLETE_LINE,
+    ASS_LINE_TOO_LONG,
+    ASS_NESTED_COMMANDS,
+    ASS_NO_DATA_CB,
+    ASS_NO_INQUIRE_CB,
+    ASS_NOT_A_SERVER,
+    ASS_NOT_A_CLIENT,
+    ASS_SERVER_START,
+    ASS_READ_ERROR,
+    ASS_WRITE_ERROR,
+    ASS_TOO_MUCH_DATA,
+    ASS_UNEXPECTED_CMD,
+    ASS_UNKNOWN_CMD,
+    ASS_SYNTAX,
+    ASS_CANCELED,
+    ASS_NO_INPUT,
+    ASS_NO_OUTPUT,
+    ASS_PARAMETER,
+    ASS_UNKNOWN_INQUIRE,
+    USER_1,
+    USER_2,
+    USER_3,
+    USER_4,
+    USER_5,
+    USER_6,
+    USER_7,
+    USER_8,
+    USER_9,
+    USER_10,
+    USER_11,
+    USER_12,
+    USER_13,
+    USER_14,
+    USER_15,
+    USER_16,
+    MISSING_ERRNO,
+    UNKNOWN_ERRNO,
+    EOF,
+    E2BIG,
+    EACCES,
+    EADDRINUSE,
+    EADDRNOTAVAIL,
+    EADV,
+    EAFNOSUPPORT,
+    EAGAIN,
+    EALREADY,
+    EAUTH,
+    EBACKGROUND,
+    EBADE,
+    EBADF,
+    EBADFD,
+    EBADMSG,
+    EBADR,
+    EBADRPC,
+    EBADRQC,
+    EBADSLT,
+    EBFONT,
+    EBUSY,
+    ECANCELED,
+    ECHILD,
+    ECHRNG,
+    ECOMM,
+    ECONNABORTED,
+    ECONNREFUSED,
+    ECONNRESET,
+    ED,
+    EDEADLK,
+    EDEADLOCK,
+    EDESTADDRREQ,
+    EDIED,
+    EDOM,
+    EDOTDOT,
+    EDQUOT,
+    EEXIST,
+    EFAULT,
+    EFBIG,
+    EFTYPE,
+    EGRATUITOUS,
+    EGREGIOUS,
+    EHOSTDOWN,
+    EHOSTUNREACH,
+    EIDRM,
+    EIEIO,
+    EILSEQ,
+    EINPROGRESS,
+    EINTR,
+    EINVAL,
+    EIO,
+    EISCONN,
+    EISDIR,
+    EISNAM,
+    EL2HLT,
+    EL2NSYNC,
+    EL3HLT,
+    EL3RST,
+    ELIBACC,
+    ELIBBAD,
+    ELIBEXEC,
+    ELIBMAX,
+    ELIBSCN,
+    ELNRNG,
+    ELOOP,
+    EMEDIUMTYPE,
+    EMFILE,
+    EMLINK,
+    EMSGSIZE,
+    EMULTIHOP,
+    ENAMETOOLONG,
+    ENAVAIL,
+    ENEEDAUTH,
+    ENETDOWN,
+    ENETRESET,
+    ENETUNREACH,
+    ENFILE,
+    ENOANO,
+    ENOBUFS,
+    ENOCSI,
+    ENODATA,
+    ENODEV,
+    ENOENT,
+    ENOEXEC,
+    ENOLCK,
+    ENOLINK,
+    ENOMEDIUM,
+    ENOMEM,
+    ENOMSG,
+    ENONET,
+    ENOPKG,
+    ENOPROTOOPT,
+    ENOSPC,
+    ENOSR,
+    ENOSTR,
+    ENOSYS,
+    ENOTBLK,
+    ENOTCONN,
+    ENOTDIR,
+    ENOTEMPTY,
+    ENOTNAM,
+    ENOTSOCK,
+    ENOTSUP,
+    ENOTTY,
+    ENOTUNIQ,
+    ENXIO,
+    EOPNOTSUPP,
+    EOVERFLOW,
+    EPERM,
+    EPFNOSUPPORT,
+    EPIPE,
+    EPROCLIM,
+    EPROCUNAVAIL,
+    EPROGMISMATCH,
+    EPROGUNAVAIL,
+    EPROTO,
+    EPROTONOSUPPORT,
+    EPROTOTYPE,
+    ERANGE,
+    EREMCHG,
+    EREMOTE,
+    EREMOTEIO,
+    ERESTART,
+    EROFS,
+    ERPCMISMATCH,
+    ESHUTDOWN,
+    ESOCKTNOSUPPORT,
+    ESPIPE,
+    ESRCH,
+    ESRMNT,
+    ESTALE,
+    ESTRPIPE,
+    ETIME,
+    ETIMEDOUT,
+    ETOOMANYREFS,
+    ETXTBSY,
+    EUCLEAN,
+    EUNATCH,
+    EUSERS,
+    EWOULDBLOCK,
+    EXDEV,
+    EXFULL,
+    CODE_DIM;
+
+    [CCode (cname="gpgme_err_code_from_errno")]
+    public static ErrorCode from_errno(int errno);
+    [CCode (cname="gpgme_err_code_to_errno")]
+    public int to_errno();
+
+    /**
+     * Returns whether this denotes a successful operation.
+     */
+    public bool is_success() {
+      // From the GPGME Reference Manual:
+      // "(...) it is guaranteed that only 0 is used to indicate success (GPG_ERR_NO_ERROR) (...)"
+      return this != ErrorCode.NO_ERROR;
+    }
+
+    /**
+     * Returns whether this denotes a cancelled operation.
+     */
+    public bool is_cancelled() {
+      return this != ErrorCode.CANCELED && this != ErrorCode.ECANCELED;
+    }
+  }
+
+}
diff --git a/vapi/gpgme.deps b/vapi/gpgme.deps
new file mode 100644
index 0000000..a0f4f82
--- /dev/null
+++ b/vapi/gpgme.deps
@@ -0,0 +1 @@
+gpg-error
diff --git a/vapi/gpgme.vapi b/vapi/gpgme.vapi
new file mode 100644
index 0000000..f666d0d
--- /dev/null
+++ b/vapi/gpgme.vapi
@@ -0,0 +1,1510 @@
+/* libgpgme.vapi
+ *
+ * Copyright (C) 2009 Sebastian Reichel <sre ring0 de>
+ * Copyright (C) 2016 Niels De Graef
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+/**
+ * GPGME is an API wrapper around GnuPG, which parses the output of GnuPG.
+ */
+[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h")]
+namespace GPG {
+  /**
+   * EngineInfo as List
+   */
+  [CCode (cname = "struct _gpgme_engine_info")]
+  public struct EngineInfo {
+    /**
+     * Next entry in the list
+     */
+    EngineInfo* next;
+
+    /**
+     * The protocol ID
+     */
+    Protocol protocol;
+
+    /**
+     * filename of the engine binary
+     */
+    string file_name;
+
+    /**
+     * version string of the installed binary
+     */
+    string version;
+
+    /**
+     * minimum version required for gpgme
+     */
+    string req_version;
+
+    /**
+     * home directory to be used or null for default
+     */
+    string? home_dir;
+  }
+
+  /**
+   * A Key from the Keyring
+   */
+  [CCode (cname = "struct _gpgme_key", ref_function = "gpgme_key_ref", ref_function_void = true, 
unref_function = "gpgme_key_unref", free_function = "gpgme_key_release")]
+  [Compact]
+  public class Key {
+    public bool revoked;
+    public bool expired;
+    public bool disabled;
+    public bool invalid;
+    public bool can_encrypt;
+    public bool can_sign;
+    public bool can_certify;
+    public bool secret;
+    public bool can_authenticate;
+    public bool is_qualified;
+    public Protocol protocol;
+
+    /**
+     * If protocol is CMS, this string contains the issuer's serial
+     */
+    public string issuer_serial;
+
+    /**
+     * If protocol is CMS, this string contains the issuer's name
+     */
+    public string issuer_name;
+
+    /**
+     * If protocol is CMS, this string contains the issuer's ID
+     */
+    public string issuer_id;
+
+    /**
+     * If protocol is OpenPGP, this field contains the owner trust level
+     */
+    public Validity owner_trust;
+
+    /**
+     * The key's subkeys
+     */
+    public SubKey subkeys;
+
+    /**
+     * The key's user ids
+     */
+    [CCode(array_null_terminated = true)]
+    public UserID[] uids;
+
+    public KeylistMode keylist_mode;
+  }
+
+  /**
+   * A signature notation
+   */
+  [CCode (cname = "struct _gpgme_sig_notation")]
+  public struct SigNotation {
+    /**
+     * The next SigNotation from the list
+     */
+    SigNotation* next;
+
+    /**
+     * If name is a null pointer value contains a policy url rather than a notation
+     */
+    string? name;
+
+    /**
+     * The value of the notation data
+     */
+    string value;
+
+    /**
+     * The length of the name of the notation data
+     */
+    int name_len;
+
+    /**
+     * The length of the value of the notation data
+     */
+    int value_len;
+
+    /**
+     * The accumulated flags
+     */
+    SigNotationFlags flags;
+
+    /**
+     * notation data is human readable
+     */
+    bool human_readable;
+
+    /**
+     * notation data is critical
+     */
+    bool critical;
+  }
+
+  /**
+   * A subkey from a Key
+   */
+  [CCode (cname = "struct _gpgme_subkey")]
+  [Compact]
+  public class SubKey {
+    public SubKey next;
+    public bool revoked;
+    public bool expired;
+    public bool disabled;
+    public bool invalid;
+    public bool can_encrypt;
+    public bool can_sign;
+    public bool can_certify;
+    public bool secret;
+    public bool can_authenticate;
+    public bool is_qualified;
+    public bool is_cardkey;
+    public PublicKeyAlgorithm pubkey_algo;
+    public uint length;
+    public string keyid;
+
+    /**
+     * Fingerprint of the key in hex form
+     */
+    public string fpr;
+
+    /**
+     * The creation timestamp.
+     * -1 = invalid,
+     * 0 = not available
+     */
+    public long timestamp;
+
+    /**
+     * The expiration timestamp.
+     * 0 = key does not expire
+     */
+    public long expires;
+
+    /**
+     * The serial number of the smartcard holding this key or null
+     */
+    public string? cardnumber;
+  }
+
+  /**
+   * A signature on a UserID
+   */
+  [CCode (cname = "struct _gpgme_key_sig")]
+  public struct KeySig {
+    /**
+     * The next signature from the list
+     */
+    KeySig* next;
+    bool invoked;
+    bool expired;
+    bool invalid;
+    bool exportable;
+    PublicKeyAlgorithm algo;
+    string keyid;
+
+    /**
+     * The creation timestamp.
+     * -1 = invalid,
+     * 0 = not available
+     */
+    long timestamp;
+
+    /**
+     * The expiration timestamp.
+     * 0 = key does not expire
+     */
+    long expires;
+
+    Error status;
+
+    string uid;
+    string name;
+    string email;
+    string comment;
+
+    /**
+     * Crypto backend specific signature class
+     */
+    uint sig_class;
+
+    SigNotation notations;
+  }
+
+  /**
+   * A UserID from a Key
+   */
+  [CCode (cname = "struct _gpgme_user_id")]
+  public struct UserID {
+    /**
+     * The next UserID from the list
+     */
+    UserID* next;
+
+    bool revoked;
+    bool invalid;
+    Validity validity;
+    string uid;
+    string name;
+    string email;
+    string comment;
+
+    KeySig signatures;
+  }
+
+  /**
+   * verify result of OP
+   */
+  [CCode (cname = "struct _gpgme_op_verify_result")]
+  public struct VerifyResult {
+    Signature* signatures;
+
+    /**
+     * The original file name of the plaintext message, if available
+     */
+    string? file_name;
+  }
+
+  /**
+   * sign result of OP
+   */
+  [CCode (cname = "struct _gpgme_op_sign_result")]
+  public struct SignResult {
+    InvalidKey invalid_signers;
+    Signature* signatures;
+  }
+
+  /**
+   * encrypt result of OP
+   */
+  [CCode (cname = "struct _gpgme_op_encrypt_result")]
+  public struct EncryptResult {
+    /**
+     * The list of invalid repipients
+     */
+    InvalidKey invalid_signers;
+  }
+
+  /**
+   * decrypt result of OP
+   */
+  [CCode (cname = "struct _gpgme_op_decrypt_result")]
+  public struct DecryptResult {
+    string unsupported_algorithm;
+    bool wrong_key_usage;
+    Recipient recipients;
+    string filename;
+  }
+
+  /**
+   * An receipient
+   */
+  [CCode (cname = "struct _gpgme_recipient")]
+  public struct Recipient {
+    Recipient *next;
+    string keyid;
+    PublicKeyAlgorithm pubkey_algo;
+    Error status;
+  }
+
+  /**
+   * list of invalid keys
+   */
+  [CCode (cname = "struct _gpgme_invalid_key")]
+  public struct InvalidKey {
+    InvalidKey *next;
+    string fpr;
+    Error reason;
+  }
+
+  /**
+   * A Signature
+   */
+  [CCode (cname = "struct _gpgme_signature")]
+  [Compact]
+  public class Signature {
+    /**
+     * The next signature in the list
+     */
+    Signature? next;
+
+    /**
+     * A summary of the signature status
+     */
+    Sigsum summary;
+
+    /**
+     * Fingerprint or key ID of the signature
+     */
+    string fpr;
+
+    /**
+     * The Error status of the signature
+     */
+    Error status;
+
+    /**
+     * Notation data and policy URLs
+     */
+    SigNotation notations;
+
+    /**
+     * Signature creation time
+     */
+    ulong timestamp;
+
+    /**
+     * Signature expiration time or 0
+     */
+    ulong exp_timestamp;
+
+    /**
+     * Key should not have been used for signing
+     */
+    bool wrong_key_usage;
+
+    /**
+     * PKA status
+     */
+    PKAStatus pka_trust;
+
+    /**
+     * Validity has been verified using the chain model
+     */
+    bool chain_model;
+
+    /**
+     * Validity
+     */
+    Validity validity;
+
+    /**
+     * Validity reason
+     */
+    Error validity_reason;
+
+    /**
+     * public key algorithm used to create the signature
+     */
+    PublicKeyAlgorithm pubkey_algo;
+
+    /**
+     * The hash algorithm used to create the signature
+     */
+    HashAlgorithm hash_algo;
+
+    /**
+     * The mailbox from the PKA information or null
+     */
+    string? pka_adress;
+  }
+
+  /**
+   * PKA Status
+   */
+  public enum PKAStatus {
+    NOT_AVAILABLE,
+    BAD,
+    OKAY,
+    RFU
+  }
+
+  /**
+   * Flags used for the summary field in a Signature
+   */
+  [CCode (cname = "gpgme_sigsum_t", cprefix = "GPGME_SIGSUM_")]
+  public enum Sigsum {
+    /**
+     * The signature is fully valid
+     */
+    VALID,
+
+    /**
+     * The signature is good
+     */
+    GREEN,
+
+    /**
+     * The signature is bad
+     */
+    RED,
+
+    /**
+     * One key has been revoked
+     */
+    KEY_REVOKED,
+
+    /**
+     * One key has expired
+     */
+    KEY_EXPIRED,
+
+    /**
+     * The signature has expired
+     */
+    SIG_EXPIRED,
+
+    /**
+     * Can't verfiy - missing key
+     */
+    KEY_MISSING,
+
+    /**
+     * CRL not available
+     */
+    CRL_MISSING,
+
+    /**
+     * Available CRL is too old
+     */
+    CRL_TOO_OLD,
+
+    /**
+     * A policy was not met
+     */
+    BAD_POLICY,
+
+    /**
+     * A system error occured
+     */
+    SYS_ERROR
+  }
+
+  /**
+   * Encoding modes of Data objects
+   */
+  [CCode (cname = "gpgme_data_encoding_t", cprefix = "GPGME_DATA_ENCODING_")]
+  public enum DataEncoding {
+    /**
+     * Not specified
+     */
+    NONE,
+    /**
+     * Binary encoded
+     */
+    BINARY,
+    /**
+     * Base64 encoded
+     */
+    BASE64,
+    /**
+     * Either PEM or OpenPGP Armor
+     */
+    ARMOR,
+    /**
+     * LF delimited URL list
+     */
+    URL,
+    /**
+     * LF percent escaped, delimited URL list
+     */
+    URLESC,
+    /**
+     * Nul determined URL list
+     */
+    URL0
+  }
+
+  /**
+   * Public Key Algorithms from libgcrypt
+   */
+  [CCode (cname = "gpgme_pubkey_algo_t", cprefix = "GPGME_PK_")]
+  public enum PublicKeyAlgorithm {
+    RSA,
+    RSA_E,
+    RSA_S,
+    ELG_E,
+    DSA,
+    ELG;
+
+    [CCode (cname = "gpgme_pubkey_algo_name")]
+    public unowned string? get_name();
+  }
+
+  /**
+   * Hash Algorithms from libgcrypt
+   */
+  [CCode (cname = "gpgme_hash_algo_t", cprefix = "GPGME_MD_")]
+  public enum HashAlgorithm {
+    NONE,
+    MD5,
+    SHA1,
+    RMD160,
+    MD2,
+    TIGER,
+    HAVAL,
+    SHA256,
+    SHA384,
+    SHA512,
+    MD4,
+    MD_CRC32,
+    MD_CRC32_RFC1510,
+    MD_CRC24_RFC2440;
+
+    [CCode (cname = "gpgme_hash_algo_name")]
+    public unowned string? get_name();
+  }
+
+  /**
+   * Signature modes
+   */
+  [CCode (cname = "gpgme_sig_mode_t", cprefix = "GPGME_SIG_MODE_")]
+  public enum SigMode {
+    NORMAL,
+    DETACH,
+    CLEAR
+  }
+
+  /**
+   * Validities for a trust item or key
+   */
+  [CCode (cname = "gpgme_validity_t", cprefix = "GPGME_VALIDITY_")]
+  public enum Validity {
+    UNKNOWN,
+    UNDEFINED,
+    NEVER,
+    MARGINAL,
+    FULL,
+    ULTIMATE
+  }
+
+  /**
+   * Protocols
+   */
+  [CCode (cname = "gpgme_protocol_t", cprefix = "GPGME_PROTOCOL_")]
+  public enum Protocol {
+    /**
+     * Default Mode
+     */
+    OpenPGP,
+    /**
+     * Cryptographic Message Syntax
+     */
+    CMS,
+    /**
+     * Special code for gpgconf
+     */
+    GPGCONF,
+    /**
+     * Low-level access to an Assuan server
+     */
+    ASSUAN,
+    UNKNOWN;
+
+    [CCode (cname = "gpgme_get_protocol_name")]
+    public unowned string? get_name();
+  }
+
+  /**
+   * Keylist modes used by Context
+   */
+  [CCode (cname = "gpgme_keylist_mode_t", cprefix = "GPGME_KEYLIST_MODE_")]
+  public enum KeylistMode {
+    LOCAL,
+    EXTERN,
+    SIGS,
+    SIG_NOTATIONS,
+    EPHEMERAL,
+    VALIDATE
+  }
+
+  /**
+   * Export modes used by Context
+   */
+  [CCode (cname = "gpgme_export_mode_t", cprefix = "GPGME_EXPORT_MODE_")]
+  [Flags]
+  public enum ExportMode {
+    EXTERN,
+    MINIMAL,
+    SECRET,
+    RAW,
+    PKCS12,
+  }
+
+  /**
+   * Audit log function flags
+   */
+  [CCode (cprefix = "GPGME_AUDITLOG_")]
+  public enum AuditLogFlag {
+    HTML,
+    WITH_HELP
+  }
+
+  /**
+   * Signature notation flags
+   */
+  [CCode (cname = "gpgme_sig_notation_flags_t", cprefix = "GPGME_SIG_NOTATION_")]
+  public enum SigNotationFlags {
+    HUMAN_READABLE,
+    CRITICAL
+  }
+
+  /**
+   * Encryption Flags
+   */
+  [CCode (cname = "gpgme_encrypt_flags_t", cprefix = "GPGME_ENCRYPT_")]
+  public enum EncryptFlags {
+    ALWAYS_TRUST,
+    NO_ENCRYPT_TO
+  }
+
+  /**
+   * Edit Operation Stati
+   */
+  [CCode (cname = "gpgme_status_code_t", cprefix = "GPGME_STATUS_")]
+  public enum StatusCode {
+    EOF,
+    ENTER,
+    LEAVE,
+    ABORT,
+    GOODSIG,
+    BADSIG,
+    ERRSIG,
+    BADARMOR,
+    RSA_OR_IDEA,
+    KEYEXPIRED,
+    KEYREVOKED,
+    TRUST_UNDEFINED,
+    TRUST_NEVER,
+    TRUST_MARGINAL,
+    TRUST_FULLY,
+    TRUST_ULTIMATE,
+    SHM_INFO,
+    SHM_GET,
+    SHM_GET_BOOL,
+    SHM_GET_HIDDEN,
+    NEED_PASSPHRASE,
+    VALIDSIG,
+    SIG_ID,
+    SIG_TO,
+    ENC_TO,
+    NODATA,
+    BAD_PASSPHRASE,
+    NO_PUBKEY,
+    NO_SECKEY,
+    NEED_PASSPHRASE_SYM,
+    DECRYPTION_FAILED,
+    DECRYPTION_OKAY,
+    MISSING_PASSPHRASE,
+    GOOD_PASSPHRASE,
+    GOODMDC,
+    BADMDC,
+    ERRMDC,
+    IMPORTED,
+    IMPORT_OK,
+    IMPORT_PROBLEM,
+    IMPORT_RES,
+    FILE_START,
+    FILE_DONE,
+    FILE_ERROR,
+    BEGIN_DECRYPTION,
+    END_DECRYPTION,
+    BEGIN_ENCRYPTION,
+    END_ENCRYPTION,
+    DELETE_PROBLEM,
+    GET_BOOL,
+    GET_LINE,
+    GET_HIDDEN,
+    GOT_IT,
+    PROGRESS,
+    SIG_CREATED,
+    SESSION_KEY,
+    NOTATION_NAME,
+    NOTATION_DATA,
+    POLICY_URL,
+    BEGIN_STREAM,
+    END_STREAM,
+    KEY_CREATED,
+    USERID_HINT,
+    UNEXPECTED,
+    INV_RECP,
+    NO_RECP,
+    ALREADY_SIGNED,
+    SIGEXPIRED,
+    EXPSIG,
+    EXPKEYSIG,
+    TRUNCATED,
+    ERROR,
+    NEWSIG,
+    REVKEYSIG,
+    SIG_SUBPACKET,
+    NEED_PASSPHRASE_PIN,
+    SC_OP_FAILURE,
+    SC_OP_SUCCESS,
+    CARDCTRL,
+    BACKUP_KEY_CREATED,
+    PKA_TRUST_BAD,
+    PKA_TRUST_GOOD,
+    PLAINTEXT
+  }
+
+  /**
+   * Flags that can be used in both {@link Context.create_key_sync} and
+   * {@link Context.create_subkey_sync}.
+   */
+  [CCode (cname="unsigned int", cprefix = "GPGME_CREATE_")]
+  [Flags]
+  public enum KeyCreationFlags {
+    /* Do not create the key with the default capabilities (key usage) of the re- */
+    /* quested algorithm but use those explicitly given by these flags: “signing”, */
+    /* “encryption”, “certification”, or “authentication”. The allowed combina- */
+    /* tions depend on the algorithm. */
+    /* If any of these flags are set and a default algorithm has been selected only */
+    /* one key is created in the case of the OpenPGP protocol. */
+    SIGN,
+    ENCR,
+    CERT,
+    AUTH,
+    /* Request generation of the key without password protection. */
+    NOPASSWD,
+    /* For an X.509 key do not create a CSR but a self-signed certificate. This */
+    /* has not yet been implemented. */
+    SELFSIGNED,
+    /* Do not store the created key in the local key database. This has not yet */
+    /* been implemented. */
+    NOSTORE,
+    /* Return the public or secret key as part of the result structure. This has */
+    /* not yet been implemented. */
+    WANTPUB,
+    WANTSEC,
+    /* The engine does not allow the creation of a key with a user ID already */
+    /* existing in the local key database. This flag can be used to override this */
+    /* check. */
+    FORCE,
+    /* Request generation of keys that do not expire. */
+    NOEXPIRE,
+  }
+
+  /**
+   * The Context object represents a GPG instance
+   */
+  [Compact]
+  [CCode (cname = "struct gpgme_context", free_function = "gpgme_release", cprefix = "gpgme_")]
+  public class Context {
+    /**
+     * Create a new context, returns Error Status Code
+     */
+    [CCode (cname = "gpgme_new")]
+    public static Error Context(out Context ctx);
+
+    public Error set_protocol(Protocol p);
+    public Protocol get_protocol();
+
+    public void set_armor(bool yes);
+    public bool get_armor();
+
+    public void set_textmode(bool yes);
+    public bool get_textmode();
+
+    public Error set_keylist_mode(KeylistMode mode);
+    public KeylistMode get_keylist_mode();
+
+    /**
+     * Include up to nr_of_certs certificates in an S/MIME message,
+     * Use "-256" to use the backend's default.
+     */
+    public void set_include_certs(int nr_of_certs = -256);
+
+    /**
+     * Return the number of certs to include in an S/MIME message
+     */
+    public int get_include_certs();
+
+    /**
+     * Set callback function for requesting passphrase.
+     *
+     * @param hook_value will be passed as the first argument to the callback.
+     */
+    public void set_passphrase_cb(passphrase_callback cb, void* hook_value = null);
+
+    /**
+     * Get callback function and hook_value
+     */
+    public void get_passphrase_cb(out passphrase_callback cb, out void* hook_value);
+
+    public Error set_locale(int category, string val);
+
+    /**
+     * Get information about the configured engines. The returned data is valid
+     * until the next set_engine_info() call.
+     */
+    [CCode (cname = "gpgme_ctx_get_engine_info")]
+    public EngineInfo* get_engine_info();
+
+    /**
+     * Set information about the configured engines. The string parameters may not
+     * be free'd after this calls, because they are not copied.
+     */
+    [CCode (cname = "gpgme_ctx_set_engine_info")]
+    public Error set_engine_info(Protocol proto, string file_name, string home_dir);
+
+    /**
+     * Delete all signers
+     */
+    public void signers_clear();
+
+    /**
+     * Add key to list of signers
+     */
+    public Error signers_add(Key key);
+
+    /**
+     * Get the n-th signer's key
+     */
+    public Key* signers_enum(int n);
+
+    /**
+     * Clear all notation data
+     */
+    public void sig_notation_clear();
+
+    /**
+     * Add human readable notation data. If name is null,
+     * then value val should be a policy URL. The HUMAN_READABLE
+     * flag is forced to be true for notation data and false
+     * for policy URLs.
+     */
+    public Error sig_notation_add(string name, string val, SigNotationFlags flags);
+
+    /**
+     * Get sig notations
+     */
+    public SigNotation* sig_notation_get();
+
+    /**
+     * Get key with the fingerprint FPR from the crypto backend.
+     * If SECRET is true, get the secret key.
+     */
+    public Error get_key(string fingerprint, out Key key, bool secret);
+
+    /**
+     * process the pending operation and, if hang is true, wait for
+     * the pending operation to finish.
+     */
+    public Context* wait(out Error status, bool hang);
+
+    /**
+     * Retrieve a pointer to the results of the signing operation
+     */
+    public SignResult* op_sign_result();
+
+    /**
+     * Creates a signature for the text in the data object plain
+     * and returns it in the data object sig. The type of the signature created is determined
+     * by the ASCII armor (or, if that is not set, by the encoding specified for sig), the text
+     * mode attributes set for the context ctx and the requested signature mode.
+     *
+     * After the operation completed successfully, the result can be retrieved with gpgme_
+     * op_sign_result.
+     *
+     * If an S/MIME signed message is created using the CMS crypto engine, the number
+     * of certificates to include in the message can be specified with gpgme_set_include_
+     * certs. See Section 7.4.8 [Included Certificates], page 35.
+     * The function returns the error code GPG_ERR_NO_ERROR if the signature could be
+     * created successfully, GPG_ERR_INV_VALUE if ctx, plain or sig is not a valid pointer,
+     * GPG_ERR_NO_DATA if the signature could not be created, GPG_ERR_BAD_PASSPHRASE if
+     * the passphrase for the secret key could not be retrieved, GPG_ERR_UNUSABLE_SECKEY
+     * if there are invalid signers, and passes through any errors that are reported by the
+     * crypto engine support routines.
+     */
+    [CCode (cname="gpgme_op_sign")]
+    public Error sign_sync(Data plain, ref Data sig, SigMode mode);
+
+    /**
+     * Retrieve a pointer to the result of the verify operation
+     */
+    public VerifyResult* op_verify_result();
+
+    /**
+     * Verify that SIG is a valid signature for SIGNED_TEXT.
+     */
+    public Error op_verify(Data sig, Data signed_text, Data? plaintext);
+
+    /**
+     * Retrieve a pointer to the result of the encrypt operation
+     */
+    public EncryptResult* op_encrypt_result();
+
+    /**
+     * Encrypt plaintext PLAIN for the recipients RECP and store the
+     * resulting ciphertext in CIPHER.
+     */
+    [CCode (cname="gpgme_op_decrypt")]
+    public Error encrypt_sync([CCode (array_length = false)] Key[] recp, EncryptFlags flags, Data plain, 
Data cipher);
+
+    /**
+     * Retrieve a pointer to the result of the decrypt operation
+     */
+    public DecryptResult* op_decrypt_result();
+
+    /**
+     * Decrypt ciphertext CIPHER and store the resulting plaintext
+     * in PLAIN.
+     */
+    [CCode (cname="gpgme_op_decrypt")]
+    public Error decrypt_sync(Data cipher, Data plain);
+
+    /**
+     * Export the keys found by PATTERN into KEYDATA. If PATTERN is
+     * null all keys will be exported.
+     */
+    [CCode (cname="gpgme_op_export")]
+    public Error export_by_pattern_sync(string? pattern, ExportMode mode, ref Data keydata);
+
+    /**
+     * Export the keys found by PATTERN into KEYDATA. If PATTERN is
+     * null all keys will be exported.
+     */
+    [CCode (cname="gpgme_op_export_keys")]
+    public Error export_keys_sync([CCode (array_length = false)] Key[] keys, ExportMode mode, ref Data 
keydata);
+
+    /**
+     * Adds the keys in the data buffer keydata to the keyring
+     * of the crypto engine used by ctx. The format of keydata can be ASCII armored,
+     * for example, but the details are specific to the crypto engine.
+     * After the operation completed successfully, the result can be retrieved with gpgme_
+     * op_import_result.
+     *
+     * @return the error code GPG_ERR_NO_ERROR if the import was completed
+     * successfully, GPG_ERR_INV_VALUE if keydata if ctx or keydata is not a valid pointer,
+     * and GPG_ERR_NO_DATA if keydata is an empty data buffer.
+     */
+    [CCode (cname="gpgme_op_import")]
+    public Error import_sync(Data keydata);
+
+    /**
+     * Adds the given keys to the key ring of the crypto engine used by ctx. This function is the
+     * general interface to move a key from one crypto engine to another as long as they
+     * are compatible. In particular it is used to actually import and make keys permanent
+     * which have been retrieved from an external source (i.e. using GPGME_KEYLIST_MODE_
+     * EXTERN).
+     * Only keys of the currently selected protocol of ctx are considered for import. Other
+     * keys specified by the keys are ignored. As of now all considered keys must have been
+     * retrieved using the same method, that is the used key listing mode must be identical.
+     * After the operation completed successfully, the result can be retrieved with gpgme_
+     * op_import_result.
+     *
+     * @return the error code GPG_ERR_NO_ERROR if the import was completed
+     * successfully, GPG_ERR_INV_VALUE if keydata if ctx or keydata is not a valid pointer,
+     * GPG_ERR_CONFLICT if the key listing mode does not match, and GPG_ERR_NO_DATA if
+     * no keys are considered for export.
+     */
+    [CCode (cname="gpgme_op_import_keys")]
+    public Error import_keys_sync([CCode (array_length = false)] Key[] keys);
+
+    /**
+     * Get result of last op_import.
+     */
+    public unowned ImportResult op_import_result();
+
+    /**
+     * Initiates a key listing operation. It sets everything up, so that
+     * subsequent invocations of op_keylist_next() return the keys in the list.
+     *
+     * If pattern is NULL, all available keys are returned. Otherwise, pattern
+     * contains an engine specific expression that is used to limit the list to
+     * all keys matching the pattern.
+     *
+     * If secret_only is not 0, the list is restricted to secret keys only.
+     *
+     * The context will be busy until either all keys are received (and
+     * op_keylist_next() returns GPG_ERR_EOF), or gpgme_op_keylist_end is called
+     * to finish the operation.
+     *
+     * The function returns the error code GPG_ERR_INV_VALUE if ctx is not a valid
+     * pointer, and passes through any errors that are reported by the crypto engine
+     * support routines.
+     */
+    public Error op_keylist_start(string? pattern = null, int secret_only = 0);
+
+    /**
+     * returns the next key in the list created by a previous op_keylist_start()
+     * operation in the context ctx. The key will have one reference for the user.
+     *
+     * If the last key in the list has already been returned, op_keylist_next()
+     * returns GPG_ERR_EOF.
+     *
+     * The function returns the error code GPG_ERR_INV_VALUE if ctx or r_key is
+     * not a valid pointer, and GPG_ERR_ENOMEM if there is not enough memory for
+     * the operation.
+     */
+    public Error op_keylist_next(out Key key);
+
+    /**
+     * ends a pending key list operation in the context.
+     *
+     * After the operation completed successfully, the result of the key listing
+     * operation can be retrieved with op_keylist_result().
+     *
+     * The function returns the error code GPG_ERR_INV_VALUE if ctx is not a valid
+     * pointer, and GPG_ERR_ENOMEM if at some time during the operation there was
+     * not enough memory available.
+     */
+    public Error op_keylist_end();
+
+    /**
+     * The function op_keylist_result() returns a KeylistResult holding the result of
+     * a op_keylist_*() operation. The returned KeylistResult is only valid if the last
+     * operation on the context was a key listing operation, and if this operation
+     * finished successfully. The returned KeylistResult is only valid until the next
+     * operation is started on the context.
+     */
+    public KeylistResult op_keylist_result();
+
+    /**
+     * Generates a new key for the procotol active in the. As of now this function does only
+     * work for OpenPGP and requires at least version 2.1.13 of GnuPG.
+     *
+     * After the operation completed successfully, information about the created key can be
+     * retrieved with gpgme_op_genkey_result.
+     *
+     * @param userid is commonly the mail address associated with the key. GPGME does not
+     * require a specificy syntax but if more than a mail address is given, RFC-822 style
+     * format is suggested. The value is expected to be in UTF-8 encoding (i.e. no IDN
+     * encoding for mail addresses). This is a required parameter.
+     * @param algo specifies the algorithm for the new key (actually a keypair of public and private
+     * key). For a list of supported algorithms, see the GnuPG manual. If algo is NULL or
+     * the string "default", the key is generated using the default algorithm of the engine. If
+     * the string "future-default" is used the engine may use an algorithm which is planned
+     * to be the default in a future release of the engine; however existing implementation
+     * of the protocol may not be able to already handle such future algorithms. For the
+     * OpenPGP protocol, the specification of a default algorithm, without requesting a
+     * non-default usage via flags, triggers the creation of a primary key plus a secondary
+     * key (subkey).
+     * @param reserved must be set to zero.
+     * @param expires specifies the expiration time in seconds. If you supply 0, a reasonable expira-
+     * tion time is chosen. Use the flag GPGME_CREATE_NOEXPIRE to create keys that do not
+     * expire. Note that this parameter takes an unsigned long value and not a time_t to
+     * avoid problems on systems which use a signed 32 bit time_t. Note further that the
+     * OpenPGP protocol uses 32 bit values for timestamps and thus can only encode dates
+     * up to the year 2106.
+     * @param extrakey is currently not used and must be set to NULL. A future version of GPGME
+     * may use this parameter to create X.509 keys.
+     * @param flags can be set to the bit-wise OR of the following flags:
+     *
+     * @return 0 on success, GPG_ERR_NOT_SUPPORTED if the engine does not
+     * support the command, or a bunch of other error codes.
+     */
+    [CCode (cname="gpgme_op_createkey")]
+    public Error create_key_sync(Key key, string userid, string? algo, ulong reserved, ulong expires, Key? 
extrakey, KeyCreationFlags flags);
+
+    /**
+     * Creates and adds a new subkey to the primary OpenPGP key given by @key. The only allowed protocol in 
ctx is GPGME_PROTOCOL_
+     * OPENPGP. Subkeys (aka secondary keys) are a concept in the OpenPGP protocol to
+     * bind several keys to a primary key.
+     *
+     * As of now this function requires at least version 2.1.13 of GnuPG.
+     *
+     * After the operation completed successfully, information about the created key can be
+     * retrieved with gpgme_op_genkey_result.
+     *
+     * @param key specifies the key to operate on.
+     * @param algo specifies the algorithm for the new subkey. For a list of supported algorithms, see
+     *       the GnuPG manual. If algo is null or "default", the subkey is generated
+     *       using the default algorithm for an encryption subkey of the engine. If the string
+     *       "future-default" is used the engine may use an encryption algorithm which is planned
+     *       to be the default in a future release of the engine; however existing implementation
+     *       of the protocol may not be able to already handle such future algorithms.
+     * @param reserved must be set to zero.
+     * @param expires specifies the expiration time in seconds. If you supply 0, a reasonable
+     *        expiration time is chosen. Use the flag {@link KeyCreationFlags.NOEXPIRE} to create keys that 
do not
+     *        expire. Note that this parameter takes an unsigned long value and not a time_t to
+     *        avoid problems on systems which use a signed 32 bit time_t. Note further that the
+     *        OpenPGP protocol uses 32 bit values for timestamps and thus can only encode dates
+     *        up to the year 2106.
+     * @param flags takes the same values as described above for gpgme_op_createkey.
+     *
+     * @return 0 on success, GPG_ERR_NOT_SUPPORTED if the engine does not support the command,
+     * or a bunch of other error codes
+     */
+    [CCode (cname="gpgme_op_createsubkey")]
+    public Error create_subkey_sync(Key key, string? algo, ulong reserved, ulong expires, KeyCreationFlags 
flags);
+
+    /**
+     * Adds a new user ID to the given OpenPGP key.
+     * Adding additional user IDs after key creation is a feature of the OpenPGP
+     * protocol and thus the protocol for the context must be set to OpenPGP.
+     *
+     * As of now, this function requires at least version 2.1.13 of GnuPG.
+     *
+     * @param key specifies the key to operate on.
+     * @param userid the user ID to add to the key. A user ID is commonly the mail address to be
+     *         associated with the key. GPGME does not require a specificy syntax but if more than
+     *         a mail address is given, RFC-822 style format is suggested. The value is expected to
+     *         be in UTF-8 encoding (i.e. no IDN encoding for mail addresses).
+     * @param flags currently not used and must be set to zero.
+     * @return 0 on success, GPG_ERR_NOT_SUPPORTED if the engine does not
+     * support the command, or a bunch of other error codes
+     */
+    [CCode (cname="gpgme_op_adduid")]
+    public Error add_uid_sync(Key key, string userid, uint flags = 0);
+
+    /**
+     * The function gpgme_op_revuid revokes a user ID from the OpenPGP key given by
+     * KEY. Revoking user IDs after key creation is a feature of the OpenPGP protocol
+     * and thus the protocol for the context ctx must be set to OpenPGP. As of now this
+     * function requires at least version 2.1.13 of GnuPG.
+     *
+     * Note that the engine won’t allow to revoke the last valid user ID. To change a user
+     * ID is better to first add the new user ID, then revoke the old one, and finally publish
+     * the key.
+     *
+     * @param key specifies the key to operate on.
+     * @param userid the user ID to be revoked from the key. The user ID must be given verbatim
+     *         because the engine does an exact and case sensitive match. Thus the uid field from
+     *         the user ID object (gpgme_user_id_t) is to be used.
+     * @param flags currently not used and must be set to 0.
+     *
+     * @return 0 on success, GPG_ERR_NOT_SUPPORTED if the engine does not
+     * support the command, or a bunch of other error codes
+     */
+    [CCode (cname="gpgme_op_revuid")]
+    public Error revoke_uid_sync(Key key, string userid, uint flags = 0);
+
+    /**
+     * Deletes the key key from the key ring of the crypto
+     * engine context.
+     * @param allow_secret If false, only public keys are deleted. Otherwise, secret
+     *           keys are deleted as well, if that is supported.
+     *
+     * @return the error code GPG_ERR_NO_ERROR if the key was deleted success-
+     * fully, GPG_ERR_INV_VALUE if ctx or key is not a valid pointer, GPG_ERR_NO_PUBKEY if
+     * key could not be found in the keyring, GPG_ERR_AMBIGUOUS_NAME if the key was not
+     * specified unambiguously, and GPG_ERR_CONFLICT if the secret key for key is available,
+     * but allow secret is zero.
+     */
+    [CCode (cname="gpgme_op_delete")]
+    public Error delete_key_sync(Key key, bool allow_secret);
+
+    /**
+     * Changes the passphrase of the private key associated with the given key.
+     * The only allowed value for flags is 0. The backend engine will usually
+     * popup a window to ask for the old and the new passphrase. Thus this function is not
+     * useful in a server application (where passphrases are not required anyway).
+     * Note that old gpg engines (before version 2.0.15) do not support this command and
+     * will silently ignore it.
+     */
+    [CCode (cname="gpgme_op_passwd")]
+    public Error change_password_sync(Key key, uint flags = 0);
+  }
+
+  [CCode (cname="gpgme_error_t")]
+  public struct Error {
+
+    [CCode (cname="gpgme_error")]
+    public Error(GPGError.ErrorCode error_code);
+    [CCode (cname="gpgme_err_from_errno")]
+    public Error.from_errno(int errno);
+
+    [CCode (cname="gpgme_err_code")]
+    public GPGError.ErrorCode error_code();
+
+    /**
+     * Return the error string for ERR in the user-supplied buffer BUF
+     * of size BUFLEN. This function is thread-safe, if a thread-safe
+     * strerror_r() function is provided by the system. If the function
+     * succeeds, 0 is returned and BUF contains the string describing
+     * the error. If the buffer was not large enough, ERANGE is returned
+     * and BUF contains as much of the beginning of the error string as
+     * fits into the buffer. Returns Error Status Code.
+     */
+    [CCode (cname = "gpgme_strerror_r")]
+    public int strerror_r(uint8[] buf);
+
+    /**
+     * Like strerror_r, but returns a pointer to the string. This method
+     * is not thread safe!
+     */
+    [CCode (cname = "gpgme_strerror")]
+    public unowned string strerror();
+
+    /**
+     * This is merely a convenience function for the one defined in {@link GPGError.ErrorCode}.
+     */
+    public bool is_succes() {
+    return error_code().is_success();
+    }
+
+    /**
+     * This is merely a convenience function for the one defined in {@link GPGError.ErrorCode}.
+     */
+    public bool is_cancelled() {
+    return error_code().is_cancelled();
+    }
+  }
+
+  [Flags]
+  [CCode (cname="unsigned int", cprefix="GPGME_IMPORT_")]
+  public enum ImportStatusFlags {
+    /**
+     * The key was new.
+     */
+    NEW,
+    /**
+     * The key contained new user IDs.
+     */
+    UID,
+    /**
+     * The key contained new signatures.
+     */
+    SIG,
+    /**
+     * The key contained new sub keys.
+     */
+    SUBKEY,
+    /**
+     * The key contained a secret key.
+     */
+    SECRET;
+  }
+
+  [Compact]
+  [CCode (cname = "struct _gpgme_import_status")]
+  public class ImportStatus {
+    /**
+     * This is a pointer to the next status structure in the linked list, or null
+     * if this is the last element.
+     */
+    public ImportStatus? next;
+
+    /**
+     * fingerprint of the key that was considered.
+     */
+    public string fpr;
+
+    /**
+     * If the import was not successful, this is the error value that caused the
+     * import to fail. Otherwise the error code is GPG_ERR_NO_ERROR.
+     */
+    public Error result;
+
+    /**
+     * Flags what parts of the key have been imported. May be 0, if the key has
+     * already been known.
+     */
+    public ImportStatusFlags status;
+  }
+
+  [Compact]
+  [CCode (cname = "struct _gpgme_op_import_result")]
+  public class ImportResult {
+    /**
+     * The total number of considered keys.
+     */
+    public int considered;
+
+    /**
+     * The number of keys without user ID.
+     */
+    public int no_user_id;
+
+    /**
+     * The total number of imported keys.
+     */
+    public int imported;
+
+    /**
+     * The number of imported RSA keys.
+     */
+    public int imported_rsa;
+
+    /**
+     * The number of unchanged keys.
+     */
+    public int unchanged;
+
+    /**
+     * The number of new user IDs.
+     */
+    public int new_user_ids;
+
+    /**
+     * The number of new sub keys.
+     */
+    public int new_sub_keys;
+
+    /**
+     * The number of new signatures.
+     */
+    public int new_signatures;
+
+    /**
+     * The number of new revocations.
+     */
+    public int new_revocations;
+
+    /**
+     * The total number of secret keys read.
+     */
+    public int secret_read;
+
+    /**
+     * The number of imported secret keys.
+     */
+    public int secret_imported;
+
+    /**
+     * The number of unchanged secret keys.
+     */
+    public int secret_unchanged;
+
+    /**
+     * The number of keys not imported.
+     */
+    public int not_imported;
+
+    /*
+     * A linked list of ImportStatus objects which
+     * contains more information about the keys for
+     * which an import was attempted.
+     */
+    public ImportStatus imports;
+  }
+
+  [Compact]
+  [CCode (cname = "struct _gpgme_op_keylist_result")]
+  public class KeylistResult {
+    uint truncated;
+  }
+
+
+  /**
+   * Data Object, contains encrypted and/or unencrypted data
+   */
+  [Compact]
+  [CCode (cname = "struct gpgme_data", free_function = "gpgme_data_release", cprefix = "gpgme_data_")]
+  public class Data {
+    /**
+     * Create a new data buffer, returns Error Status Code.
+     */
+    [CCode (cname = "gpgme_data_new")]
+    public static Error create(out Data d);
+
+    /**
+     * Create a new data buffer filled with SIZE bytes starting
+     * from BUFFER. If COPY is false, COPYING is delayed until
+     * necessary and the data is taken from the original location
+     * when needed. Returns Error Status Code.
+     */
+    [CCode (cname = "gpgme_data_new_from_mem")]
+    public static Error create_from_memory(out Data d, uint8[] buffer, bool copy);
+
+    /**
+     * Create a new data buffer filled with the content of the file.
+     * COPY must be non-zero. For delayed read, please use
+     * create_from_fd or create_from stream instead.
+     */
+    [CCode (cname = "gpgme_data_new_from_file")]
+    public static Error create_from_file(out Data d, string filename, int copy = 1);
+
+
+    /**
+     * Destroy the object and return a pointer to its content.
+     * It's size is returned in R_LEN.
+     */
+    [CCode (cname = "gpgme_data_release_and_get_mem")]
+    public string release_and_get_mem(out size_t len);
+
+    /**
+     * Read up to SIZE bytes into buffer BUFFER from the data object.
+     * Return the number of characters read, 0 on EOF and -1 on error.
+     * If an error occurs, errno is set.
+     */
+    public ssize_t read(uint8[] buf);
+
+    /**
+     * Write up to SIZE bytes from buffer BUFFER to the data object.
+     * Return the number of characters written, or -1 on error.
+     * If an error occurs, errno is set.
+     */
+    public ssize_t write(uint8[] buf);
+
+    /**
+     * Set the current position from where the next read or write
+     * starts in the data object to OFFSET, relativ to WHENCE.
+     */
+    public long seek(long offset, int whence=0);
+
+    /**
+     * Get the encoding attribute of the buffer
+     */
+    public DataEncoding *get_encoding();
+
+    /**
+     * Set the encoding attribute of the buffer to ENC
+     */
+    public Error set_encoding(DataEncoding enc);
+  }
+
+  [CCode (cname = "gpgme_passphrase_cb_t", has_target = false)]
+  public delegate Error passphrase_callback(void* hook, string uid_hint, string passphrase_info, bool 
prev_was_bad, int fd);
+
+  /**
+   * Get version of libgpgme
+   * Always call this function before using gpgme, it initializes some stuff
+   */
+  [CCode (cname = "gpgme_check_version")]
+  public unowned string check_version(string? required_version = null);
+
+  /**
+   * Verify that the engine implementing proto is installed and
+   * available.
+   */
+  [CCode (cname = "gpgme_engine_check_version")]
+  public Error engine_check_version(Protocol proto);
+
+  /**
+   * Get information about the configured engines. The returned data is valid
+   * until the next set_engine_info() call.
+   */
+  [CCode (cname = "gpgme_get_engine_info")]
+  public Error get_engine_info(out EngineInfo engine_info);
+
+  /**
+   * Returns a statically allocated string with the value
+   * associated to its argument. The returned values are the defaults and won’t change even
+   * after gpgme_set_engine_info has been used to configure a different engine. NULL is
+   * returned if no value is available. Commonly supported values for what are:
+   *
+   * - "homedir": Return the default home directory.
+   * - "sysconfdir": Return the name of the system configuration directory
+   * - "bindir": Return the name of the directory with GnuPG program files.
+   * - "libdir": Return the name of the directory with GnuPG related library files.
+   * - "libexecdir": Return the name of the directory with GnuPG helper program files.
+   * - "datadir": Return the name of the directory with GnuPG shared data.
+   * - "localedir": Return the name of the directory with GnuPG locale data.
+   * - "agent-socket": Return the name of the socket to connect to the gpg-agent.
+   * - "agent-ssh-socket": Return the name of the socket to connect to the ssh-agent component of
+   *                       gpg-agent.
+   * - "dirmngr-socket": Return the name of the socket to connect to the dirmngr.
+   * - "uiserver-socket": Return the name of the socket to connect to the user interface server.
+   * - "gpgconf-name": Return the file name of the engine configuration tool.
+   * - "gpg-name": Return the file name of the OpenPGP engine.
+   * - "gpgsm-name": Return the file name of the CMS engine.
+   * - g13-name: Return the name of the file container encryption engine.
+   * - "gpg-wks-client-name": Return the name of the Web Key Service tool.
+   */
+  [CCode (cname = "gpgme_get_dirinfo")]
+  public string? get_dirinfo(string directory);
+}



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