/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */ /* * gmime/gpgme glue layer library * Copyright (C) 2004 Albrecht Dreß * * 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, 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" #include #include #include #include #include "gmime-gpgme-context.h" #ifdef HAVE_GETTEXT #include #ifndef _ #define _(x) gettext(x) #endif #else #define _(x) (x) #endif #define GPGME_ERROR_QUARK (g_quark_from_static_string ("gmime-gpgme")) static void g_mime_gpgme_context_class_init(GMimeGpgmeContextClass * klass); static void g_mime_gpgme_context_init(GMimeGpgmeContext * ctx, GMimeGpgmeContextClass * klass); static void g_mime_gpgme_context_finalize(GObject * object); static gboolean g_mime_gpgme_context_check_protocol(GMimeGpgmeContextClass * klass, gpgme_protocol_t protocol, GError ** error); static GMimeCipherHash g_mime_gpgme_hash_id(GMimeCipherContext * ctx, const char *hash); static const char *g_mime_gpgme_hash_name(GMimeCipherContext * ctx, GMimeCipherHash hash); static int g_mime_gpgme_sign(GMimeCipherContext * ctx, const char *userid, GMimeCipherHash hash, GMimeStream * istream, GMimeStream * ostream, GError ** err); static GMimeSignatureValidity *g_mime_gpgme_verify(GMimeCipherContext * ctx, GMimeCipherHash hash, GMimeStream * istream, GMimeStream * sigstream, GError ** err); static int g_mime_gpgme_encrypt(GMimeCipherContext * ctx, gboolean sign, const char *userid, GPtrArray * recipients, GMimeStream * istream, GMimeStream * ostream, GError ** err); static int g_mime_gpgme_decrypt(GMimeCipherContext * ctx, GMimeStream * istream, GMimeStream * ostream, GError ** err); /* internal passphrase callback */ static gpgme_error_t g_mime_session_passphrase(void *HOOK, const char *UID_HINT, const char *PASSPHRASE_INFO, int PREV_WAS_BAD, int FD); /* callbacks for gpgme file handling */ static ssize_t g_mime_gpgme_stream_rd(GMimeStream * stream, void *buffer, size_t size); static ssize_t g_mime_gpgme_stream_wr(GMimeStream * stream, void *buffer, size_t size); static void cb_data_release(void *handle); /* some helper functions */ static gboolean gpgme_add_signer(GMimeGpgmeContext * ctx, const gchar * signer, GError ** error); static gpgme_key_t get_key_from_name(GMimeGpgmeContext * ctx, const gchar * name, gboolean secret_only, GError ** error); static gpgme_key_t *gpgme_build_recipients(GMimeGpgmeContext * ctx, GPtrArray * rcpt_list, GError ** error); static void release_keylist(gpgme_key_t * keylist); static GMimeCipherContextClass *parent_class = NULL; GType g_mime_gpgme_context_get_type(void) { static GType type = 0; if (!type) { static const GTypeInfo info = { sizeof(GMimeGpgmeContextClass), NULL, /* base_class_init */ NULL, /* base_class_finalize */ (GClassInitFunc) g_mime_gpgme_context_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(GMimeGpgmeContext), 0, /* n_preallocs */ (GInstanceInitFunc) g_mime_gpgme_context_init, }; type = g_type_register_static(GMIME_TYPE_CIPHER_CONTEXT, "GMimeGpgmeContext", &info, 0); } return type; } static void g_mime_gpgme_context_class_init(GMimeGpgmeContextClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GMimeCipherContextClass *cipher_class = GMIME_CIPHER_CONTEXT_CLASS(klass); parent_class = g_type_class_ref(G_TYPE_OBJECT); object_class->finalize = g_mime_gpgme_context_finalize; cipher_class->hash_id = g_mime_gpgme_hash_id; cipher_class->hash_name = g_mime_gpgme_hash_name; cipher_class->sign = g_mime_gpgme_sign; cipher_class->verify = g_mime_gpgme_verify; cipher_class->encrypt = g_mime_gpgme_encrypt; cipher_class->decrypt = g_mime_gpgme_decrypt; if (gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP) == GPG_ERR_NO_ERROR) klass->has_proto_openpgp = TRUE; else klass->has_proto_openpgp = FALSE; if (gpgme_engine_check_version(GPGME_PROTOCOL_CMS) == GPG_ERR_NO_ERROR) klass->has_proto_cms = TRUE; else klass->has_proto_cms = FALSE; } static void g_mime_gpgme_context_init(GMimeGpgmeContext * ctx, GMimeGpgmeContextClass * klass) { ctx->singlepart_mode = FALSE; ctx->micalg = NULL; ctx->sig_state = NULL; ctx->key_select_cb = NULL; ctx->passphrase_cb = NULL; } static void g_mime_gpgme_context_finalize(GObject * object) { GMimeGpgmeContext *ctx = (GMimeGpgmeContext *) object; gpgme_release(ctx->gpgme_ctx); if (ctx->micalg) { g_free(ctx->micalg); ctx->micalg = NULL; } if (ctx->sig_state) { g_object_unref(G_OBJECT(ctx->sig_state)); ctx->sig_state = NULL; } G_OBJECT_CLASS(parent_class)->finalize(object); } /* * Convert a hash algorithm name to a number */ static GMimeCipherHash g_mime_gpgme_hash_id(GMimeCipherContext * ctx, const char *hash) { if (hash == NULL) return GMIME_CIPHER_HASH_DEFAULT; if (!g_ascii_strcasecmp(hash, "pgp-")) hash += 4; if (!g_ascii_strcasecmp(hash, "md2")) return GMIME_CIPHER_HASH_MD2; else if (!g_ascii_strcasecmp(hash, "md5")) return GMIME_CIPHER_HASH_MD5; else if (!g_ascii_strcasecmp(hash, "sha1")) return GMIME_CIPHER_HASH_SHA1; else if (!g_ascii_strcasecmp(hash, "ripemd160")) return GMIME_CIPHER_HASH_RIPEMD160; else if (!g_ascii_strcasecmp(hash, "tiger192")) return GMIME_CIPHER_HASH_TIGER192; else if (!g_ascii_strcasecmp(hash, "haval-5-160")) return GMIME_CIPHER_HASH_HAVAL5160; return GMIME_CIPHER_HASH_DEFAULT; } /* * Convert a hash algorithm number to a string */ static const char * g_mime_gpgme_hash_name(GMimeCipherContext * context, GMimeCipherHash hash) { GMimeGpgmeContext *ctx = GMIME_GPGME_CONTEXT(context); char *p; g_return_val_if_fail(ctx, NULL); g_return_val_if_fail(ctx->gpgme_ctx, NULL); /* note: this is only a subset of the hash algorithms gpg(me) supports */ switch (hash) { case GMIME_CIPHER_HASH_MD2: p = "pgp-md2"; break; case GMIME_CIPHER_HASH_MD5: p = "pgp-md5"; break; case GMIME_CIPHER_HASH_SHA1: p = "pgp-sha1"; break; case GMIME_CIPHER_HASH_RIPEMD160: p = "pgp-ripemd160"; break; case GMIME_CIPHER_HASH_TIGER192: p = "pgp-tiger192"; break; case GMIME_CIPHER_HASH_HAVAL5160: p = "pgp-haval-5-160"; break; default: return NULL; } /* S/MIME (RFC2633) doesn't like the leading "pgp-" */ if (gpgme_get_protocol(ctx->gpgme_ctx) == GPGME_PROTOCOL_CMS) p += 4; return p; } /* * Wrapper to convert the passphrase returned from the gmime session to gpgme. */ static gpgme_error_t g_mime_session_passphrase(void *HOOK, const char *UID_HINT, const char *PASSPHRASE_INFO, int PREV_WAS_BAD, int FD) { GMimeCipherContext *ctx = GMIME_CIPHER_CONTEXT(HOOK); GMimeSession *session = ctx->session; gchar *msg, *passphrase; if (PREV_WAS_BAD) msg = g_strdup_printf(_ ("The passphrase for this key was bad, please try again!\n\nKey: %s"), UID_HINT); else msg = g_strdup_printf(_ ("Please enter the passphrase for the secret key!\n\nKey: %s"), UID_HINT); passphrase = g_mime_session_request_passwd(session, PASSPHRASE_INFO, TRUE, msg, NULL); if (passphrase) { write(FD, passphrase, strlen(passphrase)); write(FD, "\n", 1); g_mime_session_forget_passwd(session, msg, NULL); return GPG_ERR_NO_ERROR; } else { write(FD, "\n", 1); return GPG_ERR_CANCELED; } } /* * callback to get data from a stream */ static ssize_t g_mime_gpgme_stream_rd(GMimeStream * stream, void *buffer, size_t size) { ssize_t result; result = g_mime_stream_read(stream, buffer, size); if (result == -1 && g_mime_stream_eos(stream)) result = 0; return result; } /* * callback to write data to a stream */ static ssize_t g_mime_gpgme_stream_wr(GMimeStream * stream, void *buffer, size_t size) { return g_mime_stream_write(stream, buffer, size); } /* * dummy function for callback based gpgme data objects */ static void cb_data_release(void *handle) { /* must just be present... bug or feature?!? */ } /* * Sign istream with the key userid and write the detached signature (standard * mode) or the cleartext signed input (RFC 2440 mode) to ostream. Return 0 on * success and -1 on error. In the latter case, set error to the reason. Note * that gpgme selects the hash algorithm automatically, so we don't use the * arg, but set the value in the context. */ static int g_mime_gpgme_sign(GMimeCipherContext * context, const char *userid, GMimeCipherHash hash, GMimeStream * istream, GMimeStream * ostream, GError ** error) { GMimeGpgmeContext *ctx = (GMimeGpgmeContext *) context; gpgme_sig_mode_t sig_mode; gpgme_ctx_t gpgme_ctx; gpgme_protocol_t protocol; gpgme_error_t err; gpgme_data_t in, out; gpgme_sign_result_t sign_result; struct gpgme_data_cbs cbs = { (gpgme_data_read_cb_t) g_mime_gpgme_stream_rd, /* read method */ (gpgme_data_write_cb_t) g_mime_gpgme_stream_wr, /* write method */ NULL, /* seek method */ cb_data_release /* release method */ }; /* some paranoia checks */ g_return_val_if_fail(ctx, -1); g_return_val_if_fail(ctx->gpgme_ctx, -1); gpgme_ctx = ctx->gpgme_ctx; protocol = gpgme_get_protocol(gpgme_ctx); /* set the signature mode */ if (ctx->singlepart_mode) { if (protocol == GPGME_PROTOCOL_OpenPGP) sig_mode = GPGME_SIG_MODE_CLEAR; else sig_mode = GPGME_SIG_MODE_NORMAL; } else sig_mode = GPGME_SIG_MODE_DETACH; /* find the secret key for the "sign_for" address */ if (!gpgme_add_signer(ctx, userid, error)) return -1; /* OpenPGP signatures are ASCII armored */ gpgme_set_armor(gpgme_ctx, protocol == GPGME_PROTOCOL_OpenPGP); /* set passphrase callback */ if (protocol == GPGME_PROTOCOL_OpenPGP) { if (ctx->passphrase_cb == GPGME_USE_GMIME_SESSION_CB) gpgme_set_passphrase_cb(gpgme_ctx, g_mime_session_passphrase, context); else gpgme_set_passphrase_cb(gpgme_ctx, ctx->passphrase_cb, context); } else gpgme_set_passphrase_cb(gpgme_ctx, NULL, context); /* create gpgme data objects */ if ((err = gpgme_data_new_from_cbs(&in, &cbs, istream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not get data from stream: %s"), gpgme_strsource(err), gpgme_strerror(err)); return -1; } if ((err = gpgme_data_new_from_cbs(&out, &cbs, ostream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not create new data object: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(in); return -1; } if ((err = gpgme_op_sign(gpgme_ctx, in, out, sig_mode)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: signing failed: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(out); gpgme_data_release(in); return -1; } /* get the info about the used hash algorithm */ sign_result = gpgme_op_sign_result(gpgme_ctx); g_free(ctx->micalg); if (protocol == GPGME_PROTOCOL_OpenPGP) ctx->micalg = g_strdup_printf("PGP-%s", gpgme_hash_algo_name(sign_result->signatures-> hash_algo)); else ctx->micalg = g_strdup(gpgme_hash_algo_name (sign_result->signatures->hash_algo)); /* release gpgme data buffers */ gpgme_data_release(in); gpgme_data_release(out); return 0; } /* * In standard mode, verify that sigstream contains a detached signature for * istream. In single-part mode (RFC 2440, RFC 2633 application/pkcs7-mime), * istream contains clearsigned data, and sigstream will be filled with the * verified plaintext. The routine returns a validity object. More information * is saved in the context's signature object. On error error ist set accordingly. */ static GMimeSignatureValidity * g_mime_gpgme_verify(GMimeCipherContext * context, GMimeCipherHash hash, GMimeStream * istream, GMimeStream * sigstream, GError ** error) { GMimeGpgmeContext *ctx = (GMimeGpgmeContext *) context; gpgme_ctx_t gpgme_ctx; gpgme_protocol_t protocol; gpgme_error_t err; gpgme_data_t msg, sig; GMimeSignatureValidity *validity; struct gpgme_data_cbs cbs = { (gpgme_data_read_cb_t) g_mime_gpgme_stream_rd, /* read method */ (gpgme_data_write_cb_t) g_mime_gpgme_stream_wr, /* write method */ NULL, /* seek method */ cb_data_release /* release method */ }; /* some paranoia checks */ g_return_val_if_fail(ctx, NULL); g_return_val_if_fail(ctx->gpgme_ctx, NULL); gpgme_ctx = ctx->gpgme_ctx; protocol = gpgme_get_protocol(gpgme_ctx); /* create the message stream */ if ((err = gpgme_data_new_from_cbs(&msg, &cbs, istream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not get data from stream: %s"), gpgme_strsource(err), gpgme_strerror(err)); return NULL; } /* create data object for the detached signature stream or the "decrypted" * plaintext */ if ((err = gpgme_data_new_from_cbs(&sig, &cbs, sigstream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not get data from stream: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(msg); return NULL; } /* verify the signature */ if (ctx->singlepart_mode) err = gpgme_op_verify(gpgme_ctx, msg, NULL, sig); else err = gpgme_op_verify(gpgme_ctx, sig, msg, NULL); if (err != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: signature verification failed: %s"), gpgme_strsource(err), gpgme_strerror(err)); ctx->sig_state = g_mime_gpgme_sigstat_new(); ctx->sig_state->status = err; ctx->sig_state->protocol = protocol; } else ctx->sig_state = g_mime_gpgme_sigstat_new_from_gpgme_ctx(gpgme_ctx); validity = g_mime_signature_validity_new(); if (ctx->sig_state) { switch (ctx->sig_state->status) { case GPG_ERR_NO_ERROR: g_mime_signature_validity_set_status(validity, GMIME_SIGNATURE_STATUS_GOOD); break; case GPG_ERR_NOT_SIGNED: g_mime_signature_validity_set_status(validity, GMIME_SIGNATURE_STATUS_NONE); break; default: g_mime_signature_validity_set_status(validity, GMIME_SIGNATURE_STATUS_BAD); } } else g_mime_signature_validity_set_status(validity, GMIME_SIGNATURE_STATUS_UNKNOWN); /* release gmgme data buffers */ gpgme_data_release(msg); gpgme_data_release(sig); return validity; } /* * Encrypt istream to ostream for recipients. If sign is set, sign by userid. */ static int g_mime_gpgme_encrypt(GMimeCipherContext * context, gboolean sign, const char *userid, GPtrArray * recipients, GMimeStream * istream, GMimeStream * ostream, GError ** error) { GMimeGpgmeContext *ctx = (GMimeGpgmeContext *) context; gpgme_ctx_t gpgme_ctx; gpgme_key_t *rcpt; gpgme_protocol_t protocol; gpgme_error_t err; gpgme_data_t plain, crypt; struct gpgme_data_cbs cbs = { (gpgme_data_read_cb_t) g_mime_gpgme_stream_rd, /* read method */ (gpgme_data_write_cb_t) g_mime_gpgme_stream_wr, /* write method */ NULL, /* seek method */ cb_data_release /* release method */ }; /* some paranoia checks */ g_return_val_if_fail(ctx, -1); g_return_val_if_fail(ctx->gpgme_ctx, -1); gpgme_ctx = ctx->gpgme_ctx; protocol = gpgme_get_protocol(gpgme_ctx); /* sign & encrypt is valid only for single-part OpenPGP */ if (sign && (!ctx->singlepart_mode || protocol != GPGME_PROTOCOL_OpenPGP)) { if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_ENGINE, _("combined signing and encryption is only defined for RFC 2440")); return -1; } /* if requested, find the secret key for "userid" */ if (sign) { if (!gpgme_add_signer(ctx, userid, error)) return -1; /* set passphrase callback */ if (protocol == GPGME_PROTOCOL_OpenPGP) { if (ctx->passphrase_cb == GPGME_USE_GMIME_SESSION_CB) gpgme_set_passphrase_cb(gpgme_ctx, g_mime_session_passphrase, context); else gpgme_set_passphrase_cb(gpgme_ctx, ctx->passphrase_cb, context); } else gpgme_set_passphrase_cb(gpgme_ctx, NULL, context); } /* build the list of recipients */ if (!(rcpt = gpgme_build_recipients(ctx, recipients, error))) return -1; /* create the data objects */ if (protocol == GPGME_PROTOCOL_OpenPGP) { gpgme_set_armor(gpgme_ctx, 1); gpgme_set_textmode(gpgme_ctx, ctx->singlepart_mode); } if ((err = gpgme_data_new_from_cbs(&plain, &cbs, istream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not get data from stream: %s"), gpgme_strsource(err), gpgme_strerror(err)); release_keylist(rcpt); return -1; } if ((err = gpgme_data_new_from_cbs(&crypt, &cbs, ostream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not create new data object: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(plain); release_keylist(rcpt); return -1; } /* do the encrypt or sign and encrypt operation */ if (sign) err = gpgme_op_encrypt_sign(gpgme_ctx, rcpt, GPGME_ENCRYPT_ALWAYS_TRUST, plain, crypt); else err = gpgme_op_encrypt(gpgme_ctx, rcpt, GPGME_ENCRYPT_ALWAYS_TRUST, plain, crypt); gpgme_data_release(plain); gpgme_data_release(crypt); release_keylist(rcpt); if (err != GPG_ERR_NO_ERROR) { if (error) { if (sign) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: signing and encryption failed: %s"), gpgme_strsource(err), gpgme_strerror(err)); else g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: encryption failed: %s"), gpgme_strsource(err), gpgme_strerror(err)); } return -1; } else return 0; } /* * Decrypt istream to ostream. In RFC 2440 mode, also try to check an included * signature (if any). */ static int g_mime_gpgme_decrypt(GMimeCipherContext * context, GMimeStream * istream, GMimeStream * ostream, GError ** error) { GMimeGpgmeContext *ctx = (GMimeGpgmeContext *) context; gpgme_ctx_t gpgme_ctx; gpgme_error_t err; gpgme_protocol_t protocol; gpgme_data_t plain, crypt; struct gpgme_data_cbs cbs = { (gpgme_data_read_cb_t) g_mime_gpgme_stream_rd, /* read method */ (gpgme_data_write_cb_t) g_mime_gpgme_stream_wr, /* write method */ NULL, /* seek method */ cb_data_release /* release method */ }; /* some paranoia checks */ g_return_val_if_fail(ctx, -1); g_return_val_if_fail(ctx->gpgme_ctx, -1); gpgme_ctx = ctx->gpgme_ctx; protocol = gpgme_get_protocol(gpgme_ctx); /* set passphrase callback (only OpenPGP) */ if (protocol == GPGME_PROTOCOL_OpenPGP) { if (ctx->passphrase_cb == GPGME_USE_GMIME_SESSION_CB) gpgme_set_passphrase_cb(gpgme_ctx, g_mime_session_passphrase, context); else gpgme_set_passphrase_cb(gpgme_ctx, ctx->passphrase_cb, context); } else gpgme_set_passphrase_cb(gpgme_ctx, NULL, context); /* create the data streams */ if ((err = gpgme_data_new_from_cbs(&crypt, &cbs, istream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not get data from stream: %s"), gpgme_strsource(err), gpgme_strerror(err)); return -1; } if ((err = gpgme_data_new_from_cbs(&plain, &cbs, ostream)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not create new data object: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(crypt); return -1; } /* try to decrypt */ if ((err = gpgme_op_decrypt_verify(gpgme_ctx, crypt, plain)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: decryption failed: %s"), gpgme_strsource(err), gpgme_strerror(err)); gpgme_data_release(plain); gpgme_data_release(crypt); return -1; } gpgme_data_release(plain); gpgme_data_release(crypt); /* try to get information about the signature (if any) */ ctx->sig_state = g_mime_gpgme_sigstat_new_from_gpgme_ctx(gpgme_ctx); return 0; } /* * Create a new gpgme cipher context with protocol. If anything fails, return * NULL and set error. */ GMimeCipherContext * g_mime_gpgme_context_new(GMimeSession * session, gpgme_protocol_t protocol, GError ** error) { GMimeCipherContext *cipher; GMimeGpgmeContext *ctx; gpgme_error_t err; gpgme_ctx_t gpgme_ctx; g_return_val_if_fail(GMIME_IS_SESSION(session), NULL); /* creating the gpgme context may fail, so do this first to get the info */ if ((err = gpgme_new(&gpgme_ctx)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not create context: %s"), gpgme_strsource(err), gpgme_strerror(err)); return NULL; } /* create the cipher context */ ctx = g_object_new(GMIME_TYPE_GPGME_CONTEXT, NULL, NULL); if (!ctx) { gpgme_release(gpgme_ctx); return NULL; } else ctx->gpgme_ctx = gpgme_ctx; cipher = (GMimeCipherContext *) ctx; /* check if the requested protocol is available */ if (!g_mime_gpgme_context_check_protocol (GMIME_GPGME_CONTEXT_GET_CLASS(ctx), protocol, error)) { g_object_unref(G_OBJECT(ctx)); return NULL; } /* setup according to requested protocol */ cipher->session = session; g_object_ref(session); gpgme_set_protocol(gpgme_ctx, protocol); if (protocol == GPGME_PROTOCOL_OpenPGP) { cipher->sign_protocol = "application/pgp-signature"; cipher->encrypt_protocol = "application/pgp-encrypted"; cipher->key_protocol = NULL; /* FIXME */ } else { cipher->sign_protocol = "application/pkcs7-signature"; cipher->encrypt_protocol = "application/pkcs7-mime"; cipher->key_protocol = NULL; /* FIXME */ } return cipher; } /* * Verify that protocol is valid and available in klass. Return TRUE on success * and FALSE on fail and set error (if not NULL) in the latter case. */ static gboolean g_mime_gpgme_context_check_protocol(GMimeGpgmeContextClass * klass, gpgme_protocol_t protocol, GError ** error) { switch (protocol) { case GPGME_PROTOCOL_OpenPGP: if (!klass->has_proto_openpgp) { if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_ENGINE, _ ("the crypto engine for protocol OpenPGP is not available")); return FALSE; } break; case GPGME_PROTOCOL_CMS: if (!klass->has_proto_cms) { if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_ENGINE, _ ("the crypto engine for protocol CMS is not available")); return FALSE; } break; default: if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_INV_ENGINE, _("invalid crypto engine %d"), protocol); return FALSE; } return TRUE; } /* * Get a key for name. If secret_only is set, choose only secret (private) * keys. If multiple keys would match, call the key selection CB (if present). * If no matching key could be found or if any error occurs, return NULL and * set error. */ #define KEY_IS_OK(k) (!((k)->expired || (k)->revoked || \ (k)->disabled || (k)->invalid)) static gpgme_key_t get_key_from_name(GMimeGpgmeContext * ctx, const gchar * name, gboolean secret_only, GError ** error) { GList *keys = NULL; gpgme_ctx_t gpgme_ctx = ctx->gpgme_ctx; gpgme_key_t key; gpgme_error_t err; if ((err = gpgme_op_keylist_start(gpgme_ctx, name, secret_only)) != GPG_ERR_NO_ERROR) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not list keys for %s: %s"), gpgme_strsource(err), name, gpgme_strerror(err)); return NULL; } while ((err = gpgme_op_keylist_next(gpgme_ctx, &key)) == GPG_ERR_NO_ERROR) if (KEY_IS_OK(key) && key->subkeys && KEY_IS_OK(key->subkeys)) keys = g_list_append(keys, key); if (gpg_err_code(err) != GPG_ERR_EOF) { if (error) g_set_error(error, GPGME_ERROR_QUARK, err, _("%s: could not list keys for %s: %s"), gpgme_strsource(err), name, gpgme_strerror(err)); gpgme_op_keylist_end(gpgme_ctx); g_list_foreach(keys, (GFunc) gpgme_key_unref, NULL); g_list_free(keys); return NULL; } gpgme_op_keylist_end(gpgme_ctx); if (!keys) { if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION, _("%s: could not find a key for %s"), "gmime-gpgme", name); return NULL; } if (g_list_length(keys) > 1) { if (ctx->key_select_cb) key = ctx->key_select_cb(name, secret_only, ctx, keys); else { if (error) g_set_error(error, GPGME_ERROR_QUARK, GPG_ERR_KEY_SELECTION, _("%s: multiple keys for %s"), "gmime-gpgme", name); key = NULL; } if (key) gpgme_key_ref(key); g_list_foreach(keys, (GFunc) gpgme_key_unref, NULL); } else key = (gpgme_key_t) keys->data; g_list_free(keys); return key; } /* * Add signer to ctx's list of signers and return TRUE on success or FALSE * on error. */ static gboolean gpgme_add_signer(GMimeGpgmeContext * ctx, const gchar * signer, GError ** error) { gpgme_key_t key = NULL; if (!(key = get_key_from_name(ctx, signer, TRUE, error))) return FALSE; /* set the key (the previous operation guaranteed that it exists, no need * 2 check return values...) */ gpgme_signers_add(ctx->gpgme_ctx, key); gpgme_key_unref(key); return TRUE; } /* * Build a NULL-terminated array of keys for all recipients in rcpt_list and * return it. The caller has to take care that it's released. If something * goes wrong, NULL is returned. */ static gpgme_key_t * gpgme_build_recipients(GMimeGpgmeContext * ctx, GPtrArray * rcpt_list, GError ** error) { gpgme_key_t *rcpt = g_new0(gpgme_key_t, rcpt_list->len + 1); guint num_rcpts; /* try to find the public key for every recipient */ for (num_rcpts = 0; num_rcpts < rcpt_list->len; num_rcpts++) { gchar *name = (gchar *) g_ptr_array_index(rcpt_list, num_rcpts); gpgme_key_t key; if (!(key = get_key_from_name(ctx, name, FALSE, error))) { release_keylist(rcpt); return NULL; } /* set the recipient */ rcpt[num_rcpts] = key; g_message("encrypt for %s with fpr %s", name, key->subkeys->fpr); } g_message("encrypting for %d recipient(s)", num_rcpts); return rcpt; } /* * helper function: unref all keys in the NULL-terminated array keylist * and finally release the array itself */ static void release_keylist(gpgme_key_t * keylist) { gpgme_key_t *key = keylist; while (*key) { gpgme_key_unref(*key); key++; } g_free(keylist); }