about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/command/command.c24
-rw-r--r--src/command/commands.c66
-rw-r--r--src/command/commands.h1
-rw-r--r--src/config/account.c9
-rw-r--r--src/config/account.h3
-rw-r--r--src/config/accounts.c26
-rw-r--r--src/config/accounts.h2
-rw-r--r--src/main.c6
-rw-r--r--src/pgp/gpg.c374
-rw-r--r--src/pgp/gpg.h55
-rw-r--r--src/profanity.c9
-rw-r--r--src/ui/console.c4
-rw-r--r--src/xmpp/message.c48
-rw-r--r--src/xmpp/presence.c37
-rw-r--r--src/xmpp/stanza.h2
15 files changed, 660 insertions, 6 deletions
diff --git a/src/command/command.c b/src/command/command.c
index fca25690..519550a3 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -852,6 +852,16 @@ static struct cmd_t command_defs[] =
           "Send chat state notifications during chat sessions.",
           NULL } } },
 
+    { "/pgp",
+        cmd_pgp, parse_args, 1, 1, NULL,
+        { "/pgp keys|libver", "Open PGP.",
+        { "/pgp keys|libver",
+          "----------------",
+          "Open PGP.",
+          "keys   : List private keys."
+          "libver : Show which version of the libgpgme library is being used.",
+          NULL } } },
+
     { "/otr",
         cmd_otr, parse_args, 1, 3, NULL,
         { "/otr command [args..]", "Off The Record encryption commands.",
@@ -1207,6 +1217,7 @@ static Autocomplete time_statusbar_ac;
 static Autocomplete resource_ac;
 static Autocomplete inpblock_ac;
 static Autocomplete receipts_ac;
+static Autocomplete pgp_ac;
 
 /*
  * Initialise command autocompleter and history
@@ -1363,6 +1374,7 @@ cmd_init(void)
     autocomplete_add(account_set_ac, "muc");
     autocomplete_add(account_set_ac, "nick");
     autocomplete_add(account_set_ac, "otr");
+    autocomplete_add(account_set_ac, "pgpkeyid");
 
     account_clear_ac = autocomplete_new();
     autocomplete_add(account_clear_ac, "password");
@@ -1370,6 +1382,7 @@ cmd_init(void)
     autocomplete_add(account_clear_ac, "server");
     autocomplete_add(account_clear_ac, "port");
     autocomplete_add(account_clear_ac, "otr");
+    autocomplete_add(account_clear_ac, "pgpkeyid");
 
     account_default_ac = autocomplete_new();
     autocomplete_add(account_default_ac, "set");
@@ -1571,6 +1584,11 @@ cmd_init(void)
     receipts_ac = autocomplete_new();
     autocomplete_add(receipts_ac, "send");
     autocomplete_add(receipts_ac, "request");
+
+    pgp_ac = autocomplete_new();
+    autocomplete_add(pgp_ac, "keys");
+    autocomplete_add(pgp_ac, "fps");
+    autocomplete_add(pgp_ac, "libver");
 }
 
 void
@@ -1630,6 +1648,7 @@ cmd_uninit(void)
     autocomplete_free(resource_ac);
     autocomplete_free(inpblock_ac);
     autocomplete_free(receipts_ac);
+    autocomplete_free(pgp_ac);
 }
 
 gboolean
@@ -1798,6 +1817,7 @@ cmd_reset_autocomplete()
     autocomplete_reset(resource_ac);
     autocomplete_reset(inpblock_ac);
     autocomplete_reset(receipts_ac);
+    autocomplete_reset(pgp_ac);
 
     if (ui_current_win_type() == WIN_CHAT) {
         ProfChatWin *chatwin = wins_get_current_chat();
@@ -1981,8 +2001,8 @@ _cmd_complete_parameters(const char * const input)
         }
     }
 
-    gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins", "/subject", "/room" };
-    Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, subject_ac, room_ac };
+    gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins", "/subject", "/room", "/pgp" };
+    Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, subject_ac, room_ac, pgp_ac };
 
     for (i = 0; i < ARRAY_SIZE(cmds); i++) {
         result = autocomplete_param_with_ac(input, cmds[i], completers[i], TRUE);
diff --git a/src/command/commands.c b/src/command/commands.c
index 9ca70ef0..51e19d1a 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -57,6 +57,9 @@
 #ifdef HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
+#ifdef HAVE_LIBGPGME
+#include "pgp/gpg.h"
+#endif
 #include "profanity.h"
 #include "tools/autocomplete.h"
 #include "tools/parser.h"
@@ -449,6 +452,10 @@ cmd_account(gchar **args, struct cmd_help_t help)
                         cons_show("Updated login status for account %s: %s", account_name, value);
                     }
                     cons_show("");
+                } else if (strcmp(property, "pgpkeyid") == 0) {
+                    accounts_set_pgp_keyid(account_name, value);
+                    cons_show("Updated PGP key ID for account %s: %s", account_name, value);
+                    cons_show("");
                 } else if (valid_resource_presence_string(property)) {
                     int intval;
                     char *err_msg = NULL;
@@ -527,6 +534,10 @@ cmd_account(gchar **args, struct cmd_help_t help)
                     accounts_clear_otr(account_name);
                     cons_show("OTR policy removed for account %s", account_name);
                     cons_show("");
+                } else if (strcmp(property, "pgpkeyid") == 0) {
+                    accounts_clear_pgp_keyid(account_name);
+                    cons_show("Removed PGP key ID for account %s", account_name);
+                    cons_show("");
                 } else {
                     cons_show("Invalid property: %s", property);
                     cons_show("");
@@ -4024,6 +4035,61 @@ cmd_xa(gchar **args, struct cmd_help_t help)
 }
 
 gboolean
+cmd_pgp(gchar **args, struct cmd_help_t help)
+{
+#ifdef HAVE_LIBGPGME
+    if (g_strcmp0(args[0], "keys") == 0) {
+        GSList *keys = p_gpg_list_keys();
+        if (keys) {
+            cons_show("PGP keys:");
+            while (keys) {
+                ProfPGPKey *key = keys->data;
+                cons_show("  %s", key->name);
+                cons_show("    ID          : %s", key->id);
+                cons_show("    Fingerprint : %s", key->fp);
+                keys = g_slist_next(keys);
+            }
+        } else {
+            cons_show("No keys found");
+        }
+        g_slist_free_full(keys, (GDestroyNotify)p_gpg_free_key);
+    } else if (g_strcmp0(args[0], "fps") == 0) {
+        GHashTable *fingerprints = p_gpg_fingerprints();
+        GList *jids = g_hash_table_get_keys(fingerprints);
+        if (jids) {
+            cons_show("Received PGP fingerprints:");
+            GList *curr = jids;
+            while (curr) {
+                char *jid = curr->data;
+                char *fingerprint = g_hash_table_lookup(fingerprints, jid);
+                cons_show("  %s: %s", jid, fingerprint);
+                curr = g_list_next(curr);
+            }
+        } else {
+            cons_show("No PGP fingerprints received.");
+        }
+        g_list_free(jids);
+    } else if (g_strcmp0(args[0], "libver") == 0) {
+        const char *libver = p_gpg_libver();
+        if (libver) {
+            GString *fullstr = g_string_new("Using libgpgme version ");
+            g_string_append(fullstr, libver);
+            cons_show("%s", fullstr->str);
+            g_string_free(fullstr, TRUE);
+        } else {
+            cons_show("Could not get libgpgme version");
+        }
+    }
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with PGP support enabled");
+    return TRUE;
+#endif
+
+}
+
+gboolean
 cmd_otr(gchar **args, struct cmd_help_t help)
 {
 #ifdef HAVE_LIBOTR
diff --git a/src/command/commands.h b/src/command/commands.h
index 7b7e7c93..9fe645e3 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -103,6 +103,7 @@ gboolean cmd_nick(gchar **args, struct cmd_help_t help);
 gboolean cmd_notify(gchar **args, struct cmd_help_t help);
 gboolean cmd_online(gchar **args, struct cmd_help_t help);
 gboolean cmd_otr(gchar **args, struct cmd_help_t help);
+gboolean cmd_pgp(gchar **args, struct cmd_help_t help);
 gboolean cmd_outtype(gchar **args, struct cmd_help_t help);
 gboolean cmd_prefs(gchar **args, struct cmd_help_t help);
 gboolean cmd_priority(gchar **args, struct cmd_help_t help);
diff --git a/src/config/account.c b/src/config/account.c
index 3896286b..82294dac 100644
--- a/src/config/account.c
+++ b/src/config/account.c
@@ -49,7 +49,7 @@ account_new(const gchar * const name, const gchar * const jid,
     int priority_away, int priority_xa, int priority_dnd,
     const gchar * const muc_service, const gchar * const muc_nick,
     const gchar * const otr_policy, GList *otr_manual, GList *otr_opportunistic,
-    GList *otr_always)
+    GList *otr_always, const gchar * const pgp_keyid)
 {
     ProfAccount *new_account = malloc(sizeof(ProfAccount));
 
@@ -142,6 +142,12 @@ account_new(const gchar * const name, const gchar * const jid,
     new_account->otr_opportunistic = otr_opportunistic;
     new_account->otr_always = otr_always;
 
+    if (pgp_keyid != NULL) {
+        new_account->pgp_keyid = strdup(pgp_keyid);
+    } else {
+        new_account->pgp_keyid = NULL;
+    }
+
     return new_account;
 }
 
@@ -170,6 +176,7 @@ account_free(ProfAccount *account)
         free(account->muc_service);
         free(account->muc_nick);
         free(account->otr_policy);
+        free(account->pgp_keyid);
         g_list_free_full(account->otr_manual, g_free);
         g_list_free_full(account->otr_opportunistic, g_free);
         g_list_free_full(account->otr_always, g_free);
diff --git a/src/config/account.h b/src/config/account.h
index c237a19e..a49aebd0 100644
--- a/src/config/account.h
+++ b/src/config/account.h
@@ -59,6 +59,7 @@ typedef struct prof_account_t {
     GList *otr_manual;
     GList *otr_opportunistic;
     GList *otr_always;
+    gchar *pgp_keyid;
 } ProfAccount;
 
 ProfAccount* account_new(const gchar * const name, const gchar * const jid,
@@ -68,7 +69,7 @@ ProfAccount* account_new(const gchar * const name, const gchar * const jid,
     int priority_away, int priority_xa, int priority_dnd,
     const gchar * const muc_service, const gchar * const muc_nick,
     const gchar * const otr_policy, GList *otr_manual, GList *otr_opportunistic,
-    GList *otr_always);
+    GList *otr_always, const gchar * const pgp_keyid);
 
 char* account_create_full_jid(ProfAccount *account);
 
diff --git a/src/config/accounts.c b/src/config/accounts.c
index 6c04549c..b18974ca 100644
--- a/src/config/accounts.c
+++ b/src/config/accounts.c
@@ -280,11 +280,16 @@ accounts_get_account(const char * const name)
             g_strfreev(always);
         }
 
+        gchar *pgp_keyid = NULL;
+        if (g_key_file_has_key(accounts, name, "pgp.keyid", NULL)) {
+            pgp_keyid = g_key_file_get_string(accounts, name, "pgp.keyid", NULL);
+        }
+
         ProfAccount *new_account = account_new(name, jid, password, eval_password, enabled,
             server, port, resource, last_presence, login_presence,
             priority_online, priority_chat, priority_away, priority_xa,
             priority_dnd, muc_service, muc_nick, otr_policy, otr_manual,
-            otr_opportunistic, otr_always);
+            otr_opportunistic, otr_always, pgp_keyid);
 
         g_free(jid);
         g_free(password);
@@ -296,6 +301,7 @@ accounts_get_account(const char * const name)
         g_free(muc_service);
         g_free(muc_nick);
         g_free(otr_policy);
+        g_free(pgp_keyid);
 
         return new_account;
     }
@@ -454,6 +460,15 @@ accounts_set_eval_password(const char * const account_name, const char * const v
 }
 
 void
+accounts_set_pgp_keyid(const char * const account_name, const char * const value)
+{
+    if (accounts_account_exists(account_name)) {
+        g_key_file_set_string(accounts, account_name, "pgp.keyid", value);
+        _save_accounts();
+    }
+}
+
+void
 accounts_clear_password(const char * const account_name)
 {
     if (accounts_account_exists(account_name)) {
@@ -490,6 +505,15 @@ accounts_clear_port(const char * const account_name)
 }
 
 void
+accounts_clear_pgp_keyid(const char * const account_name)
+{
+    if (accounts_account_exists(account_name)) {
+        g_key_file_remove_key(accounts, account_name, "pgp.keyid", NULL);
+        _save_accounts();
+    }
+}
+
+void
 accounts_clear_otr(const char * const account_name)
 {
     if (accounts_account_exists(account_name)) {
diff --git a/src/config/accounts.h b/src/config/accounts.h
index 50307b5b..eb981cb8 100644
--- a/src/config/accounts.h
+++ b/src/config/accounts.h
@@ -77,11 +77,13 @@ void accounts_set_priority_dnd(const char * const account_name, const gint value
 void accounts_set_priority_all(const char * const account_name, const gint value);
 gint accounts_get_priority_for_presence_type(const char * const account_name,
     resource_presence_t presence_type);
+void accounts_set_pgp_keyid(const char * const account_name, const char * const value);
 void accounts_clear_password(const char * const account_name);
 void accounts_clear_eval_password(const char * const account_name);
 void accounts_clear_server(const char * const account_name);
 void accounts_clear_port(const char * const account_name);
 void accounts_clear_otr(const char * const account_name);
+void accounts_clear_pgp_keyid(const char * const account_name);
 void accounts_add_otr_policy(const char * const account_name, const char * const contact_jid, const char * const policy);
 
 #endif
diff --git a/src/main.c b/src/main.c
index 3bb7eeb6..ea8f0cea 100644
--- a/src/main.c
+++ b/src/main.c
@@ -121,6 +121,12 @@ main(int argc, char **argv)
         g_print("OTR support: Disabled\n");
 #endif
 
+#ifdef HAVE_LIBGPGME
+        g_print("PGP support: Enabled\n");
+#else
+        g_print("PGP support: Disabled\n");
+#endif
+
         return 0;
     }
 
diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c
new file mode 100644
index 00000000..a998ecc9
--- /dev/null
+++ b/src/pgp/gpg.c
@@ -0,0 +1,374 @@
+/*
+ * gpg.c
+ *
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity 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 Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#include <locale.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <gpgme.h>
+
+#include "pgp/gpg.h"
+#include "log.h"
+
+#define PGP_SIGNATURE_HEADER "-----BEGIN PGP SIGNATURE-----"
+#define PGP_SIGNATURE_FOOTER "-----END PGP SIGNATURE-----"
+#define PGP_MESSAGE_HEADER "-----BEGIN PGP MESSAGE-----"
+#define PGP_MESSAGE_FOOTER "-----END PGP MESSAGE-----"
+
+static const char *libversion;
+static GHashTable *fingerprints;
+
+static char* _remove_header_footer(char *str, const char * const footer);
+static char* _add_header_footer(const char * const str, const char * const header, const char * const footer);
+
+void
+p_gpg_init(void)
+{
+    libversion = gpgme_check_version(NULL);
+    log_debug("GPG: Found gpgme version: %s", libversion);
+    gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
+
+    // TODO add close function to clean up
+    fingerprints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+}
+
+void
+p_gpg_close(void)
+{
+    g_hash_table_destroy(fingerprints);
+}
+
+GSList *
+p_gpg_list_keys(void)
+{
+    gpgme_error_t error;
+    gpgme_ctx_t ctx;
+    gpgme_key_t key;
+    GSList *result = NULL;
+
+    error = gpgme_new(&ctx);
+    if (error) {
+        log_error("GPG: Could not list keys. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        return NULL;
+    }
+
+    error = gpgme_op_keylist_start(ctx, NULL, 1);
+    if (error == GPG_ERR_NO_ERROR) {
+        while (!error) {
+            error = gpgme_op_keylist_next(ctx, &key);
+            if (error) {
+                break;
+            }
+
+            ProfPGPKey *p_pgpkey = malloc(sizeof(ProfPGPKey));
+            p_pgpkey->id = strdup(key->subkeys->keyid);
+            p_pgpkey->name = strdup(key->uids->uid);
+            p_pgpkey->fp = strdup(key->subkeys->fpr);
+
+            result = g_slist_append(result, p_pgpkey);
+
+            gpgme_key_release(key);
+        }
+    } else {
+        log_error("GPG: Could not list keys. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+    }
+
+    gpgme_release(ctx);
+
+    return result;
+}
+
+GHashTable *
+p_gpg_fingerprints(void)
+{
+    return fingerprints;
+}
+
+const char*
+p_gpg_libver(void)
+{
+    return libversion;
+}
+
+void
+p_gpg_free_key(ProfPGPKey *key)
+{
+    if (key) {
+        free(key->id);
+        free(key->name);
+        free(key->fp);
+        free(key);
+    }
+}
+
+void
+p_gpg_verify(const char * const barejid, const char *const sign)
+{
+    if (!sign) {
+        return;
+    }
+
+    gpgme_ctx_t ctx;
+    gpgme_error_t error = gpgme_new(&ctx);
+    if (error) {
+        log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        return;
+    }
+
+    gpgme_data_t sign_data;
+    gpgme_data_t plain_data;
+    char *sign_with_header_footer = _add_header_footer(sign, PGP_SIGNATURE_HEADER, PGP_SIGNATURE_FOOTER);
+    gpgme_data_new_from_mem(&sign_data, sign_with_header_footer, strlen(sign_with_header_footer), 1);
+    gpgme_data_new(&plain_data);
+
+    error = gpgme_op_verify(ctx, sign_data, NULL, plain_data);
+    if (error) {
+        log_error("GPG: Failed to verify. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return;
+    }
+
+    gpgme_verify_result_t result = gpgme_op_verify_result(ctx);
+    if (result) {
+        if (result->signatures) {
+            log_debug("Fingerprint found for %s: %s ", barejid, result->signatures->fpr);
+            g_hash_table_replace(fingerprints, strdup(barejid), strdup(result->signatures->fpr));
+        }
+    }
+
+    gpgme_data_release(sign_data);
+    gpgme_data_release(plain_data);
+}
+
+char*
+p_gpg_sign(const char * const str, const char * const fp)
+{
+    gpgme_ctx_t ctx;
+    gpgme_error_t error = gpgme_new(&ctx);
+    if (error) {
+        log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        return NULL;
+    }
+
+    gpgme_key_t key = NULL;
+    error = gpgme_get_key(ctx, fp, &key, 1);
+    if (error || key == NULL) {
+        log_error("GPG: Failed to get key. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release (ctx);
+        return NULL;
+    }
+
+    gpgme_signers_clear(ctx);
+    error = gpgme_signers_add(ctx, key);
+    if (error) {
+        log_error("GPG: Failed to load signer. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return NULL;
+    }
+
+    gpgme_data_t str_data;
+    gpgme_data_t signed_data;
+    char *str_or_empty = NULL;
+    if (str) {
+        str_or_empty = strdup(str);
+    } else {
+        str_or_empty = strdup("");
+    }
+    gpgme_data_new_from_mem(&str_data, str_or_empty, strlen(str_or_empty), 1);
+    gpgme_data_new(&signed_data);
+
+    gpgme_set_armor(ctx,1);
+    error = gpgme_op_sign(ctx,str_data,signed_data,GPGME_SIG_MODE_DETACH);
+    if (error) {
+        log_error("GPG: Failed to sign string. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return NULL;
+    }
+
+    char *result = NULL;
+    gpgme_data_release(str_data);
+
+    size_t len = 0;
+    char *signed_str = gpgme_data_release_and_get_mem(signed_data, &len);
+    if (signed_str != NULL) {
+        signed_str[len] = 0;
+        result = _remove_header_footer(signed_str, PGP_SIGNATURE_FOOTER);
+    }
+    gpgme_free(signed_str);
+    gpgme_release(ctx);
+    free(str_or_empty);
+
+    return result;
+}
+
+char *
+p_gpg_encrypt(const char * const barejid, const char * const message)
+{
+    char *fp = g_hash_table_lookup(fingerprints, barejid);
+
+    if (!fp) {
+        return NULL;
+    }
+
+    gpgme_key_t keys[2];
+
+    keys[0] = NULL;
+    keys[1] = NULL;
+
+    gpgme_ctx_t ctx;
+    gpgme_error_t error = gpgme_new(&ctx);
+    if (error) {
+        log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        return NULL;
+    }
+
+    gpgme_key_t key;
+    error = gpgme_get_key(ctx, fp, &key, 0);
+    if (error || key == NULL) {
+        log_error("GPG: Failed to get key. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return NULL;
+    }
+
+    keys[0] = key;
+
+    gpgme_data_t plain;
+    gpgme_data_t cipher;
+    gpgme_data_new_from_mem(&plain, message, strlen(message), 1);
+    gpgme_data_new(&cipher);
+
+    gpgme_set_armor(ctx, 1);
+    error = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, plain, cipher);
+    if (error) {
+        log_error("GPG: Failed to encrypt message. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return NULL;
+    }
+    gpgme_data_release(plain);
+
+    char *cipher_str = NULL;
+    char *result = NULL;
+    size_t len;
+    cipher_str = gpgme_data_release_and_get_mem(cipher, &len);
+    if (cipher_str) {
+        result = _remove_header_footer(cipher_str, PGP_MESSAGE_FOOTER);
+    }
+
+    gpgme_free(cipher_str);
+    gpgme_release(ctx);
+
+    return result;
+}
+
+char *
+p_gpg_decrypt(const char * const barejid, const char * const cipher)
+{
+    char *cipher_with_headers = _add_header_footer(cipher, PGP_MESSAGE_HEADER, PGP_MESSAGE_FOOTER);
+
+    gpgme_ctx_t ctx;
+    gpgme_error_t error = gpgme_new(&ctx);
+    if (error) {
+        log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        return NULL;
+    }
+
+    gpgme_data_t plain_data;
+    gpgme_data_t cipher_data;
+    gpgme_data_new_from_mem (&cipher_data, cipher_with_headers, strlen(cipher_with_headers), 1);
+    gpgme_data_new(&plain_data);
+
+    error = gpgme_op_decrypt(ctx, cipher_data, plain_data);
+    if (error) {
+        log_error("GPG: Failed to encrypt message. %s %s", gpgme_strsource(error), gpgme_strerror(error));
+        gpgme_release(ctx);
+        return NULL;
+    }
+
+    gpgme_data_release(cipher_data);
+
+    size_t len = 0;
+    char *plain_str = gpgme_data_release_and_get_mem(plain_data, &len);
+    char *result = NULL;
+    if (plain_str) {
+        plain_str[len] = 0;
+        result = g_strdup(plain_str);
+    }
+    gpgme_free(plain_str);
+
+    gpgme_release(ctx);
+
+    return result;
+}
+
+static char*
+_remove_header_footer(char *str, const char * const footer)
+{
+    int pos = 0;
+    int newlines = 0;
+
+    while (newlines < 3) {
+        if (str[pos] == '\n') {
+            newlines++;
+        }
+        pos++;
+
+        if (str[pos] == '\0') {
+            return NULL;
+        }
+    }
+
+    char *stripped = strdup(&str[pos]);
+    char *footer_start = g_strrstr(stripped, footer);
+    footer_start[0] = '\0';
+
+    return stripped;
+}
+
+static char*
+_add_header_footer(const char * const str, const char * const header, const char * const footer)
+{
+    GString *result_str = g_string_new("");
+
+    g_string_append(result_str, header);
+    g_string_append(result_str, "\n\n");
+    g_string_append(result_str, str);
+    g_string_append(result_str, "\n");
+    g_string_append(result_str, footer);
+
+    char *result = result_str->str;
+    g_string_free(result_str, FALSE);
+
+    return result;
+}
diff --git a/src/pgp/gpg.h b/src/pgp/gpg.h
new file mode 100644
index 00000000..fb1f0f6b
--- /dev/null
+++ b/src/pgp/gpg.h
@@ -0,0 +1,55 @@
+/*
+ * gpg.h
+ *
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity 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 Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef GPG_H
+#define GPG_H
+
+typedef struct pgp_key_t {
+    char *id;
+    char *name;
+    char *fp;
+} ProfPGPKey;
+
+void p_gpg_init(void);
+void p_gpg_close(void);
+GSList* p_gpg_list_keys(void);
+GHashTable* p_gpg_fingerprints(void);
+const char* p_gpg_libver(void);
+void p_gpg_free_key(ProfPGPKey *key);
+char* p_gpg_sign(const char * const str, const char * const fp);
+void p_gpg_verify(const char * const barejid, const char *const sign);
+char* p_gpg_encrypt(const char * const barejid, const char * const message);
+char* p_gpg_decrypt(const char * const barejid, const char * const cipher);
+
+#endif
diff --git a/src/profanity.c b/src/profanity.c
index 6a2966dd..47063ae8 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -59,6 +59,9 @@
 #ifdef HAVE_LIBOTR
 #include "otr/otr.h"
 #endif
+#ifdef HAVE_LIBGPGME
+#include "pgp/gpg.h"
+#endif
 #include "resource.h"
 #include "xmpp/xmpp.h"
 #include "ui/ui.h"
@@ -241,6 +244,9 @@ _init(const int disable_tls, char *log_level)
 #ifdef HAVE_LIBOTR
     otr_init();
 #endif
+#ifdef HAVE_LIBGPGME
+    p_gpg_init();
+#endif
     atexit(_shutdown);
     ui_input_nonblocking(TRUE);
 }
@@ -265,6 +271,9 @@ _shutdown(void)
 #ifdef HAVE_LIBOTR
     otr_shutdown();
 #endif
+#ifdef HAVE_LIBGPGME
+    p_gpg_close();
+#endif
     chat_log_close();
     prefs_close();
     theme_close();
diff --git a/src/ui/console.c b/src/ui/console.c
index 8dfd473f..246dc336 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -710,6 +710,10 @@ cons_show_account(ProfAccount *account)
         g_string_free(always, TRUE);
     }
 
+    if (account->pgp_keyid) {
+        cons_show   ("PGP Key ID        : %s", account->pgp_keyid);
+    }
+
     cons_show       ("Priority          : chat:%d, online:%d, away:%d, xa:%d, dnd:%d",
         account->priority_chat, account->priority_online, account->priority_away,
         account->priority_xa, account->priority_dnd);
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 8febbe11..aa5e55d9 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -49,6 +49,7 @@
 #include "roster_list.h"
 #include "xmpp/stanza.h"
 #include "xmpp/xmpp.h"
+#include "pgp/gpg.h"
 
 #define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_MESSAGE, type, ctx)
 
@@ -104,7 +105,36 @@ message_send_chat(const char * const barejid, const char * const msg)
     }
 
     char *id = create_unique_id("msg");
-    xmpp_stanza_t *message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, msg);
+    xmpp_stanza_t *message = NULL;
+
+#ifdef HAVE_LIBGPGME
+    char *account_name = jabber_get_account_name();
+    ProfAccount *account = accounts_get_account(account_name);
+    if (account->pgp_keyid) {
+        Jid *jidp = jid_create(jid);
+        char *encrypted = p_gpg_encrypt(jidp->barejid, msg);
+        if (encrypted) {
+            message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, "This message is encrypted.");
+            xmpp_stanza_t *x = xmpp_stanza_new(ctx);
+            xmpp_stanza_set_name(x, STANZA_NAME_X);
+            xmpp_stanza_set_ns(x, STANZA_NS_ENCRYPTED);
+            xmpp_stanza_t *enc_st = xmpp_stanza_new(ctx);
+            xmpp_stanza_set_text(enc_st, encrypted);
+            xmpp_stanza_add_child(x, enc_st);
+            xmpp_stanza_release(enc_st);
+            xmpp_stanza_add_child(message, x);
+            xmpp_stanza_release(x);
+            free(encrypted);
+        } else {
+            message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, msg);
+        }
+    } else {
+        message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, msg);
+    }
+#else
+    message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, msg);
+#endif
+
     free(jid);
 
     if (state) {
@@ -641,7 +671,23 @@ _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
                 if (delayed) {
                     srv_delayed_message(jid->barejid, message, tv_stamp);
                 } else {
+#ifdef HAVE_LIBGPGME
+                    gboolean handled = FALSE;
+                    xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_ENCRYPTED);
+                    if (x) {
+                        char *enc_message = xmpp_stanza_get_text(x);
+                        char *decrypted = p_gpg_decrypt(jid->barejid, enc_message);
+                        if (decrypted) {
+                            srv_incoming_message(jid->barejid, jid->resourcepart, decrypted);
+                            handled = TRUE;
+                        }
+                    }
+                    if (!handled) {
+                        srv_incoming_message(jid->barejid, jid->resourcepart, message);
+                    }
+#else
                     srv_incoming_message(jid->barejid, jid->resourcepart, message);
+#endif
                 }
                 if (id && prefs_get_boolean(PREF_RECEIPTS_SEND)) {
                     xmpp_stanza_t *receipts = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS);
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 403ddc4e..b704b49c 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -49,6 +49,9 @@
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
 #include "xmpp/xmpp.h"
+#ifdef HAVE_LIBGPGME
+#include "pgp/gpg.h"
+#endif
 
 static Autocomplete sub_requests_ac;
 
@@ -222,7 +225,31 @@ presence_update(const resource_presence_t presence_type, const char * const msg,
     char *id = create_unique_id("presence");
     xmpp_stanza_set_id(presence, id);
     stanza_attach_show(ctx, presence, show);
+
     stanza_attach_status(ctx, presence, msg);
+
+#ifdef HAVE_LIBGPGME
+    char *account_name = jabber_get_account_name();
+    ProfAccount *account = accounts_get_account(account_name);
+    if (account->pgp_keyid) {
+        char *signed_status = p_gpg_sign(msg, account->pgp_keyid);
+
+        if (signed_status) {
+            xmpp_stanza_t *x = xmpp_stanza_new(ctx);
+            xmpp_stanza_set_name(x, STANZA_NAME_X);
+            xmpp_stanza_set_ns(x, STANZA_NS_SIGNED);
+            xmpp_stanza_t *signed_text = xmpp_stanza_new(ctx);
+            xmpp_stanza_set_text(signed_text, signed_status);
+            xmpp_stanza_add_child(x, signed_text);
+            xmpp_stanza_release(signed_text);
+            xmpp_stanza_add_child(presence, x);
+            xmpp_stanza_release(x);
+
+            free(signed_status);
+        }
+    }
+#endif
+
     stanza_attach_priority(ctx, presence, pri);
     stanza_attach_last_activity(ctx, presence, idle);
     stanza_attach_caps(ctx, presence);
@@ -580,6 +607,16 @@ _available_handler(xmpp_conn_t * const conn,
         log_debug("Presence available handler fired for: %s", jid);
     }
 
+#ifdef HAVE_LIBGPGME
+    xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_SIGNED);
+    if (x) {
+        char *sign = xmpp_stanza_get_text(x);
+        if (sign) {
+            p_gpg_verify(xmpp_presence->jid->barejid, sign);
+        }
+    }
+#endif
+
     const char *my_jid_str = xmpp_conn_get_jid(conn);
     Jid *my_jid = jid_create(my_jid_str);
 
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 89dbda57..042b6aea 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -160,6 +160,8 @@
 #define STANZA_NS_CARBONS "urn:xmpp:carbons:2"
 #define STANZA_NS_FORWARD "urn:xmpp:forward:0"
 #define STANZA_NS_RECEIPTS "urn:xmpp:receipts"
+#define STANZA_NS_SIGNED "jabber:x:signed"
+#define STANZA_NS_ENCRYPTED "jabber:x:encrypted"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"