mu/lib/mu-msg-crypto.cc

341 lines
10 KiB
C++

/*
** Copyright (C) 2012-2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** 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.
**
*/
#include "config.h"
#include <string.h>
#include "mu-msg.hh"
#include "mu-msg-priv.hh"
#include "mu-msg-part.hh"
#include "utils/mu-date.h"
#include <gmime/gmime.h>
#include <gmime/gmime-multipart-signed.h>
using namespace Mu;
static const char*
get_pubkey_algo_name(GMimePubKeyAlgo algo)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
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 pubkey algorithm";
}
#pragma GCC diagnostic pop
}
static const gchar*
get_digestkey_algo_name(GMimeDigestAlgo algo)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
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 digest algorithm";
}
#pragma GCC diagnostic pop
}
/* get data from the 'certificate' */
static char*
get_cert_data(GMimeCertificate* cert)
{
const char /**email,*/ *name, *digest_algo, *pubkey_algo, *keyid, *trust;
/* email = g_mime_certificate_get_email (cert); */
name = g_mime_certificate_get_name(cert);
keyid = g_mime_certificate_get_key_id(cert);
digest_algo = get_digestkey_algo_name(g_mime_certificate_get_digest_algo(cert));
pubkey_algo = get_pubkey_algo_name(g_mime_certificate_get_pubkey_algo(cert));
switch (g_mime_certificate_get_trust(cert)) {
case GMIME_TRUST_UNKNOWN: trust = "unknown"; break;
case GMIME_TRUST_UNDEFINED: trust = "undefined"; break;
case GMIME_TRUST_NEVER: trust = "never"; break;
case GMIME_TRUST_MARGINAL: trust = "marginal"; break;
case GMIME_TRUST_FULL: trust = "full"; break;
case GMIME_TRUST_ULTIMATE: trust = "ultimate"; break;
default: g_return_val_if_reached(NULL);
}
return g_strdup_printf("signer:%s, key:%s (%s,%s), trust:%s",
name ? name : "?",
/* email ? email : "?", */
keyid,
pubkey_algo,
digest_algo,
trust);
}
static char*
get_signature_status(GMimeSignatureStatus status)
{
size_t n;
GString* descr;
struct {
GMimeSignatureStatus status;
const char* name;
} status_info[] = {
{GMIME_SIGNATURE_STATUS_VALID, "valid"},
{GMIME_SIGNATURE_STATUS_GREEN, "green"},
{GMIME_SIGNATURE_STATUS_RED, "red"},
{GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key revoked"},
{GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key expired"},
{GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "signature expired"},
{GMIME_SIGNATURE_STATUS_KEY_MISSING, "key missing"},
{GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl missing"},
{GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl too old"},
{GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad policy"},
{GMIME_SIGNATURE_STATUS_SYS_ERROR, "system error"},
{GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu conflict "},
};
descr = g_string_new("");
for (n = 0; n != G_N_ELEMENTS(status_info); ++n) {
if (!(status & status_info[n].status))
continue;
g_string_append_printf(descr,
"%s%s",
descr->len > 0 ? ", " : "",
status_info[n].name);
}
return g_string_free(descr, FALSE);
}
/* get a human-readable report about the signature */
static char*
get_verdict_report(GMimeSignature* msig)
{
time_t t;
const char * created, *expires;
gchar * certdata, *report, *status;
GMimeSignatureStatus sigstat;
sigstat = g_mime_signature_get_status(msig);
status = get_signature_status(sigstat);
t = g_mime_signature_get_created(msig);
created = (t == 0 || t == (time_t)-1) ? "?" : mu_date_str_s("%x", t);
t = g_mime_signature_get_expires(msig);
expires = (t == 0 || t == (time_t)-1) ? "?" : mu_date_str_s("%x", t);
certdata = get_cert_data(g_mime_signature_get_certificate(msig));
report = g_strdup_printf("%s; created:%s, expires:%s, %s",
status,
created,
expires,
certdata ? certdata : "?");
g_free(certdata);
g_free(status);
return report;
}
static char*
get_signers(GHashTable* signerhash)
{
GString* gstr;
GHashTableIter iter;
char* name;
if (!signerhash || g_hash_table_size(signerhash) == 0)
return NULL;
gstr = g_string_new(NULL);
g_hash_table_iter_init(&iter, signerhash);
while (g_hash_table_iter_next(&iter, reinterpret_cast<void**>(&name), NULL)) {
if (gstr->len != 0)
g_string_append_c(gstr, ',');
gstr = g_string_append(gstr, name);
}
return g_string_free(gstr, FALSE);
}
static MuMsgPartSigStatusReport*
get_status_report(GMimeSignatureList* sigs)
{
int i;
MuMsgPartSigStatus status;
MuMsgPartSigStatusReport* status_report;
char* report;
GHashTable* signerhash;
status = MU_MSG_PART_SIG_STATUS_GOOD; /* let's start positive! */
signerhash = g_hash_table_new(g_str_hash, g_str_equal);
for (i = 0, report = NULL; i != g_mime_signature_list_length(sigs); ++i) {
GMimeSignature* msig;
GMimeCertificate* cert;
GMimeSignatureStatus sigstat;
gchar* rep;
msig = g_mime_signature_list_get_signature(sigs, i);
sigstat = g_mime_signature_get_status(msig);
/* downgrade our expectations */
if ((sigstat & GMIME_SIGNATURE_STATUS_ERROR_MASK) &&
status != MU_MSG_PART_SIG_STATUS_ERROR)
status = MU_MSG_PART_SIG_STATUS_ERROR;
else if ((sigstat & GMIME_SIGNATURE_STATUS_RED) &&
status == MU_MSG_PART_SIG_STATUS_GOOD)
status = MU_MSG_PART_SIG_STATUS_BAD;
rep = get_verdict_report(msig);
report = g_strdup_printf("%s%s%d: %s",
report ? report : "",
report ? "; " : "",
i + 1,
rep);
g_free(rep);
cert = g_mime_signature_get_certificate(msig);
if (cert && g_mime_certificate_get_name(cert))
g_hash_table_add(signerhash, (gpointer)g_mime_certificate_get_name(cert));
}
status_report = g_slice_new0(MuMsgPartSigStatusReport);
status_report->verdict = status;
status_report->report = report;
status_report->signers = get_signers(signerhash);
g_hash_table_unref(signerhash);
return status_report;
}
void
Mu::mu_msg_part_sig_status_report_destroy(MuMsgPartSigStatusReport* report)
{
if (!report)
return;
g_free((char*)report->report);
g_free((char*)report->signers);
g_slice_free(MuMsgPartSigStatusReport, report);
}
static inline void
tag_with_sig_status(GObject* part, MuMsgPartSigStatusReport* report)
{
g_object_set_data_full(part,
SIG_STATUS_REPORT,
report,
(GDestroyNotify)mu_msg_part_sig_status_report_destroy);
}
void
Mu::mu_msg_crypto_verify_part(GMimeMultipartSigned* sig, MuMsgOptions opts, GError** err)
{
/* the signature status */
MuMsgPartSigStatusReport* report;
GMimeSignatureList* sigs;
g_return_if_fail(GMIME_IS_MULTIPART_SIGNED(sig));
sigs = g_mime_multipart_signed_verify(sig, GMIME_VERIFY_NONE, err);
if (!sigs) {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "verification failed");
return;
}
report = get_status_report(sigs);
g_clear_object(&sigs);
/* tag this part with the signature status check */
tag_with_sig_status(G_OBJECT(sig), report);
}
static inline void
check_decrypt_result(GMimeMultipartEncrypted* part, GMimeDecryptResult* res, GError** err)
{
GMimeSignatureList* sigs;
MuMsgPartSigStatusReport* report;
if (res) {
/* Check if the decrypted part had any embed signatures */
sigs = res->signatures;
if (sigs) {
report = get_status_report(sigs);
g_mime_signature_list_clear(sigs);
/* tag this part with the signature status check */
tag_with_sig_status(G_OBJECT(part), report);
} else {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "verification failed");
}
g_object_unref(res);
}
}
GMimeObject* /* this is declared in mu-msg-priv.h */
Mu::mu_msg_crypto_decrypt_part(GMimeMultipartEncrypted* enc,
MuMsgOptions opts,
MuMsgPartPasswordFunc func,
gpointer user_data,
GError** err)
{
GMimeObject* dec;
GMimeDecryptResult* res;
g_return_val_if_fail(GMIME_IS_MULTIPART_ENCRYPTED(enc), NULL);
res = NULL;
dec = g_mime_multipart_encrypted_decrypt(enc, GMIME_DECRYPT_NONE, NULL, &res, err);
check_decrypt_result(enc, res, err);
if (!dec) {
if (err && !*err)
mu_util_g_set_error(err, MU_ERROR_CRYPTO, "decryption failed");
return NULL;
}
return dec;
}