Mercurial > hg
view mcabber/src/pgp.c @ 1369:acf5bbdddfc0
Update PL help files (entragian)
author | Mikael Berthe <mikael@lilotux.net> |
---|---|
date | Sat, 17 Nov 2007 11:35:23 +0100 |
parents | 80008fe2a4f2 |
children | f89844a0448a |
line wrap: on
line source
/* * pgp.c -- PGP utility functions * * Copyright (C) 2006-2007 Mikael Berthe <mikael@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, "GPGME initialization error: Bad library version"); 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) && key) { 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; // For some reason summary could be 0 when status is 0 too, // which means the signature is valid... if (!*sigsum && !vr->signatures->status) *sigsum = GPGME_SIGSUM_GREEN; } } gpgme_data_release(data_text); } gpgme_data_release(data_sign); } if (err) scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, "GPGME verification 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 || !key) { scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found"); gpgme_release(ctx); return NULL; } 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 signature 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 decryption 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 && key) { 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); } else { scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found"); err = 0; } if (err && err != GPG_ERR_CANCELED) scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, "GPGME encryption 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... */