/* ** Copyright (C) 2012 Dirk-Jan C. Binnema ** ** 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 3, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ #if HAVE_CONFIG_H #include "config.h" #endif /*HAVE_CONFIG_H*/ #include "mu-msg.h" #include "mu-msg-priv.h" #include "mu-msg-part.h" #include "mu-msg-crypto.h" #include "mu-date.h" #include #include static gboolean dummy_password_requester (GMimeCryptoContext *ctx, const char *user_id, const char* prompt_ctx, gboolean reprompt, GMimeStream *response, GError **err) { g_print ("password requested\n"); return TRUE; } GMimeCryptoContext* get_gpg_crypto_context (MuMsgPartOptions opts, GError **err) { GMimeCryptoContext *ctx; const char *prog; ctx = NULL; prog = g_getenv ("MU_GPG_PATH"); if (prog) ctx = g_mime_gpg_context_new ( (GMimePasswordRequestFunc)dummy_password_requester, prog); else { char *path; path = g_find_program_in_path ("gpg"); if (path) ctx = g_mime_gpg_context_new ( (GMimePasswordRequestFunc)dummy_password_requester, path); g_free (path); } if (!ctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get GPG crypto context"); return NULL; } g_mime_gpg_context_set_use_agent (GMIME_GPG_CONTEXT(ctx), opts & MU_MSG_PART_OPTION_USE_AGENT); g_mime_gpg_context_set_always_trust (GMIME_GPG_CONTEXT(ctx), FALSE); g_mime_gpg_context_set_auto_key_retrieve (GMIME_GPG_CONTEXT(ctx), opts & MU_MSG_PART_OPTION_AUTO_RETRIEVE_KEY); return ctx; } GMimeCryptoContext* get_pkcs7_crypto_context (MuMsgPartOptions opts, GError **err) { GMimeCryptoContext *ctx; ctx = g_mime_pkcs7_context_new ((GMimePasswordRequestFunc)dummy_password_requester); if (!ctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get PKCS7 crypto context"); return NULL; } g_mime_pkcs7_context_set_always_trust (GMIME_PKCS7_CONTEXT(ctx), FALSE); return ctx; } const char* get_pubkey_algo_name (GMimePubKeyAlgo algo) { switch (algo) { case GMIME_PUBKEY_ALGO_DEFAULT: return "default"; case GMIME_PUBKEY_ALGO_RSA: return "RSA"; case GMIME_PUBKEY_ALGO_RSA_E: return "RSA (encryption only)"; case GMIME_PUBKEY_ALGO_RSA_S: return "RSA (signing only)"; case GMIME_PUBKEY_ALGO_ELG_E: return "ElGamal (encryption only)"; case GMIME_PUBKEY_ALGO_DSA: return "DSA"; case GMIME_PUBKEY_ALGO_ELG: return "ElGamal"; default: return "unknown algorithm"; } } const gchar* get_digestkey_algo_name (GMimeDigestAlgo algo) { switch (algo) { case GMIME_DIGEST_ALGO_DEFAULT: return "default"; case GMIME_DIGEST_ALGO_MD5: return "MD5"; case GMIME_DIGEST_ALGO_SHA1: return "SHA-1"; case GMIME_DIGEST_ALGO_RIPEMD160: return "RIPEMD160"; case GMIME_DIGEST_ALGO_MD2: return "MD2"; case GMIME_DIGEST_ALGO_TIGER192: return "TIGER-192"; case GMIME_DIGEST_ALGO_HAVAL5160: return "HAVAL-5-160"; case GMIME_DIGEST_ALGO_SHA256: return "SHA-256"; case GMIME_DIGEST_ALGO_SHA384: return "SHA-384"; case GMIME_DIGEST_ALGO_SHA512: return "SHA-512"; case GMIME_DIGEST_ALGO_SHA224: return "SHA-224"; case GMIME_DIGEST_ALGO_MD4: return "MD4"; default: return "unknown algorithm"; } } static void harvest_certificate_info (GMimeSignature *sig, MuMsgPartSigInfo *siginfo) { GMimeCertificate *cert; cert = g_mime_signature_get_certificate (sig); if (!cert) return; /* nothing to harvest */ siginfo->_cert = cert; siginfo->issuer_serial = g_mime_certificate_get_issuer_serial (cert); siginfo->issuer_name = g_mime_certificate_get_issuer_name (cert); siginfo->fingerprint = g_mime_certificate_get_fingerprint (cert); siginfo->key_id = g_mime_certificate_get_key_id (cert); siginfo->email = g_mime_certificate_get_email (cert); siginfo->name = g_mime_certificate_get_name (cert); siginfo->pubkey_algo = get_pubkey_algo_name (g_mime_certificate_get_pubkey_algo (cert)); siginfo->digest_algo = get_digestkey_algo_name (g_mime_certificate_get_digest_algo (cert)); } static MuMsgPartSigInfo* sig_info_new (GMimeSignature *sig) { MuMsgPartSigInfo *siginfo; MuMsgPartSigStatus status; switch (g_mime_signature_get_status (sig)) { case GMIME_SIGNATURE_STATUS_GOOD: status = MU_MSG_PART_SIG_STATUS_GOOD; break; case GMIME_SIGNATURE_STATUS_BAD: status = MU_MSG_PART_SIG_STATUS_BAD; break; default: status = MU_MSG_PART_SIG_STATUS_ERROR; break; } if (status != MU_MSG_PART_SIG_STATUS_GOOD) { GMimeSignatureError sigerr; sigerr = g_mime_signature_get_errors (sig); if (sigerr & GMIME_SIGNATURE_ERROR_EXPSIG) status |= MU_MSG_PART_SIG_STATUS_EXPSIG; if (sigerr & GMIME_SIGNATURE_ERROR_NO_PUBKEY) status |= MU_MSG_PART_SIG_STATUS_NO_PUBKEY; if (sigerr & GMIME_SIGNATURE_ERROR_EXPKEYSIG) status |= MU_MSG_PART_SIG_STATUS_EXPKEYSIG; if (sigerr & GMIME_SIGNATURE_ERROR_REVKEYSIG) status |= MU_MSG_PART_SIG_STATUS_REVKEYSIG; if (sigerr & GMIME_SIGNATURE_ERROR_UNSUPP_ALGO) status |= MU_MSG_PART_SIG_STATUS_UNSUPP_ALGO; } siginfo = g_new0 (MuMsgPartSigInfo, 1); siginfo->status = status; siginfo->created = g_mime_signature_get_created (sig); siginfo->expires = g_mime_signature_get_expires (sig); harvest_certificate_info (sig, siginfo); return siginfo; } static void sig_info_destroy (MuMsgPartSigInfo *siginfo) { if (!siginfo) return; g_clear_object (&siginfo->_cert); g_free (siginfo); } /* we create a fake siginfo when things go wrong */ static GSList* error_sig_infos (void) { MuMsgPartSigInfo *sig_info; sig_info = g_new0 (MuMsgPartSigInfo, 1); sig_info->status = MU_MSG_PART_SIG_STATUS_FAIL; return g_slist_prepend (NULL, sig_info); } GSList* mu_msg_mime_sig_infos (GMimeMultipartSigned *sigmpart, MuMsgPartOptions opts, GError **err) { int i; GMimeSignatureList *sigs; GMimeCryptoContext *cctx; GSList *siginfos; if (!GMIME_IS_MULTIPART_SIGNED (sigmpart)) { mu_util_g_set_error (err, MU_ERROR_IN_PARAMETERS, "not a multipart/signed part"); return NULL; /* error */ } if (opts & MU_MSG_PART_OPTION_USE_PKCS7) cctx = get_pkcs7_crypto_context (opts, err); else cctx = get_gpg_crypto_context (opts, err); /* return a fake siginfos with the error */ if (!cctx) return error_sig_infos (); /* error */ sigs = g_mime_multipart_signed_verify (sigmpart, cctx, err); g_object_unref (cctx); if (!sigs) return NULL; /* error */ for (i = 0, siginfos = NULL; i != g_mime_signature_list_length (sigs); ++i) { MuMsgPartSigInfo *siginfo; siginfo = sig_info_new (g_mime_signature_list_get_signature (sigs, i)); siginfos = g_slist_prepend (siginfos, siginfo); } return siginfos; } void mu_msg_part_free_sig_infos (GSList *siginfos) { g_slist_foreach (siginfos, (GFunc)sig_info_destroy, NULL); g_slist_free (siginfos); } const char* mu_msg_part_sig_status_to_string (MuMsgPartSigStatus status) { switch (status) { case MU_MSG_PART_SIG_STATUS_UNKNOWN: return "no signed part found"; case MU_MSG_PART_SIG_STATUS_GOOD: return "good"; case MU_MSG_PART_SIG_STATUS_BAD: return "bad signature"; case MU_MSG_PART_SIG_STATUS_ERROR: return "error verifying signature"; case MU_MSG_PART_SIG_STATUS_FAIL: return "crypto failed"; case MU_MSG_PART_SIG_STATUS_EXPSIG: return "signature is expired"; case MU_MSG_PART_SIG_STATUS_NO_PUBKEY: return "no public key found"; case MU_MSG_PART_SIG_STATUS_EXPKEYSIG: return "expired public key"; case MU_MSG_PART_SIG_STATUS_REVKEYSIG: return "revoked public key"; case MU_MSG_PART_SIG_STATUS_UNSUPP_ALGO: return "unsupported algorithm"; default: g_warning ("%s: invalid status %d", __FUNCTION__, status); return "invalid status"; } } char* mu_msg_part_sig_statuses_to_string (MuMsgPartSigStatus status) { unsigned u; GString *gstr; MuMsgPartSigStatus statuses[] = { MU_MSG_PART_SIG_STATUS_UNKNOWN, MU_MSG_PART_SIG_STATUS_GOOD, MU_MSG_PART_SIG_STATUS_BAD, MU_MSG_PART_SIG_STATUS_ERROR, MU_MSG_PART_SIG_STATUS_FAIL, MU_MSG_PART_SIG_STATUS_EXPSIG, MU_MSG_PART_SIG_STATUS_NO_PUBKEY, MU_MSG_PART_SIG_STATUS_EXPKEYSIG, MU_MSG_PART_SIG_STATUS_REVKEYSIG, MU_MSG_PART_SIG_STATUS_UNSUPP_ALGO }; if (status == MU_MSG_PART_SIG_STATUS_UNKNOWN) return g_strdup (mu_msg_part_sig_status_to_string (status)); gstr = g_string_sized_new (128); for (u = 0; u != G_N_ELEMENTS(statuses); ++u) { const gchar *statstr; if (!(status & statuses[u])) continue; statstr = mu_msg_part_sig_status_to_string (statuses[u]); if (gstr->len != 0) gstr = g_string_append (gstr, ", "); gstr = g_string_append (gstr, statstr); } return g_string_free (gstr, FALSE); } char* mu_msg_part_sig_info_to_string (MuMsgPartSigInfo *info) { GString *gstr; gchar *statuses; g_return_val_if_fail (info, NULL); gstr = g_string_sized_new (128); statuses = mu_msg_part_sig_statuses_to_string (info->status); g_string_append_printf (gstr, "status: %s", statuses); g_free (statuses); if (info->status & MU_MSG_PART_SIG_STATUS_ERROR || info->status & MU_MSG_PART_SIG_STATUS_FAIL) return g_string_free (gstr, FALSE); g_string_append_printf (gstr, "; algorithms (P/D) (%s, %s)", info->pubkey_algo, info->digest_algo); g_string_append_printf (gstr, "; created: %s, expires: %s", mu_date_str_s ("%c", info->created), mu_date_str_s ("%c", info->expires)); if (info->name || info->email) g_string_append_printf (gstr, "; who:%s %s", info->name ? info-> name : "", info->email ? info->email : ""); if (info->issuer_name && info->issuer_serial) g_string_append_printf (gstr, "; issuer: %s (%s)", info->issuer_name, info->issuer_serial); if (info->fingerprint) g_string_append_printf (gstr, "; fingerprint: %s", info->fingerprint); return g_string_free (gstr, FALSE); }