changeset 1041:d4b97a2423eb

Introduce PGP utility functions
author Mikael Berthe <mikael@lilotux.net>
date Sun, 26 Nov 2006 10:30:52 +0100
parents b6c6734a781b
children 8a395c2cafc4
files mcabber/src/Makefile.am mcabber/src/pgp.c mcabber/src/pgp.h
diffstat 3 files changed, 491 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/src/Makefile.am	Sun Nov 26 10:14:47 2006 +0100
+++ b/mcabber/src/Makefile.am	Sun Nov 26 10:30:52 2006 +0100
@@ -4,7 +4,8 @@
 		  commands.c commands.h compl.c compl.h \
 		  hbuf.c hbuf.h screen.c screen.h logprint.h \
 		  settings.c settings.h hooks.c hooks.h utf8.c utf8.h \
-		  histolog.c histolog.h utils.c utils.h help.c help.h
+		  histolog.c histolog.h utils.c utils.h pgp.c pgp.h \
+		  help.c help.h
 
 LDADD = $(GLIB_LIBS) $(GPGME_LIBS) \
 	../libjabber/liblibjabber.a ../connwrap/libconnwrap.a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/src/pgp.c	Sun Nov 26 10:30:52 2006 +0100
@@ -0,0 +1,460 @@
+/*
+ * pgp.c        -- PGP utility functions
+ *
+ * Copyright (C) 2006 Mikael Berthe <bmikael@lists.lilotux.net>
+ * Some parts inspired by centericq (impgp.cc)
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+
+#ifdef HAVE_GPGME
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <locale.h>
+#include <sys/mman.h>
+#include <glib.h>
+
+#include "pgp.h"
+#include "logprint.h"
+
+#define MIN_GPGME_VERSION "1.0.0"
+
+static struct gpg_struct
+{
+  int enabled;
+  char *private_key;
+  char *passphrase;
+} gpg;
+
+
+//  gpg_init(priv_key, passphrase)
+// Initialize the GPG sub-systems.  This function must be invoked early.
+// Note: priv_key & passphrase are optional, they can be set later.
+// This function returns 0 if gpgme is available and initialized;
+// if not it returns the gpgme error code.
+int gpg_init(const char *priv_key, const char *passphrase)
+{
+  gpgme_error_t err;
+
+  // Check for version and OpenPGP protocol support.
+  if (!gpgme_check_version(MIN_GPGME_VERSION)) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME initialization error: %s", gpgme_strerror(err));
+    return -1;
+  }
+
+  err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME initialization error: %s", gpgme_strerror(err));
+    return err;
+  }
+
+  // Set the locale information.
+  gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
+  gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
+
+  // Store private data.
+  gpg_set_private_key(priv_key);
+  gpg_set_passphrase(passphrase);
+
+  gpg.enabled = 1;
+  return 0;
+}
+
+//  gpg_terminate()
+// Destroy data and free memory.
+void gpg_terminate(void)
+{
+  gpg.enabled = 0;
+  gpg_set_passphrase(NULL);
+  gpg_set_private_key(NULL);
+}
+
+//  gpg_set_passphrase(passphrase)
+// Set the current passphrase (use NULL to erase it).
+void gpg_set_passphrase(const char *passphrase)
+{
+  // Remove current passphrase
+  if (gpg.passphrase) {
+    ssize_t len = strlen(gpg.passphrase);
+    memset(gpg.passphrase, 0, len);
+    munlock(gpg.passphrase, len);
+    g_free(gpg.passphrase);
+  }
+  if (passphrase) {
+    gpg.passphrase = g_strdup(passphrase);
+    mlock(gpg.passphrase, strlen(gpg.passphrase));
+  } else {
+    gpg.passphrase = NULL;
+  }
+}
+
+//  gpg_set_private_key(keyid)
+// Set the current private key id (use NULL to unset it).
+void gpg_set_private_key(const char *priv_keyid)
+{
+  g_free(gpg.private_key);
+  if (priv_keyid)
+    gpg.private_key = g_strdup(priv_keyid);
+  else
+    gpg.private_key = NULL;
+}
+
+//  strip_header_footer(data)
+// Remove PGP header & footer from data.
+// Return a new string, or NULL.
+// The string must be freed by the caller with g_free() when no longer needed.
+static char *strip_header_footer(const char *data)
+{
+  char *p, *q;
+
+  if (!data)
+    return NULL;
+
+  // p: beginning of real data
+  // q: end of real data
+
+  // Strip header (to the first empty line)
+  p = strstr(data, "\n\n");
+  if (!p)
+    return g_strdup(data);
+
+  // Strip footer
+  // We want to remove the last lines, until the line beginning with a '-'
+  p += 2;
+  for (q = p ; *q; q++) ;
+  // (q is at the end of data now)
+  for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ;
+
+  if (q <= p)
+    return NULL; // Shouldn't happen...
+
+  return g_strndup(p, q-p);
+}
+
+//  passphrase_cb()
+// GPGME passphrase callback function.
+static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint,
+                       const char *passphrase_info, int prev_was_bad, int fd)
+{
+  ssize_t len;
+
+  // Abort if we do not have the password.
+  if (!gpg.passphrase) {
+    write(fd, "\n", 1);
+    return gpg_error(GPG_ERR_CANCELED);
+  }
+
+  // Write the passphrase to the file descriptor.
+  len = strlen(gpg.passphrase);
+  if (write(fd, gpg.passphrase, len) != len)
+    return gpg_error(GPG_ERR_CANCELED);
+  if (write(fd, "\n", 1) != 1)
+    return gpg_error(GPG_ERR_CANCELED);
+
+  return 0; // Success
+}
+
+//  gpg_verify(gpg_data, text, *sigsum)
+// Verify that gpg_data is a correct signature for text.
+// Return the key id (or fingerprint), and set *sigsum to
+// the gpgme signature summary value.
+// The returned string must be freed with g_free() after use.
+char *gpg_verify(const char *gpg_data, const char *text,
+                 gpgme_sigsum_t *sigsum)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t data_sign, data_text;
+  char *data;
+  char *verified_key = NULL;
+  gpgme_key_t key;
+  gpgme_error_t err;
+  const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n";
+  const char suffix[] = "\n-----END PGP SIGNATURE-----\n";
+
+  // Reset the summary.
+  *sigsum = 0;
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+
+  // Surround the given data with the prefix & suffix
+  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
+  strcpy(data, prefix);
+  strcat(data, gpg_data);
+  strcat(data, suffix);
+
+  err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0);
+  if (!err) {
+    err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0);
+    if (!err) {
+      err = gpgme_op_verify(ctx, data_sign, data_text, 0);
+      if (!err) {
+        gpgme_verify_result_t vr = gpgme_op_verify_result(ctx);
+        if (vr && vr->signatures) {
+          char *r = vr->signatures->fpr;
+          // Found the fingerprint.  Let's try to get the key id.
+          if(!gpgme_get_key(ctx, r, &key, 0)) {
+            r = key->subkeys->keyid;
+            gpgme_key_release(key);
+          }
+          // r is a static variable, let's copy it.
+          verified_key = g_strdup(r);
+          *sigsum = vr->signatures->summary;
+        }
+      }
+      gpgme_data_release(data_text);
+    }
+    gpgme_data_release(data_sign);
+  }
+  if (err)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  g_free(data);
+  return verified_key;
+}
+
+//  gpg_sign(gpg_data)
+// Return a signature of gpg_data (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_sign(const char *gpg_data)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *p;
+  char *signed_data = NULL;
+  size_t nread;
+  gpgme_key_t key;
+  gpgme_error_t err;
+
+  if (!gpg.enabled || !gpg.private_key)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+  gpgme_set_textmode(ctx, 0);
+  gpgme_set_armor(ctx, 1);
+
+  p = getenv("GPG_AGENT_INFO");
+  if (!(p && strchr(p, ':')))
+    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
+
+  err = gpgme_get_key(ctx, gpg.private_key, &key, 1);
+  if (!err) {
+    gpgme_signers_clear(ctx);
+    gpgme_signers_add(ctx, key);
+    gpgme_key_release(key);
+    err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
+  }
+  if (!err) {
+    err = gpgme_data_new(&out);
+    if (!err) {
+      err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH);
+      if (!err) {
+        signed_data = gpgme_data_release_and_get_mem(out, &nread);
+        if (signed_data) {
+          // We need to add a trailing NULL
+          char *dd = g_strndup(signed_data, nread);
+          free(signed_data);
+          signed_data = strip_header_footer(dd);
+          g_free(dd);
+        }
+      } else {
+        gpgme_data_release(out);
+      }
+    }
+    gpgme_data_release(in);
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  return signed_data;
+}
+
+//  gpg_decrypt(gpg_data)
+// Return decrypted gpg_data (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_decrypt(const char *gpg_data)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *p, *data;
+  char *decrypted_data = NULL;
+  size_t nread;
+  gpgme_error_t err;
+  const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n";
+  const char suffix[] = "\n-----END PGP MESSAGE-----\n";
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+
+  p = getenv("GPG_AGENT_INFO");
+  if (!(p && strchr(p, ':')))
+    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
+
+  // Surround the given data with the prefix & suffix
+  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
+  strcpy(data, prefix);
+  strcat(data, gpg_data);
+  strcat(data, suffix);
+
+  err = gpgme_data_new_from_mem(&in, data, strlen(data), 0);
+  if (!err) {
+    err = gpgme_data_new(&out);
+    if (!err) {
+      err = gpgme_op_decrypt(ctx, in, out);
+      if (!err) {
+        decrypted_data = gpgme_data_release_and_get_mem(out, &nread);
+        if (decrypted_data) {
+          // We need to add a trailing NULL
+          char *dd = g_strndup(decrypted_data, nread);
+          free(decrypted_data);
+          decrypted_data = dd;
+        }
+      } else {
+        gpgme_data_release(out);
+      }
+    }
+    gpgme_data_release(in);
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  g_free(data);
+  return decrypted_data;
+}
+
+//  gpg_encrypt(gpg_data, keyid)
+// Return encrypted gpg_data with the key keyid (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_encrypt(const char *gpg_data, const char *keyid)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *encrypted_data = NULL, *edata;
+  size_t nread;
+  gpgme_key_t key;
+  gpgme_error_t err;
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+  gpgme_set_textmode(ctx, 0);
+  gpgme_set_armor(ctx, 1);
+
+  err = gpgme_get_key(ctx, keyid, &key, 0);
+  if (!err) {
+    gpgme_key_t keys[] = { key, 0 };
+    err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
+    if (!err) {
+      err = gpgme_data_new(&out);
+      if (!err) {
+        err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
+        if (!err)
+          encrypted_data = gpgme_data_release_and_get_mem(out, &nread);
+        else
+          gpgme_data_release(out);
+      }
+      gpgme_data_release(in);
+    }
+    gpgme_key_release(key);
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  edata = strip_header_footer(encrypted_data);
+  if (encrypted_data)
+    free(encrypted_data);
+  return edata;
+}
+
+//  gpg_test_passphrase()
+// Test the current gpg.passphrase with gpg.private_key.
+// If the test doesn't succeed, the passphrase is cleared and a non-null
+// value is returned.
+int gpg_test_passphrase(void)
+{
+  char *s;
+
+  if (!gpg.private_key)
+    return -1; // No private key...
+
+  s = gpg_sign("test");
+  if (s) {
+    free(s);
+    return 0; // Ok, test successful
+  }
+  // The passphrase is wrong (if provided)
+  gpg_set_passphrase(NULL);
+  return -1;
+}
+
+inline int gpg_enabled(void)
+{
+  return gpg.enabled;
+}
+
+#else  /* not HAVE_GPGME */
+
+inline int gpg_enabled(void)
+{
+  return 0;
+}
+
+#endif /* HAVE_GPGME */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/src/pgp.h	Sun Nov 26 10:30:52 2006 +0100
@@ -0,0 +1,29 @@
+#ifndef __PGP_H__
+#define __PGP_H__ 1
+
+#include <config.h>
+
+#ifdef HAVE_GPGME
+
+#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1
+#include <gpgme.h>
+
+int   gpg_init(const char *priv_key, const char *passphrase);
+void  gpg_terminate(void);
+void  gpg_set_passphrase(const char *passphrase);
+void  gpg_set_private_key(const char *priv_keyid);
+char *gpg_verify(const char *gpg_data, const char *text,
+                 gpgme_sigsum_t *sigsum);
+char *gpg_sign(const char *gpg_data);
+char *gpg_decrypt(const char *gpg_data);
+char *gpg_encrypt(const char *gpg_data, const char *keyid);
+
+int   gpg_test_passphrase(void);
+
+#endif /* HAVE_GPGME */
+
+inline int gpg_enabled(void);
+
+#endif /* __PGP_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */