diff --git a/src/addressbook.cpp b/src/addressbook.cpp index 7a92961..09857e1 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -1,306 +1,473 @@ /* addressbook.cpp - Functions for the Addressbook * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "addressbook.h" #include "oomhelp.h" #include "keycache.h" #include "mail.h" #include "cpphelp.h" #include "windowmessages.h" #include #include #include +#include typedef struct { std::string name; - std::string data; + std::string pgp_data; + std::string cms_data; HWND hwnd; shared_disp_t contact; } keyadder_args_t; static std::set s_checked_entries; +static Addressbook::callback_args_t +parse_output (const std::string &output) +{ + Addressbook::callback_args_t ret; + + std::istringstream ss(output); + std::string line; + + std::string pgp_data; + std::string cms_data; + + bool in_pgp_data = false; + bool in_options = false; + bool in_cms_data = false; + while (std::getline (ss, line)) + { + rtrim (line); + if (in_pgp_data) + { + if (line == "empty") + { + pgp_data = ""; + } + else if (line != "END KEYADDER PGP DATA") + { + pgp_data += line + std::string("\n"); + } + else + { + in_pgp_data = false; + } + } + else if (in_cms_data) + { + if (line == "empty") + { + in_cms_data = false; + cms_data = ""; + } + else if (line != "END KEYADDER CMS DATA") + { + cms_data += line + std::string("\n"); + } + else + { + in_cms_data = false; + } + } + else if (in_options) + { + if (line == "END KEYADDER OPTIONS") + { + in_options = false; + continue; + } + std::istringstream lss (line); + std::string key, value; + + std::getline (lss, key, '='); + std::getline (lss, value, '='); + + if (key == "secure") + { + int val = atoi (value.c_str()); + if (val > 3 || val < 0) + { + log_error ("%s:%s: Loading secure value: %s failed", + SRCNAME, __func__, value.c_str ()); + continue; + } + ret.crypto_flags = val; + } + else + { + log_debug ("%s:%s: Unknown setting: %s", + SRCNAME, __func__, key.c_str ()); + } + continue; + } + else + { + if (line == "BEGIN KEYADDER OPTIONS") + { + in_options = true; + } + else if (line == "BEGIN KEYADDER CMS DATA") + { + in_cms_data = true; + } + else if (line == "BEGIN KEYADDER PGP DATA") + { + in_pgp_data = true; + } + else + { + log_debug ("%s:%s: Unknown line: %s", + SRCNAME, __func__, line.c_str ()); + } + } + } + + ret.pgp_data = xstrdup (pgp_data.c_str ()); + ret.cms_data = xstrdup (cms_data.c_str ()); + + return ret; +} + static DWORD WINAPI open_keyadder (LPVOID arg) { TSTART; auto adder_args = std::unique_ptr ((keyadder_args_t*) arg); std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; TRETURN -1; } const auto keyadder = std::string (gpg4win_dir) + "\\bin\\gpgolkeyadder.exe"; args.push_back (keyadder); args.push_back (std::string ("--hwnd")); args.push_back (std::to_string ((int) (intptr_t) adder_args->hwnd)); args.push_back (std::string ("--username")); args.push_back (adder_args->name); + if (opt.enable_smime) + { + args.push_back (std::string ("--cms")); + } + auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen TRACEPOINT; TRETURN -1; } - GpgME::Data mystdin (adder_args->data.c_str(), adder_args->data.size(), + std::string input = adder_args->pgp_data; + input += "BEGIN CMS DATA\n"; + input += adder_args->cms_data; + + GpgME::Data mystdin (input.c_str(), input.size(), false); GpgME::Data mystdout, mystderr; char **cargs = vector_to_cArray (args); log_data ("%s:%s: launching keyadder args:", SRCNAME, __func__); for (size_t i = 0; cargs && cargs[i]; i++) { log_data (SIZE_T_FORMAT ": '%s'", i, cargs[i]); } GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); release_cArray (cargs); if (err) { log_error ("%s:%s: Err code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); TRETURN 0; } - auto newKey = mystdout.toString (); + auto output = mystdout.toString (); - rtrim(newKey); + rtrim(output); - if (newKey.empty()) + if (output.empty()) { log_debug ("%s:%s: keyadder canceled.", SRCNAME, __func__); TRETURN 0; } - if (newKey == "empty") - { - log_debug ("%s:%s: keyadder empty.", SRCNAME, __func__); - newKey = ""; - } - - Addressbook::callback_args_t cb_args; - /* cb args are valid in the same scope as newKey */ - cb_args.data = newKey.c_str(); + Addressbook::callback_args_t cb_args = parse_output (output); cb_args.contact = adder_args->contact; do_in_ui_thread (CONFIG_KEY_DONE, (void*) &cb_args); + xfree (cb_args.pgp_data); + xfree (cb_args.cms_data); TRETURN 0; } void Addressbook::update_key_o (void *callback_args) { TSTART; if (!callback_args) { TRACEPOINT; TRETURN; } callback_args_t *cb_args = static_cast (callback_args); LPDISPATCH contact = cb_args->contact.get(); LPDISPATCH user_props = get_oom_object (contact, "UserProperties"); if (!user_props) { TRACEPOINT; TRETURN; } LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key"); if (!pgp_key) { TRACEPOINT; + gpgol_release (user_props); TRETURN; } - put_oom_string (pgp_key, "Value", cb_args->data); + put_oom_string (pgp_key, "Value", cb_args->pgp_data); + gpgol_release (pgp_key); log_debug ("%s:%s: PGP key data updated", SRCNAME, __func__); - gpgol_release (pgp_key); + if (opt.enable_smime) + { + LPDISPATCH cms_data = find_or_add_text_prop (user_props, + "GpgOL CMS Cert"); + if (!cms_data) + { + TRACEPOINT; + gpgol_release (user_props); + TRETURN; + } + put_oom_string (cms_data, "Value", cb_args->cms_data); + gpgol_release (cms_data); + log_debug ("%s:%s: CMS key data updated", + SRCNAME, __func__); + } + gpgol_release (user_props); s_checked_entries.clear (); TRETURN; } void Addressbook::edit_key_o (LPDISPATCH contact) { TSTART; if (!contact) { TRACEPOINT; TRETURN; } - LPDISPATCH user_props = get_oom_object (contact, "UserProperties"); + auto user_props = MAKE_SHARED (get_oom_object (contact, "UserProperties")); if (!user_props) { TRACEPOINT; TRETURN; } - auto pgp_key = MAKE_SHARED ( - find_or_add_text_prop (user_props, "OpenPGP Key")); - gpgol_release (user_props); + auto pgp_key = MAKE_SHARED (find_or_add_text_prop (user_props.get (), + "OpenPGP Key")); if (!pgp_key) { TRACEPOINT; TRETURN; } char *key_data = get_oom_string (pgp_key.get(), "Value"); if (!key_data) { TRACEPOINT; TRETURN; } + char *cms_data = nullptr; + if (opt.enable_smime) + { + auto cms_key = MAKE_SHARED (find_or_add_text_prop (user_props.get (), + "GpgOL CMS Cert")); + cms_data = get_oom_string (cms_key.get(), "Value"); + if (!cms_data) + { + TRACEPOINT; + TRETURN; + } + } + char *name = get_oom_string (contact, "Subject"); if (!name) { TRACEPOINT; name = get_oom_string (contact, "Email1Address"); if (!name) { name = xstrdup (/* TRANSLATORS: Placeholder for a contact without a configured name */ _("Unknown contact")); } } keyadder_args_t *args = new keyadder_args_t; args->name = name; - args->data = key_data; + args->pgp_data = key_data; + args->cms_data = cms_data ? cms_data : ""; args->hwnd = get_active_hwnd (); contact->AddRef (); memdbg_addRef (contact); args->contact = MAKE_SHARED (contact); CloseHandle (CreateThread (NULL, 0, open_keyadder, (LPVOID) args, 0, NULL)); xfree (name); xfree (key_data); + xfree (cms_data); TRETURN; } /* For each new recipient check the address book to look for a potentially configured key for this recipient and import / register it into the keycache. */ void Addressbook::check_o (Mail *mail) { TSTART; if (!mail) { TRACEPOINT; TRETURN; } LPDISPATCH mailitem = mail->item (); if (!mailitem) { TRACEPOINT; TRETURN; } auto recipients_obj = MAKE_SHARED (get_oom_object (mailitem, "Recipients")); if (!recipients_obj) { TRACEPOINT; TRETURN; } bool err = false; const auto recipient_entries = get_oom_recipients_with_addrEntry (recipients_obj.get(), &err); for (const auto pair: recipient_entries) { if (s_checked_entries.find (pair.first) != s_checked_entries.end ()) { continue; } if (!pair.second) { TRACEPOINT; continue; } auto contact = MAKE_SHARED (get_oom_object (pair.second.get (), "GetContact")); if (!contact) { log_debug ("%s:%s: failed to resolve contact for %s", SRCNAME, __func__, anonstr (pair.first.c_str())); continue; } s_checked_entries.insert (pair.first); LPDISPATCH user_props = get_oom_object (contact.get (), "UserProperties"); if (!user_props) { TRACEPOINT; continue; } LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key"); + LPDISPATCH cms_prop = nullptr; + + if (opt.enable_smime) + { + cms_prop = find_or_add_text_prop (user_props, "GpgOL CMS Cert"); + } + gpgol_release (user_props); - if (!pgp_key) + if (!pgp_key && !cms_prop) { continue; } - log_debug ("%s:%s: found configured pgp key for %s", + log_debug ("%s:%s: found configured key for %s", SRCNAME, __func__, anonstr (pair.first.c_str())); - char *key_data = get_oom_string (pgp_key, "Value"); - if (!key_data || !strlen (key_data)) + char *pgp_data = get_oom_string (pgp_key, "Value"); + char *cms_data = nullptr; + if (cms_prop) + { + cms_data = get_oom_string (cms_prop, "Value"); + } + + if ((!pgp_data || !strlen (pgp_data)) + && (!cms_data || !strlen (cms_data))) { log_debug ("%s:%s: No key data", SRCNAME, __func__); } - KeyCache::instance ()->importFromAddrBook (pair.first, key_data, - mail); - xfree (key_data); + if (pgp_data && strlen (pgp_data)) + { + KeyCache::instance ()->importFromAddrBook (pair.first, pgp_data, + mail, GpgME::OpenPGP); + } + if (cms_data && strlen (cms_data)) + { + KeyCache::instance ()->importFromAddrBook (pair.first, cms_data, + mail, GpgME::CMS); + } + xfree (pgp_data); + xfree (cms_data); gpgol_release (pgp_key); + gpgol_release (cms_prop); } TRETURN; } diff --git a/src/addressbook.h b/src/addressbook.h index c175bb8..5705fdd 100644 --- a/src/addressbook.h +++ b/src/addressbook.h @@ -1,48 +1,50 @@ #ifndef SRC_ADDRESSBOOK_H #define SRC_ADDRESSBOOK_H /* addressbook.h - Functions for the Addressbook * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "common.h" #include #include #include #include "oomhelp.h" class Mail; namespace Addressbook { typedef struct { shared_disp_t contact; - const char *data; + char *pgp_data; + char *cms_data; + int crypto_flags; } callback_args_t; /* Configure the OpenPGP Key for this contact. */ void edit_key_o (LPDISPATCH contact); /* Check the address book for keys to import. */ void check_o (Mail *mail); /* Update the key information for a contact. */ void update_key_o (void *callback_args); } // namespace Addressbook #endif // SRC_ADDRESSBOOK_H diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp index 88ec97f..5d965ae 100644 --- a/src/cryptcontroller.cpp +++ b/src/cryptcontroller.cpp @@ -1,1275 +1,1292 @@ /* @file cryptcontroller.cpp * @brief Helper to do crypto on a mail. * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "common.h" #include "cpphelp.h" #include "cryptcontroller.h" #include "mail.h" #include "mapihelp.h" #include "mimemaker.h" #include "wks-helper.h" #include "overlay.h" #include "keycache.h" #include "mymapitags.h" #include #include #include #include "common.h" #include static int sink_data_write (sink_t sink, const void *data, size_t datalen) { GpgME::Data *d = static_cast(sink->cb_data); d->write (data, datalen); return 0; } static int create_sign_attach (sink_t sink, protocol_t protocol, GpgME::Data &signature, GpgME::Data &signedData, const char *micalg); /** We have some C Style cruft in here as this was historically how GpgOL worked directly in the MAPI data objects. To reduce the regression risk the new object oriented way for crypto reused as much as possible from this. */ CryptController::CryptController (Mail *mail, bool encrypt, bool sign, GpgME::Protocol proto): m_mail (mail), m_encrypt (encrypt), m_sign (sign), m_crypto_success (false), m_proto (proto) { TSTART; memdbg_ctor ("CryptController"); log_debug ("%s:%s: CryptController ctor for %p encrypt %i sign %i inline %i.", SRCNAME, __func__, mail, encrypt, sign, mail->getDoPGPInline ()); m_recipient_addrs = mail->getCachedRecipients (); TRETURN; } CryptController::~CryptController() { TSTART; memdbg_dtor ("CryptController"); log_debug ("%s:%s:%p", SRCNAME, __func__, m_mail); TRETURN; } int CryptController::collect_data () { TSTART; /* Get the attachment info and the body. We need to do this before creating the engine's filter because sending the cancel to the engine with nothing for the engine to process. Will result in an error. This is actually a bug in our engine code but we better avoid triggering this bug because the engine sometimes hangs. Fixme: Needs a proper fix. */ /* Take the Body from the mail if possible. This is a fix for GnuPG-Bug-ID: T3614 because the body is not always properly updated in MAPI when sending. */ char *body = m_mail->takeCachedPlainBody (); if (body && !*body) { xfree (body); body = nullptr; } LPMESSAGE message = m_mail->isCryptoMail() ? get_oom_base_message (m_mail->item ()) : get_oom_message (m_mail->item ()); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); } auto att_table = mapi_create_attach_table (message, 0); int n_att_usable = count_usable_attachments (att_table); if (!n_att_usable && !body) { if (!m_mail->isDraftEncrypt()) { gpgol_message_box (m_mail->getWindow (), utf8_gettext ("Can't encrypt / sign an empty message."), utf8_gettext ("GpgOL"), MB_OK); } gpgol_release (message); mapi_release_attach_table (att_table); xfree (body); TRETURN -1; } bool do_inline = m_mail->getDoPGPInline (); if (n_att_usable && do_inline) { log_debug ("%s:%s: PGP Inline not supported for attachments." " Using PGP MIME", SRCNAME, __func__); do_inline = false; m_mail->setDoPGPInline (false); } else if (do_inline) { /* Inline. Use Body as input. We need to collect also our mime structure for S/MIME as we don't know yet if we are S/MIME or OpenPGP */ m_bodyInput.write (body, strlen (body)); log_debug ("%s:%s: Inline. Caching body.", SRCNAME, __func__); /* Set the input buffer to start. */ m_bodyInput.seek (0, SEEK_SET); } /* Set up the sink object to collect the mime structure */ struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &m_input; sink->writefnc = sink_data_write; /* Collect the mime strucutre */ int err = add_body_and_attachments (sink, message, att_table, m_mail, body, n_att_usable); xfree (body); if (err) { log_error ("%s:%s: Collecting body and attachments failed.", SRCNAME, __func__); gpgol_release (message); mapi_release_attach_table (att_table); TRETURN -1; } /* Message is no longer needed */ gpgol_release (message); mapi_release_attach_table (att_table); /* Set the input buffer to start. */ m_input.seek (0, SEEK_SET); TRETURN 0; } int CryptController::lookup_fingerprints (const std::string &sigFpr, const std::vector recpFprs) { TSTART; auto ctx = std::shared_ptr (GpgME::Context::createForProtocol (m_proto)); if (!ctx) { log_error ("%s:%s: failed to create context with protocol '%s'", SRCNAME, __func__, m_proto == GpgME::CMS ? "smime" : m_proto == GpgME::OpenPGP ? "openpgp" : "unknown"); TRETURN -1; } ctx->setKeyListMode (GpgME::Local); GpgME::Error err; if (!sigFpr.empty()) { m_signer_key = ctx->key (sigFpr.c_str (), err, true); if (err || m_signer_key.isNull ()) { log_error ("%s:%s: failed to lookup key for '%s' with protocol '%s'", SRCNAME, __func__, anonstr (sigFpr.c_str ()), m_proto == GpgME::CMS ? "smime" : m_proto == GpgME::OpenPGP ? "openpgp" : "unknown"); TRETURN -1; } // reset context ctx = std::shared_ptr (GpgME::Context::createForProtocol (m_proto)); ctx->setKeyListMode (GpgME::Local); } if (!recpFprs.size()) { TRETURN 0; } // Convert recipient fingerprints char **cRecps = vector_to_cArray (recpFprs); err = ctx->startKeyListing (const_cast (cRecps)); if (err) { log_error ("%s:%s: failed to start recipient keylisting", SRCNAME, __func__); release_cArray (cRecps); TRETURN -1; } do { m_recipients.push_back(ctx->nextKey(err)); } while (!err); m_recipients.pop_back(); release_cArray (cRecps); TRETURN 0; } int CryptController::parse_output (GpgME::Data &resolverOutput) { TSTART; // Todo: Use Data::toString std::istringstream ss(resolverOutput.toString()); std::string line; std::string sigFpr; std::vector recpFprs; while (std::getline (ss, line)) { rtrim (line); if (line == "cancel") { log_debug ("%s:%s: resolver canceled", SRCNAME, __func__); TRETURN -2; } if (line == "unencrypted") { log_debug ("%s:%s: FIXME resolver wants unencrypted", SRCNAME, __func__); TRETURN -1; } std::istringstream lss (line); // First is sig or enc std::string what; std::string how; std::string fingerprint; std::getline (lss, what, ':'); std::getline (lss, how, ':'); std::getline (lss, fingerprint, ':'); if (m_proto == GpgME::UnknownProtocol) { m_proto = (how == "smime") ? GpgME::CMS : GpgME::OpenPGP; } if (what == "sig") { if (!sigFpr.empty ()) { log_error ("%s:%s: multiple signing keys not supported", SRCNAME, __func__); } sigFpr = fingerprint; continue; } if (what == "enc") { recpFprs.push_back (fingerprint); } } if (m_sign && sigFpr.empty()) { log_error ("%s:%s: Sign requested but no signing fingerprint - sending unsigned", SRCNAME, __func__); m_sign = false; } if (m_encrypt && !recpFprs.size()) { log_error ("%s:%s: Encrypt requested but no recipient fingerprints", SRCNAME, __func__); gpgol_message_box (m_mail->getWindow (), utf8_gettext ("No recipients for encryption selected."), _("GpgOL"), MB_OK); TRETURN -2; } TRETURN lookup_fingerprints (sigFpr, recpFprs); } static bool resolve_through_protocol (const GpgME::Protocol &proto, bool sign, bool encrypt, const std::string &sender, const std::vector &recps, std::vector &r_keys, GpgME::Key &r_sig) { TSTART; bool sig_ok = true; bool enc_ok = true; const auto cache = KeyCache::instance(); if (encrypt) { r_keys = cache->getEncryptionKeys(recps, proto); enc_ok = !r_keys.empty(); } if (sign && enc_ok) { r_sig = cache->getSigningKey (sender.c_str (), proto); sig_ok = !r_sig.isNull(); } TRETURN sig_ok && enc_ok; } int CryptController::resolve_keys_cached() { TSTART; // Prepare variables const auto cached_sender = m_mail->getSender (); auto recps = m_recipient_addrs; if (m_encrypt) { recps.push_back (cached_sender); } bool resolved = false; if (opt.enable_smime && opt.prefer_smime) { resolved = resolve_through_protocol (GpgME::CMS, m_sign, m_encrypt, cached_sender, recps, m_recipients, m_signer_key); if (resolved) { log_debug ("%s:%s: Resolved with CMS due to preference.", SRCNAME, __func__); m_proto = GpgME::CMS; } } if (!resolved) { resolved = resolve_through_protocol (GpgME::OpenPGP, m_sign, m_encrypt, cached_sender, recps, m_recipients, m_signer_key); if (resolved) { log_debug ("%s:%s: Resolved with OpenPGP.", SRCNAME, __func__); m_proto = GpgME::OpenPGP; } } if (!resolved && (opt.enable_smime && !opt.prefer_smime)) { resolved = resolve_through_protocol (GpgME::CMS, m_sign, m_encrypt, cached_sender, recps, m_recipients, m_signer_key); if (resolved) { log_debug ("%s:%s: Resolved with CMS as fallback.", SRCNAME, __func__); m_proto = GpgME::CMS; } } if (!resolved) { log_debug ("%s:%s: Failed to resolve through cache", SRCNAME, __func__); m_recipients.clear(); m_signer_key = GpgME::Key(); m_proto = GpgME::UnknownProtocol; TRETURN 1; } if (!m_recipients.empty()) { log_debug ("%s:%s: Encrypting with protocol %s to:", SRCNAME, __func__, to_cstr (m_proto)); } for (const auto &key: m_recipients) { log_debug ("%s", anonstr (key.primaryFingerprint ())); } if (!m_signer_key.isNull()) { log_debug ("%s:%s: Signing key: %s:%s", SRCNAME, __func__, anonstr (m_signer_key.primaryFingerprint ()), to_cstr (m_signer_key.protocol())); } TRETURN 0; } int CryptController::resolve_keys () { TSTART; m_recipients.clear(); if (m_mail->isDraftEncrypt() && opt.draft_key) { const auto key = KeyCache::instance()->getByFpr (opt.draft_key); if (key.isNull()) { const char *buf = utf8_gettext ("Failed to encrypt draft.\n\n" "The configured encryption key for drafts " "could not be found.\n" "Please check your configuration or " "turn off draft encryption in the settings."); gpgol_message_box (get_active_hwnd (), buf, _("GpgOL"), MB_OK); TRETURN -1; } log_debug ("%s:%s: resolved draft encryption key protocol is: %s", SRCNAME, __func__, to_cstr (key.protocol())); m_proto = key.protocol (); m_recipients.push_back (key); TRETURN 0; } if (!m_recipient_addrs.size()) { /* Should not happen. But we add it for better bug reports. */ const char *bugmsg = utf8_gettext ("Operation failed.\n\n" "This is usually caused by a bug in GpgOL or an error in your setup.\n" "Please see https://www.gpg4win.org/reporting-bugs.html " "or ask your Administrator for support."); char *buf; gpgrt_asprintf (&buf, "Failed to resolve recipients.\n\n%s\n", bugmsg); memdbg_alloc (buf); gpgol_message_box (get_active_hwnd (), buf, _("GpgOL"), MB_OK); xfree(buf); TRETURN -1; } if (opt.autoresolve && !resolve_keys_cached ()) { log_debug ("%s:%s: resolved keys through the cache", SRCNAME, __func__); start_crypto_overlay(); TRETURN 0; } std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; TRETURN -1; } const auto resolver = std::string (gpg4win_dir) + "\\bin\\resolver.exe"; args.push_back (resolver); log_debug ("%s:%s: resolving keys with '%s'", SRCNAME, __func__, resolver.c_str ()); // We want debug output as OutputDebugString args.push_back (std::string ("--debug")); // Yes passing it as int is ok. auto wnd = m_mail->getWindow (); if (wnd) { // Pass the handle of the active window for raise / overlay. args.push_back (std::string ("--hwnd")); args.push_back (std::to_string ((int) (intptr_t) wnd)); } // Set the overlay caption args.push_back (std::string ("--overlayText")); if (m_encrypt) { args.push_back (std::string (_("Resolving recipients..."))); } else if (m_sign) { args.push_back (std::string (_("Resolving signers..."))); } if (!opt.enable_smime) { args.push_back (std::string ("--protocol")); args.push_back (std::string ("pgp")); } if (m_sign) { args.push_back (std::string ("--sign")); } const auto cached_sender = m_mail->getSender (); if (cached_sender.empty()) { log_error ("%s:%s: resolve keys without sender.", SRCNAME, __func__); } else { args.push_back (std::string ("--sender")); args.push_back (cached_sender); } if (!opt.autoresolve) { args.push_back (std::string ("--alwaysShow")); } if (opt.prefer_smime) { args.push_back (std::string ("--preferred-protocol")); args.push_back (std::string ("cms")); } args.push_back (std::string ("--lang")); args.push_back (std::string (gettext_localename ())); + bool has_smime_override = false; + if (m_encrypt) { args.push_back (std::string ("--encrypt")); // Get the recipients that are cached from OOM for (const auto &addr: m_recipient_addrs) { const auto mbox = GpgME::UserID::addrSpecFromString (addr.c_str()); - const auto overrides = KeyCache::instance ()->getOverrides (mbox); + auto overrides = + KeyCache::instance ()->getOverrides (mbox, GpgME::OpenPGP); + const auto cms_overrides = + KeyCache::instance ()->getOverrides (mbox, GpgME::CMS); + + overrides.insert(overrides.end(), cms_overrides.begin(), + cms_overrides.end()); + if (overrides.size()) { std::string overrideStr = mbox + ":"; for (const auto &key: overrides) { if (key.isNull()) { TRACEPOINT; continue; } + has_smime_override |= key.protocol() == GpgME::CMS; overrideStr += key.primaryFingerprint(); overrideStr += ","; } overrideStr.erase(overrideStr.size() - 1, 1); args.push_back (std::string ("-o")); args.push_back (overrideStr); } args.push_back (mbox); } } + if (!opt.prefer_smime && has_smime_override) + { + /* Prefer S/MIME if there was an S/MIME override */ + args.push_back (std::string ("--preferred-protocol")); + args.push_back (std::string ("cms")); + } + // Args are prepared. Spawn the resolver. auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen TRACEPOINT; TRETURN -1; } // Convert our collected vector to c strings // It's a bit overhead but should be quick for such small // data. char **cargs = vector_to_cArray (args); log_data ("%s:%s: Spawn args:", SRCNAME, __func__); for (size_t i = 0; cargs && cargs[i]; i++) { log_data (SIZE_T_FORMAT ": '%s'", i, cargs[i]); } GpgME::Data mystdin (GpgME::Data::null), mystdout, mystderr; GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); // Somehow Qt messes up which window to bring back to front. // So we do it manually. bring_to_front (wnd); // We need to create an overlay while encrypting as pinentry can take a while start_crypto_overlay(); log_data ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_data ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); release_cArray (cargs); if (err) { log_debug ("%s:%s: Resolver spawn finished Err code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); } int ret = parse_output (mystdout); if (ret == -1) { log_debug ("%s:%s: Failed to parse / resolve keys.", SRCNAME, __func__); log_data ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_data ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); TRETURN -1; } TRETURN ret; } int CryptController::do_crypto (GpgME::Error &err, std::string &r_diag) { TSTART; log_debug ("%s:%s", SRCNAME, __func__); if (m_mail->isDraftEncrypt ()) { log_debug ("%s:%s Disabling sign because of draft encrypt", SRCNAME, __func__); m_sign = false; } /* Start a WKS check if necessary. */ WKSHelper::instance()->start_check (m_mail->getSender ()); int ret = resolve_keys (); if (ret == -1) { //error log_debug ("%s:%s: Failure to resolve keys.", SRCNAME, __func__); TRETURN -1; } if (ret == -2) { // Cancel TRETURN -2; } bool do_inline = m_mail->getDoPGPInline (); if (m_proto == GpgME::CMS && do_inline) { log_debug ("%s:%s: Inline for S/MIME not supported. Switching to mime.", SRCNAME, __func__); do_inline = false; m_mail->setDoPGPInline (false); m_bodyInput = GpgME::Data(GpgME::Data::null); } auto ctx = GpgME::Context::create(m_proto); if (!ctx) { log_error ("%s:%s: Failure to create context.", SRCNAME, __func__); gpgol_message_box (m_mail->getWindow (), "Failure to create context.", utf8_gettext ("GpgOL"), MB_OK); TRETURN -1; } if (!m_signer_key.isNull()) { ctx->addSigningKey (m_signer_key); } ctx->setTextMode (m_proto == GpgME::OpenPGP); ctx->setArmor (m_proto == GpgME::OpenPGP); if (m_encrypt && m_sign && do_inline) { // Sign encrypt combined const auto result_pair = ctx->signAndEncrypt (m_recipients, do_inline ? m_bodyInput : m_input, m_output, GpgME::Context::AlwaysTrust); const auto err1 = result_pair.first.error(); const auto err2 = result_pair.second.error(); if (err1 || err2) { log_error ("%s:%s: Encrypt / Sign error %s %s.", SRCNAME, __func__, result_pair.first.error().asString(), result_pair.second.error().asString()); err = err1 ? err1 : err2; GpgME::Data log; const auto err3 = ctx->getAuditLog (log, GpgME::Context::DiagnosticAuditLog); if (!err3) { r_diag = log.toString(); } TRETURN -1; } if (err1.isCanceled() || err2.isCanceled()) { err = err1.isCanceled() ? err1 : err2; log_debug ("%s:%s: User cancled", SRCNAME, __func__); TRETURN -2; } } else if (m_encrypt && m_sign) { // First sign then encrypt const auto sigResult = ctx->sign (m_input, m_output, GpgME::Detached); err = sigResult.error(); if (err) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, sigResult.error().asString()); GpgME::Data log; const auto err3 = ctx->getAuditLog (log, GpgME::Context::DiagnosticAuditLog); if (!err3) { r_diag = log.toString(); } TRETURN -1; } if (err.isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); TRETURN -2; } parse_micalg (sigResult); // We now have plaintext in m_input // The detached signature in m_output // Set up the sink object to construct the multipart/signed GpgME::Data multipart; struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &multipart; sink->writefnc = sink_data_write; if (create_sign_attach (sink, m_proto == GpgME::CMS ? PROTOCOL_SMIME : PROTOCOL_OPENPGP, m_output, m_input, m_micalg.c_str ())) { TRACEPOINT; TRETURN -1; } // Now we have the multipart throw away the rest. m_output = GpgME::Data (); m_input = GpgME::Data (); multipart.seek (0, SEEK_SET); const auto encResult = ctx->encrypt (m_recipients, multipart, m_output, GpgME::Context::AlwaysTrust); err = encResult.error(); if (err) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, err.asString()); GpgME::Data log; const auto err3 = ctx->getAuditLog (log, GpgME::Context::DiagnosticAuditLog); if (!err3) { r_diag = log.toString(); } TRETURN -1; } if (err.isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); TRETURN -2; } // Now we have encrypted output just treat it like encrypted. } else if (m_encrypt) { const auto result = ctx->encrypt (m_recipients, do_inline ? m_bodyInput : m_input, m_output, GpgME::Context::AlwaysTrust); err = result.error(); if (err) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, err.asString()); GpgME::Data log; const auto err3 = ctx->getAuditLog (log, GpgME::Context::DiagnosticAuditLog); if (!err3) { r_diag = log.toString(); } TRETURN -1; } if (err.isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); TRETURN -2; } } else if (m_sign) { const auto result = ctx->sign (do_inline ? m_bodyInput : m_input, m_output, do_inline ? GpgME::Clearsigned : GpgME::Detached); err = result.error(); if (err) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, err.asString()); GpgME::Data log; const auto err3 = ctx->getAuditLog (log, GpgME::Context::DiagnosticAuditLog); if (!err3) { r_diag = log.toString(); } TRETURN -1; } if (err.isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); TRETURN -2; } parse_micalg (result); } else { // ??? log_error ("%s:%s: unreachable code reached.", SRCNAME, __func__); } log_debug ("%s:%s: Crypto done sucessfuly.", SRCNAME, __func__); m_crypto_success = true; TRETURN 0; } static int write_data (sink_t sink, GpgME::Data &data) { TSTART; if (!sink || !sink->writefnc) { TRETURN -1; } char buf[4096]; size_t nread; data.seek (0, SEEK_SET); while ((nread = data.read (buf, 4096)) > 0) { sink->writefnc (sink, buf, nread); } TRETURN 0; } int create_sign_attach (sink_t sink, protocol_t protocol, GpgME::Data &signature, GpgME::Data &signedData, const char *micalg) { TSTART; char boundary[BOUNDARYSIZE+1]; char top_header[BOUNDARYSIZE+200]; int rc = 0; /* Write the top header. */ generate_boundary (boundary); create_top_signing_header (top_header, sizeof top_header, protocol, 1, boundary, micalg); if ((rc = write_string (sink, top_header))) { TRACEPOINT; TRETURN rc; } /* Write the boundary so that it is not included in the hashing. */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; TRETURN rc; } /* Write the signed mime structure */ if ((rc = write_data (sink, signedData))) { TRACEPOINT; TRETURN rc; } /* Write the signature attachment */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; TRETURN rc; } if (protocol == PROTOCOL_OPENPGP) { rc = write_string (sink, "Content-Type: application/pgp-signature;\r\n" "\tname=\"" OPENPGP_SIG_NAME "\"\r\n" "Content-Transfer-Encoding: 7Bit\r\n"); } else { rc = write_string (sink, "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-signature\r\n" "Content-Disposition: inline;\r\n" "\tfilename=\"" SMIME_SIG_NAME "\"\r\n"); /* rc = write_string (sink, */ /* "Content-Type: application/x-pkcs7-signature\r\n" */ /* "\tname=\"smime.p7s\"\r\n" */ /* "Content-Transfer-Encoding: base64\r\n" */ /* "Content-Disposition: attachment;\r\n" */ /* "\tfilename=\"smime.p7s\"\r\n"); */ } if (rc) { TRACEPOINT; TRETURN rc; } if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; TRETURN rc; } // Write the signature data if (protocol == PROTOCOL_SMIME) { const std::string sigStr = signature.toString(); if ((rc = write_b64 (sink, (const void *) sigStr.c_str (), sigStr.size()))) { TRACEPOINT; TRETURN rc; } } else if ((rc = write_data (sink, signature))) { TRACEPOINT; TRETURN rc; } // Add an extra linefeed with should not harm. if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; TRETURN rc; } /* Write the final boundary. */ if ((rc = write_boundary (sink, boundary, 1))) { TRACEPOINT; TRETURN rc; } TRETURN rc; } static int create_encrypt_attach (sink_t sink, protocol_t protocol, GpgME::Data &encryptedData, int exchange_major_version) { TSTART; char boundary[BOUNDARYSIZE+1]; int rc = create_top_encryption_header (sink, protocol, boundary, false, exchange_major_version); // From here on use goto failure pattern. if (rc) { log_error ("%s:%s: Failed to create top header.", SRCNAME, __func__); TRETURN rc; } if (protocol == PROTOCOL_OPENPGP || exchange_major_version >= 15) { // With exchange 2016 we have to construct S/MIME // differently and write the raw data here. rc = write_data (sink, encryptedData); } else { const auto encStr = encryptedData.toString(); rc = write_b64 (sink, encStr.c_str(), encStr.size()); } if (rc) { log_error ("%s:%s: Failed to create top header.", SRCNAME, __func__); TRETURN rc; } /* Write the final boundary (for OpenPGP) and finish the attachment. */ if (*boundary && (rc = write_boundary (sink, boundary, 1))) { log_error ("%s:%s: Failed to write boundary.", SRCNAME, __func__); } TRETURN rc; } int CryptController::update_mail_mapi () { TSTART; log_debug ("%s:%s", SRCNAME, __func__); LPMESSAGE message = get_oom_base_message (m_mail->item()); if (!message) { log_error ("%s:%s: Failed to obtain message.", SRCNAME, __func__); TRETURN -1; } if (m_mail->getDoPGPInline ()) { // Nothing to do for inline. log_debug ("%s:%s: Inline mail. Setting encoding.", SRCNAME, __func__); SPropValue prop; prop.ulPropTag = PR_INTERNET_CPID; prop.Value.l = 65001; if (HrSetOneProp (message, &prop)) { log_error ("%s:%s: Failed to set CPID mapiprop.", SRCNAME, __func__); } TRETURN 0; } mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0); /* When we forward e.g. a crypto mail we have sent the message has a MOSSTEMPL. We need to remove that. T4321 */ for (ULONG pos=0; att_table && !att_table[pos].end_of_table; pos++) { if (att_table[pos].attach_type == ATTACHTYPE_MOSSTEMPL) { log_debug ("%s:%s: Found existing moss attachment at " "pos %i removing it.", SRCNAME, __func__, att_table[pos].mapipos); if (message->DeleteAttach (att_table[pos].mapipos, 0, nullptr, 0) != S_OK) { log_error ("%s:%s: Failed to remove attachment.", SRCNAME, __func__); } } } // Set up the sink object for our MSOXSMIME attachment. struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &m_input; sink->writefnc = sink_data_write; // For S/MIME encrypted mails we have to use the application/pkcs7-mime // content type. Otherwise newer (2016) exchange servers will throw // an M2MCVT.StorageError.Exeption (See GnuPG-Bug-Id: T3853 ) // This means that the conversion / build of the mime structure also // happens differently. int exchange_major_version = get_ex_major_version_for_addr ( m_mail->getSender ().c_str ()); std::string overrideMimeTag; if (m_proto == GpgME::CMS && m_encrypt && exchange_major_version >= 15) { log_debug ("%s:%s: CMS Encrypt with Exchange %i activating alternative.", SRCNAME, __func__, exchange_major_version); overrideMimeTag = "application/pkcs7-mime"; } LPATTACH attach = create_mapi_attachment (message, sink, overrideMimeTag.empty() ? nullptr : overrideMimeTag.c_str()); if (!attach) { log_error ("%s:%s: Failed to create moss attach.", SRCNAME, __func__); gpgol_release (message); TRETURN -1; } protocol_t protocol = m_proto == GpgME::CMS ? PROTOCOL_SMIME : PROTOCOL_OPENPGP; int rc = 0; /* Do we have override MIME ? */ const auto overrideMime = m_mail->get_override_mime_data (); if (!overrideMime.empty()) { rc = write_string (sink, overrideMime.c_str ()); } else if (m_sign && m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version); } else if (m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version); } else if (m_sign) { rc = create_sign_attach (sink, protocol, m_output, m_input, m_micalg.c_str ()); } // Close our attachment if (!rc) { rc = close_mapi_attachment (&attach, sink); } // Set message class etc. if (!rc) { rc = finalize_message (message, att_table, protocol, m_encrypt ? 1 : 0, false, m_mail->isDraftEncrypt ()); } // only on error. if (rc) { cancel_mapi_attachment (&attach, sink); } // cleanup mapi_release_attach_table (att_table); gpgol_release (attach); gpgol_release (message); TRETURN rc; } std::string CryptController::get_inline_data () { TSTART; std::string ret; if (!m_mail->getDoPGPInline ()) { TRETURN ret; } m_output.seek (0, SEEK_SET); char buf[4096]; size_t nread; while ((nread = m_output.read (buf, 4096)) > 0) { ret += std::string (buf, nread); } TRETURN ret; } void CryptController::parse_micalg (const GpgME::SigningResult &result) { TSTART; if (result.isNull()) { TRACEPOINT; TRETURN; } const auto signature = result.createdSignature(0); if (signature.isNull()) { TRACEPOINT; TRETURN; } const char *hashAlg = signature.hashAlgorithmAsString (); if (!hashAlg) { TRACEPOINT; TRETURN; } if (m_proto == GpgME::OpenPGP) { m_micalg = std::string("pgp-") + hashAlg; } else { m_micalg = hashAlg; } std::transform(m_micalg.begin(), m_micalg.end(), m_micalg.begin(), ::tolower); log_debug ("%s:%s: micalg is: '%s'.", SRCNAME, __func__, m_micalg.c_str ()); TRETURN; } void CryptController::start_crypto_overlay () { TSTART; auto wid = m_mail->getWindow (); std::string text; if (m_encrypt) { text = _("Encrypting..."); } else if (m_sign) { text = _("Signing..."); } m_overlay = std::unique_ptr (new Overlay (wid, text)); TRETURN; } diff --git a/src/keycache.cpp b/src/keycache.cpp index 5b4fcb8..446c06f 100644 --- a/src/keycache.cpp +++ b/src/keycache.cpp @@ -1,1510 +1,1604 @@ /* @file keycache.cpp * @brief Internal keycache * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "keycache.h" #include "common.h" #include "cpphelp.h" #include "mail.h" #include #include #include #include #include #include #include #include #include GPGRT_LOCK_DEFINE (keycache_lock); GPGRT_LOCK_DEFINE (fpr_map_lock); GPGRT_LOCK_DEFINE (update_lock); GPGRT_LOCK_DEFINE (import_lock); static KeyCache* singleton = nullptr; /** At some point we need to set a limit. There seems to be no limit on how many recipients a mail can have in outlook. We would run out of resources or block. 50 Threads already seems a bit excessive but it should really cover most legit use cases. */ #define MAX_LOCATOR_THREADS 50 static int s_thread_cnt; namespace { class LocateArgs { public: LocateArgs (const std::string& mbox, Mail *mail = nullptr): m_mbox (mbox), m_mail (mail) { TSTART; s_thread_cnt++; Mail::lockDelete (); if (Mail::isValidPtr (m_mail)) { m_mail->incrementLocateCount (); } Mail::unlockDelete (); TRETURN; }; ~LocateArgs() { TSTART; s_thread_cnt--; Mail::lockDelete (); if (Mail::isValidPtr (m_mail)) { m_mail->decrementLocateCount (); } Mail::unlockDelete (); TRETURN; } std::string m_mbox; Mail *m_mail; }; } // namespace typedef std::pair update_arg_t; typedef std::pair, std::string> import_arg_t; +static std::vector +filter_chain (const std::vector &input) +{ + std::vector leaves; + + log_debug ("filter_chain: %i", input.size()); + std::remove_copy_if(input.begin(), input.end(), + std::back_inserter(leaves), + [input] (const auto &k) + { + /* Check if a key has this fingerprint in the + * chain ID. Meaning that there is any child of + * this certificate. In that case remove it. */ + for (const auto &c: input) + { + if (!c.chainID()) + { + continue; + } + if (!k.primaryFingerprint() || !c.primaryFingerprint()) + { + STRANGEPOINT; + continue; + } + if (!strcmp (c.chainID(), k.primaryFingerprint())) + { + log_debug ("%s:%s: Filtering %s as non leaf cert", + SRCNAME, __func__, k.primaryFingerprint ()); + return true; + } + } + return false; + }); + return leaves; +} + static DWORD WINAPI do_update (LPVOID arg) { TSTART; auto args = std::unique_ptr ((update_arg_t*) arg); log_debug ("%s:%s updating: \"%s\" with protocol %s", SRCNAME, __func__, anonstr (args->first.c_str ()), to_cstr (args->second)); auto ctx = std::unique_ptr (GpgME::Context::createForProtocol (args->second)); if (!ctx) { TRACEPOINT; KeyCache::instance ()->onUpdateJobDone (args->first.c_str(), GpgME::Key ()); TRETURN 0; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Signatures | GpgME::KeyListMode::Validate | GpgME::KeyListMode::WithTofu); GpgME::Error err; const auto newKey = ctx->key (args->first.c_str (), err, false); TRACEPOINT; if (newKey.isNull()) { log_debug ("%s:%s Failed to find key for %s", SRCNAME, __func__, anonstr (args->first.c_str ())); } if (err) { log_debug ("%s:%s Failed to find key for %s err: %s", SRCNAME, __func__, anonstr (args->first.c_str()), err.asString ()); } KeyCache::instance ()->onUpdateJobDone (args->first.c_str(), newKey); log_debug ("%s:%s Update job done", SRCNAME, __func__); TRETURN 0; } static DWORD WINAPI do_import (LPVOID arg) { TSTART; auto args = std::unique_ptr ((import_arg_t*) arg); const std::string mbox = args->first->m_mbox; log_debug ("%s:%s importing for: \"%s\" with data \n%s", SRCNAME, __func__, anonstr (mbox.c_str ()), anonstr (args->second.c_str ())); - auto ctx = std::unique_ptr (GpgME::Context::createForProtocol - (GpgME::OpenPGP)); + + // We want to avoid unneccessary copies. The c_str will be valid + // until args goes out of scope. + const char *keyStr = args->second.c_str (); + GpgME::Data data (keyStr, strlen (keyStr), /* copy */ false); + + GpgME::Protocol proto = GpgME::OpenPGP; + auto type = data.type(); + if (type == GpgME::Data::X509Cert) + { + proto = GpgME::CMS; + } + data.rewind (); + + auto ctx = GpgME::Context::create(proto); if (!ctx) { TRACEPOINT; TRETURN 0; } - // We want to avoid unneccessary copies. The c_str will be valid - // until args goes out of scope. - const char *keyStr = args->second.c_str (); - GpgME::Data data (keyStr, strlen (keyStr), /* copy */ false); - if (data.type () != GpgME::Data::PGPKey) + if (type != GpgME::Data::PGPKey && type != GpgME::Data::X509Cert) { - log_debug ("%s:%s Data for: %s is not a PGP Key", + log_debug ("%s:%s Data for: %s is not a PGP Key or Cert ", SRCNAME, __func__, anonstr (mbox.c_str ())); TRETURN 0; } data.rewind (); const auto result = ctx->importKeys (data); std::vector fingerprints; for (const auto import: result.imports()) { if (import.error()) { log_debug ("%s:%s Error importing: %s", SRCNAME, __func__, import.error().asString()); continue; } const char *fpr = import.fingerprint (); if (!fpr) { TRACEPOINT; continue; } update_arg_t * update_args = new update_arg_t; update_args->first = std::string (fpr); - update_args->second = GpgME::OpenPGP; + update_args->second = proto; // We do it blocking to be sure that when all imports // are done they are also part of the keycache. do_update ((LPVOID) update_args); - fingerprints.push_back (fpr); + if (std::find(fingerprints.begin(), fingerprints.end(), fpr) == + fingerprints.end()) + { + fingerprints.push_back (fpr); + } log_debug ("%s:%s Imported: %s from addressbook.", SRCNAME, __func__, anonstr (fpr)); } - KeyCache::instance ()->onAddrBookImportJobDone (mbox, fingerprints); + KeyCache::instance ()->onAddrBookImportJobDone (mbox, + fingerprints, + proto); log_debug ("%s:%s Import job done for: %s", SRCNAME, __func__, anonstr (mbox.c_str ())); TRETURN 0; } static void do_populate_protocol (GpgME::Protocol proto, bool secret) { log_debug ("%s:%s: Starting keylisting for proto %s", SRCNAME, __func__, to_cstr (proto)); auto ctx = GpgME::Context::create (proto); if (!ctx) { /* Maybe PGP broken and not S/MIME */ log_error ("%s:%s: broken installation no ctx.", SRCNAME, __func__); TRETURN; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); ctx->setOffline (true); GpgME::Error err; if ((err = ctx->startKeyListing ((const char*)nullptr, secret))) { log_error ("%s:%s: Failed to start keylisting err: %i: %s", SRCNAME, __func__, err.code (), err.asString()); TRETURN; } while (!err) { const auto key = ctx->nextKey(err); if (err || key.isNull()) { TRACEPOINT; break; } KeyCache::instance()->onUpdateJobDone (key.primaryFingerprint(), key); } TRETURN; } static DWORD WINAPI do_populate (LPVOID) { TSTART; log_debug ("%s:%s: Populating keycache", SRCNAME, __func__); do_populate_protocol (GpgME::OpenPGP, false); do_populate_protocol (GpgME::OpenPGP, true); if (opt.enable_smime) { do_populate_protocol (GpgME::CMS, false); do_populate_protocol (GpgME::CMS, true); } log_debug ("%s:%s: Keycache populated", SRCNAME, __func__); TRETURN 0; } class KeyCache::Private { public: Private() { } void setPgpKey(const std::string &mbox, const GpgME::Key &key) { TSTART; gpgol_lock (&keycache_lock); auto it = m_pgp_key_map.find (mbox); if (it == m_pgp_key_map.end ()) { m_pgp_key_map.insert (std::pair (mbox, key)); } else { it->second = key; } insertOrUpdateInFprMap (key); gpgol_unlock (&keycache_lock); TRETURN; } void setSmimeKey(const std::string &mbox, const GpgME::Key &key) { TSTART; gpgol_lock (&keycache_lock); auto it = m_smime_key_map.find (mbox); if (it == m_smime_key_map.end ()) { m_smime_key_map.insert (std::pair (mbox, key)); } else { it->second = key; } insertOrUpdateInFprMap (key); gpgol_unlock (&keycache_lock); TRETURN; } void setPgpKeySecret(const std::string &mbox, const GpgME::Key &key, bool insert = true) { TSTART; gpgol_lock (&keycache_lock); auto it = m_pgp_skey_map.find (mbox); if (it == m_pgp_skey_map.end ()) { m_pgp_skey_map.insert (std::pair (mbox, key)); } else { it->second = key; } if (insert) { insertOrUpdateInFprMap (key); } gpgol_unlock (&keycache_lock); TRETURN; } void setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key, bool insert = true) { TSTART; gpgol_lock (&keycache_lock); auto it = m_smime_skey_map.find (mbox); if (it == m_smime_skey_map.end ()) { m_smime_skey_map.insert (std::pair (mbox, key)); } else { it->second = key; } if (insert) { insertOrUpdateInFprMap (key); } gpgol_unlock (&keycache_lock); TRETURN; } - std::vector getPGPOverrides (const char *addr) + std::vector getOverrides (const char *addr, + GpgME::Protocol proto = GpgME::OpenPGP) { TSTART; std::vector ret; if (!addr) { TRETURN ret; } auto mbox = GpgME::UserID::addrSpecFromString (addr); - gpgol_lock (&keycache_lock); - const auto it = m_addr_book_overrides.find (mbox); - if (it == m_addr_book_overrides.end ()) + gpgol_lock (&import_lock); + const auto job_set = (proto == GpgME::OpenPGP ? + &m_pgp_import_jobs : &m_cms_import_jobs); + int i = 0; + while (job_set->find (mbox) != job_set->end ()) + { + i++; + if (i % 100 == 0) + { + log_debug ("%s:%s Waiting on import for \"%s\"", + SRCNAME, __func__, anonstr (addr)); + } + gpgol_unlock (&import_lock); + Sleep (10); + gpgol_lock (&import_lock); + if (i == 1000) + { + /* Just to be on the save side */ + log_error ("%s:%s Waiting on import for \"%s\" " + "failed! Bug!", + SRCNAME, __func__, anonstr (addr)); + break; + } + } + gpgol_unlock (&import_lock); + + auto override_map = (proto == GpgME::OpenPGP ? + &m_pgp_overrides : &m_cms_overrides); + const auto it = override_map->find (mbox); + if (it == override_map->end ()) { gpgol_unlock (&keycache_lock); TRETURN ret; } for (const auto fpr: it->second) { const auto key = getByFpr (fpr.c_str (), false); if (key.isNull()) { log_debug ("%s:%s: No key for %s in the cache?!", SRCNAME, __func__, anonstr (fpr.c_str())); continue; } ret.push_back (key); } - + if (proto == GpgME::CMS) { + /* Remove root and intermediate ca's */ + ret = filter_chain (ret); + } gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getKey (const char *addr, GpgME::Protocol proto) { TSTART; if (!addr) { TRETURN GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgol_lock (&keycache_lock); const auto it = m_pgp_key_map.find (mbox); if (it == m_pgp_key_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } gpgol_lock (&keycache_lock); const auto it = m_smime_key_map.find (mbox); if (it == m_smime_key_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getSKey (const char *addr, GpgME::Protocol proto) { TSTART; if (!addr) { TRETURN GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgol_lock (&keycache_lock); const auto it = m_pgp_skey_map.find (mbox); if (it == m_pgp_skey_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } gpgol_lock (&keycache_lock); const auto it = m_smime_skey_map.find (mbox); if (it == m_smime_skey_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto) { TSTART; const auto key = getSKey (addr, proto); if (key.isNull()) { log_debug ("%s:%s: secret key for %s is null", SRCNAME, __func__, anonstr (addr)); TRETURN key; } if (!key.canReallySign()) { log_debug ("%s:%s: Discarding key for %s because it can't sign", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } if (!key.hasSecret()) { log_debug ("%s:%s: Discarding key for %s because it has no secret", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } if (in_de_vs_mode () && !key.isDeVs()) { log_debug ("%s:%s: signing key for %s is not deVS", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } TRETURN key; } std::vector getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) { TSTART; std::vector ret; if (recipients.empty ()) { TRACEPOINT; TRETURN ret; } for (const auto &recip: recipients) { - if (proto == GpgME::OpenPGP) - { - const auto overrides = getPGPOverrides (recip.c_str ()); + const auto overrides = getOverrides (recip.c_str (), proto); - if (!overrides.empty()) - { - ret.insert (ret.end (), overrides.begin (), overrides.end ()); - log_debug ("%s:%s: Using overides for %s", - SRCNAME, __func__, anonstr (recip.c_str ())); - continue; - } + if (!overrides.empty()) + { + const auto filtered = (proto == GpgME::CMS ? filter_chain(overrides) + : overrides); + ret.insert (ret.end (), filtered.begin (), filtered.end ()); + log_debug ("%s:%s: Using overrides for %s", + SRCNAME, __func__, anonstr (recip.c_str ())); + continue; } const auto key = getKey (recip.c_str (), proto); if (key.isNull()) { log_debug ("%s:%s: No key for %s. no internal encryption", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } if (!key.canEncrypt() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_data ("%s:%s: Invalid key for %s. no internal encryption", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } if (in_de_vs_mode () && !key.isDeVs ()) { log_data ("%s:%s: key for %s is not deVS", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } bool validEnough = false; /* Here we do the check if the key is valid for this recipient */ const auto addrSpec = GpgME::UserID::addrSpecFromString (recip.c_str ()); for (const auto &uid: key.userIDs ()) { if (addrSpec != uid.addrSpec()) { // Ignore unmatching addr specs continue; } if (uid.validity() >= GpgME::UserID::Marginal || uid.origin() == GpgME::Key::OriginWKD) { validEnough = true; break; } if (opt.auto_unstrusted && uid.validity() == GpgME::UserID::Unknown) { log_debug ("%s:%s: Passing unknown trust key for %s because of option", SRCNAME, __func__, anonstr (recip.c_str ())); validEnough = true; break; } } if (!validEnough) { log_debug ("%s:%s: UID for %s does not have at least marginal trust", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } // Accepting key ret.push_back (key); } TRETURN ret; } void insertOrUpdateInFprMap (const GpgME::Key &key) { TSTART; if (key.isNull() || !key.primaryFingerprint()) { TRACEPOINT; TRETURN; } gpgol_lock (&fpr_map_lock); /* First ensure that we have the subkeys mapped to the primary fpr */ const char *primaryFpr = key.primaryFingerprint (); #if 0 { std::stringstream ss; ss << key; log_debug ("%s:%s: Inserting key\n%s", SRCNAME, __func__, ss.str().c_str ()); } #endif for (const auto &sub: key.subkeys()) { const char *subFpr = sub.fingerprint(); auto it = m_sub_fpr_map.find (subFpr); if (it == m_sub_fpr_map.end ()) { m_sub_fpr_map.insert (std::make_pair( std::string (subFpr), std::string (primaryFpr))); } } auto it = m_fpr_map.find (primaryFpr); if (it == m_fpr_map.end ()) { m_fpr_map.insert (std::make_pair (primaryFpr, key)); gpgol_unlock (&fpr_map_lock); TRETURN; } for (const auto &uid: key.userIDs()) { if (key.isBad() || uid.isBad()) { continue; } /* Update ultimate keys map */ if (uid.validity() == GpgME::UserID::Validity::Ultimate && uid.id()) { const char *fpr = key.primaryFingerprint(); if (!fpr) { STRANGEPOINT; continue; } TRACEPOINT; m_ultimate_keys.erase (std::remove_if (m_ultimate_keys.begin(), m_ultimate_keys.end(), [fpr] (const GpgME::Key &ult) { return ult.primaryFingerprint() && !strcmp (fpr, ult.primaryFingerprint()); }), m_ultimate_keys.end()); TRACEPOINT; m_ultimate_keys.push_back (key); } /* Update skey maps */ if (key.hasSecret ()) { if (key.protocol () == GpgME::OpenPGP) { setPgpKeySecret (uid.addrSpec(), key, false); } else if (key.protocol () == GpgME::CMS) { setSmimeKeySecret (uid.addrSpec(), key, false); } else { STRANGEPOINT; } } } if (it->second.hasSecret () && !key.hasSecret()) { log_debug ("%s:%s Lost secret info on update. Merging.", SRCNAME, __func__); auto merged = key; merged.mergeWith (it->second); it->second = merged; } else { it->second = key; } gpgol_unlock (&fpr_map_lock); TRETURN; } GpgME::Key getFromMap (const char *fpr) const { TSTART; if (!fpr) { TRACEPOINT; TRETURN GpgME::Key(); } gpgol_lock (&fpr_map_lock); std::string primaryFpr; const auto it = m_sub_fpr_map.find (fpr); if (it != m_sub_fpr_map.end ()) { log_debug ("%s:%s using \"%s\" for \"%s\"", SRCNAME, __func__, anonstr (it->second.c_str()), anonstr (fpr)); primaryFpr = it->second; } else { primaryFpr = fpr; } const auto keyIt = m_fpr_map.find (primaryFpr); if (keyIt != m_fpr_map.end ()) { const auto ret = keyIt->second; gpgol_unlock (&fpr_map_lock); TRETURN ret; } gpgol_unlock (&fpr_map_lock); TRETURN GpgME::Key(); } GpgME::Key getByFpr (const char *fpr, bool block) const { TSTART; if (!fpr) { TRACEPOINT; TRETURN GpgME::Key (); } TRACEPOINT; const auto ret = getFromMap (fpr); if (ret.isNull()) { // If the key was not found we need to check if there is // an update running. if (block) { const std::string sFpr (fpr); int i = 0; gpgol_lock (&update_lock); while (m_update_jobs.find(sFpr) != m_update_jobs.end ()) { i++; if (i % 100 == 0) { log_debug ("%s:%s Waiting on update for \"%s\"", SRCNAME, __func__, anonstr (fpr)); } gpgol_unlock (&update_lock); Sleep (10); gpgol_lock (&update_lock); if (i == 3000) { /* Just to be on the save side */ log_error ("%s:%s Waiting on update for \"%s\" " "failed! Bug!", SRCNAME, __func__, anonstr (fpr)); break; } } gpgol_unlock (&update_lock); TRACEPOINT; const auto ret2 = getFromMap (fpr); if (ret2.isNull ()) { log_debug ("%s:%s Cache miss after blocking check %s.", SRCNAME, __func__, anonstr (fpr)); } else { log_debug ("%s:%s Cache hit after wait for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN ret2; } } log_debug ("%s:%s Cache miss for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN GpgME::Key(); } log_debug ("%s:%s Cache hit for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN ret; } void update (const char *fpr, GpgME::Protocol proto) { TSTART; if (!fpr) { TRETURN; } const std::string sFpr (fpr); gpgol_lock (&update_lock); if (m_update_jobs.find(sFpr) != m_update_jobs.end ()) { log_debug ("%s:%s Update for \"%s\" already in progress.", SRCNAME, __func__, anonstr (fpr)); gpgol_unlock (&update_lock); } m_update_jobs.insert (sFpr); gpgol_unlock (&update_lock); update_arg_t * args = new update_arg_t; args->first = sFpr; args->second = proto; CloseHandle (CreateThread (NULL, 0, do_update, (LPVOID) args, 0, NULL)); TRETURN; } void onUpdateJobDone (const char *fpr, const GpgME::Key &key) { TSTART; if (!fpr) { TRETURN; } TRACEPOINT; insertOrUpdateInFprMap (key); gpgol_lock (&update_lock); const auto it = m_update_jobs.find(fpr); if (it == m_update_jobs.end()) { gpgol_unlock (&update_lock); TRETURN; } m_update_jobs.erase (it); gpgol_unlock (&update_lock); TRACEPOINT; TRETURN; } void importFromAddrBook (const std::string &mbox, const char *data, - Mail *mail) + Mail *mail, GpgME::Protocol proto) { TSTART; if (!data || mbox.empty() || !mail) { TRACEPOINT; TRETURN; } std::string sdata (data); trim (sdata); if (sdata.empty()) { TRETURN; } gpgol_lock (&import_lock); - if (m_import_jobs.find (mbox) != m_import_jobs.end ()) + auto job_set = (proto == GpgME::OpenPGP ? + &m_pgp_import_jobs : &m_cms_import_jobs); + if (job_set->find (mbox) != job_set->end ()) { - log_debug ("%s:%s import for \"%s\" already in progress.", - SRCNAME, __func__, anonstr (mbox.c_str ())); + log_debug ("%s:%s import for \"%s\" %s already in progress.", + SRCNAME, __func__, anonstr (mbox.c_str ()), + to_cstr (proto)); gpgol_unlock (&import_lock); } - m_import_jobs.insert (mbox); + job_set->insert (mbox); gpgol_unlock (&import_lock); import_arg_t * args = new import_arg_t; args->first = std::unique_ptr (new LocateArgs (mbox, mail)); args->second = sdata; CloseHandle (CreateThread (NULL, 0, do_import, (LPVOID) args, 0, NULL)); TRETURN; } void onAddrBookImportJobDone (const std::string &mbox, - const std::vector &result_fprs) + const std::vector &result_fprs, + GpgME::Protocol proto) { TSTART; gpgol_lock (&keycache_lock); - auto it = m_addr_book_overrides.find (mbox); - if (it != m_addr_book_overrides.end ()) + auto override_map = (proto == GpgME::OpenPGP ? + &m_pgp_overrides : &m_cms_overrides); + auto job_set = (proto == GpgME::OpenPGP ? + &m_pgp_import_jobs : &m_cms_import_jobs); + + auto it = override_map->find (mbox); + if (it != override_map->end ()) { it->second = result_fprs; } else { - m_addr_book_overrides.insert ( - std::make_pair (mbox, result_fprs)); + override_map->insert (std::make_pair (mbox, result_fprs)); } gpgol_unlock (&keycache_lock); gpgol_lock (&import_lock); - const auto job_it = m_import_jobs.find(mbox); + const auto job_it = job_set->find(mbox); - if (job_it == m_import_jobs.end()) + if (job_it == job_set->end()) { - log_error ("%s:%s import for \"%s\" already finished.", - SRCNAME, __func__, anonstr (mbox.c_str ())); + log_error ("%s:%s import for \"%s\" %s already finished.", + SRCNAME, __func__, anonstr (mbox.c_str ()), + to_cstr (proto)); gpgol_unlock (&import_lock); TRETURN; } - m_import_jobs.erase (job_it); + job_set->erase (job_it); gpgol_unlock (&import_lock); TRETURN; } void populate () { TSTART; gpgrt_lock_lock (&keycache_lock); m_ultimate_keys.clear (); gpgrt_lock_unlock (&keycache_lock); CloseHandle (CreateThread (nullptr, 0, do_populate, nullptr, 0, nullptr)); TRETURN; } std::unordered_map m_pgp_key_map; std::unordered_map m_smime_key_map; std::unordered_map m_pgp_skey_map; std::unordered_map m_smime_skey_map; std::unordered_map m_fpr_map; std::unordered_map m_sub_fpr_map; std::unordered_map > - m_addr_book_overrides; + m_pgp_overrides; + std::unordered_map > + m_cms_overrides; std::vector m_ultimate_keys; std::set m_update_jobs; - std::set m_import_jobs; + std::set m_pgp_import_jobs; + std::set m_cms_import_jobs; }; KeyCache::KeyCache(): d(new Private) { } KeyCache * KeyCache::instance () { if (!singleton) { singleton = new KeyCache(); } return singleton; } GpgME::Key KeyCache::getSigningKey (const char *addr, GpgME::Protocol proto) const { return d->getSigningKey (addr, proto); } std::vector KeyCache::getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) const { return d->getEncryptionKeys (recipients, proto); } static GpgME::Key get_most_valid_key_simple (const std::vector &keys) { GpgME::Key candidate; for (const auto &key: keys) { if (key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_debug ("%s:%s: Skipping invalid S/MIME key", SRCNAME, __func__); continue; } if (candidate.isNull() || !candidate.numUserIDs()) { if (key.numUserIDs() && candidate.userID(0).validity() <= key.userID(0).validity()) { candidate = key; } } } return candidate; } static std::vector get_local_smime_keys (const std::string &addr) { TSTART; std::vector keys; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (GpgME::CMS)); if (!ctx) { TRACEPOINT; TRETURN keys; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate | GpgME::KeyListMode::Signatures); GpgME::Error e = ctx->startKeyListing (addr.c_str()); if (e) { TRACEPOINT; TRETURN keys; } GpgME::Error err; do { keys.push_back(ctx->nextKey(err)); } while (!err); keys.pop_back(); TRETURN keys; } static std::vector get_extern_smime_keys (const std::string &addr, bool import) { TSTART; std::vector keys; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (GpgME::CMS)); if (!ctx) { TRACEPOINT; TRETURN keys; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Extern); GpgME::Error e = ctx->startKeyListing (addr.c_str()); if (e) { TRACEPOINT; TRETURN keys; } GpgME::Error err; do { const auto key = ctx->nextKey (err); if (!err && !key.isNull()) { keys.push_back (key); log_debug ("%s:%s: Found extern S/MIME key for %s with fpr: %s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (key.primaryFingerprint())); } } while (!err); if (import && keys.size ()) { const GpgME::ImportResult res = ctx->importKeys(keys); log_debug ("%s:%s: Import result for %s: err: %s", SRCNAME, __func__, anonstr (addr.c_str()), res.error ().asString ()); } TRETURN keys; } static DWORD WINAPI do_locate (LPVOID arg) { TSTART; if (!arg) { TRETURN 0; } auto args = std::unique_ptr ((LocateArgs *) arg); const auto addr = args->m_mbox; log_debug ("%s:%s searching key for addr: \"%s\"", SRCNAME, __func__, anonstr (addr.c_str())); const auto k = GpgME::Key::locate (addr.c_str()); if (!k.isNull ()) { log_debug ("%s:%s found key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (k.primaryFingerprint())); KeyCache::instance ()->setPgpKey (addr, k); } log_debug ("%s:%s pgp locate done", SRCNAME, __func__); if (opt.enable_smime) { GpgME::Key candidate = get_most_valid_key_simple ( get_local_smime_keys (addr)); if (!candidate.isNull()) { log_debug ("%s:%s found SMIME key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (candidate.primaryFingerprint())); KeyCache::instance()->setSmimeKey (addr, candidate); TRETURN 0; } if (!opt.search_smime_servers || (!k.isNull() && !opt.prefer_smime)) { log_debug ("%s:%s Found no S/MIME key locally and external " "search is disabled.", SRCNAME, __func__); TRETURN 0; } /* Search for extern keys and import them */ const auto externs = get_extern_smime_keys (addr, true); if (externs.empty()) { TRETURN 0; } /* We found and imported external keys. We need to get them locally now to ensure that they are valid etc. */ candidate = get_most_valid_key_simple ( get_local_smime_keys (addr)); if (!candidate.isNull()) { log_debug ("%s:%s found ext. SMIME key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (candidate.primaryFingerprint())); KeyCache::instance()->setSmimeKey (addr, candidate); TRETURN 0; } else { log_debug ("%s:%s: Found no valid key in extern S/MIME certs", SRCNAME, __func__); } } TRETURN 0; } static void locate_secret (const char *addr, GpgME::Protocol proto) { TSTART; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (proto)); if (!ctx) { TRACEPOINT; TRETURN; } if (!addr) { TRACEPOINT; TRETURN; } const auto mbox = GpgME::UserID::addrSpecFromString (addr); if (mbox.empty()) { log_debug ("%s:%s: Empty mbox for addr %s", SRCNAME, __func__, anonstr (addr)); TRETURN; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); GpgME::Error e = ctx->startKeyListing (mbox.c_str(), true); if (e) { TRACEPOINT; TRETURN; } std::vector keys; GpgME::Error err; do { const auto key = ctx->nextKey(err); if (key.isNull()) { continue; } if (key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { if ((opt.enable_debug & DBG_DATA)) { std::stringstream ss; ss << key; log_data ("%s:%s: Skipping invalid secret key %s", SRCNAME, __func__, ss.str().c_str()); } continue; } if (proto == GpgME::OpenPGP) { log_debug ("%s:%s found pgp skey for addr: \"%s\":%s", SRCNAME, __func__, anonstr (mbox.c_str()), anonstr (key.primaryFingerprint())); KeyCache::instance()->setPgpKeySecret (mbox, key); TRETURN; } if (proto == GpgME::CMS) { log_debug ("%s:%s found cms skey for addr: \"%s\":%s", SRCNAME, __func__, anonstr (mbox.c_str ()), anonstr (key.primaryFingerprint())); KeyCache::instance()->setSmimeKeySecret (mbox, key); TRETURN; } } while (!err); TRETURN; } static DWORD WINAPI do_locate_secret (LPVOID arg) { TSTART; auto args = std::unique_ptr ((LocateArgs *) arg); log_debug ("%s:%s searching secret key for addr: \"%s\"", SRCNAME, __func__, anonstr (args->m_mbox.c_str ())); locate_secret (args->m_mbox.c_str(), GpgME::OpenPGP); if (opt.enable_smime) { locate_secret (args->m_mbox.c_str(), GpgME::CMS); } log_debug ("%s:%s locator sthread thread done", SRCNAME, __func__); TRETURN 0; } void KeyCache::startLocate (const std::vector &addrs, Mail *mail) const { for (const auto &addr: addrs) { startLocate (addr.c_str(), mail); } } void KeyCache::startLocate (const char *addr, Mail *mail) const { TSTART; if (!addr) { TRACEPOINT; TRETURN; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { TRETURN; } gpgol_lock (&keycache_lock); if (d->m_pgp_key_map.find (recp) == d->m_pgp_key_map.end ()) { // It's enough to look at the PGP Key map. We marked // searched keys there. d->m_pgp_key_map.insert (std::pair (recp, GpgME::Key())); log_debug ("%s:%s Creating a locator thread", SRCNAME, __func__); const auto args = new LocateArgs(recp, mail); HANDLE thread = CreateThread (NULL, 0, do_locate, args, 0, NULL); CloseHandle (thread); } gpgol_unlock (&keycache_lock); TRETURN; } void KeyCache::startLocateSecret (const char *addr, Mail *mail) const { TSTART; if (!addr) { TRACEPOINT; TRETURN; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { TRETURN; } gpgol_lock (&keycache_lock); if (d->m_pgp_skey_map.find (recp) == d->m_pgp_skey_map.end ()) { // It's enough to look at the PGP Key map. We marked // searched keys there. d->m_pgp_skey_map.insert (std::pair (recp, GpgME::Key())); log_debug ("%s:%s Creating a locator thread", SRCNAME, __func__); const auto args = new LocateArgs(recp, mail); HANDLE thread = CreateThread (NULL, 0, do_locate_secret, (LPVOID) args, 0, NULL); CloseHandle (thread); } gpgol_unlock (&keycache_lock); TRETURN; } void KeyCache::setSmimeKey(const std::string &mbox, const GpgME::Key &key) { d->setSmimeKey(mbox, key); } void KeyCache::setPgpKey(const std::string &mbox, const GpgME::Key &key) { d->setPgpKey(mbox, key); } void KeyCache::setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key) { d->setSmimeKeySecret(mbox, key); } void KeyCache::setPgpKeySecret(const std::string &mbox, const GpgME::Key &key) { d->setPgpKeySecret(mbox, key); } bool KeyCache::isMailResolvable(Mail *mail) { TSTART; /* Get the data from the mail. */ const auto sender = mail->getSender (); auto recps = mail->getCachedRecipients (); if (sender.empty() || recps.empty()) { log_debug ("%s:%s: Mail has no sender or no recipients.", SRCNAME, __func__); TRETURN false; } std::vector encKeys = getEncryptionKeys (recps, GpgME::OpenPGP); if (!encKeys.empty()) { TRETURN true; } if (!opt.enable_smime) { TRETURN false; } /* Check S/MIME instead here we need to include the sender as we can't just generate a key. */ recps.push_back (sender); GpgME::Key sigKey= getSigningKey (sender.c_str(), GpgME::CMS); encKeys = getEncryptionKeys (recps, GpgME::CMS); TRETURN !encKeys.empty() && !sigKey.isNull(); } void KeyCache::update (const char *fpr, GpgME::Protocol proto) { d->update (fpr, proto); } GpgME::Key KeyCache::getByFpr (const char *fpr, bool block) const { return d->getByFpr (fpr, block); } void KeyCache::onUpdateJobDone (const char *fpr, const GpgME::Key &key) { return d->onUpdateJobDone (fpr, key); } void KeyCache::importFromAddrBook (const std::string &mbox, const char *key_data, - Mail *mail) const + Mail *mail, GpgME::Protocol proto) const { - return d->importFromAddrBook (mbox, key_data, mail); + return d->importFromAddrBook (mbox, key_data, mail, proto); } void KeyCache::onAddrBookImportJobDone (const std::string &mbox, - const std::vector &result_fprs) + const std::vector &result_fprs, + GpgME::Protocol proto) { - return d->onAddrBookImportJobDone (mbox, result_fprs); + return d->onAddrBookImportJobDone (mbox, result_fprs, proto); } std::vector -KeyCache::getOverrides (const std::string &mbox) +KeyCache::getOverrides (const std::string &mbox, GpgME::Protocol proto) { - return d->getPGPOverrides (mbox.c_str ()); + return d->getOverrides (mbox.c_str (), proto); } void KeyCache::populate () { return d->populate (); } std::vector KeyCache::getUltimateKeys () { gpgrt_lock_lock (&fpr_map_lock); const auto ret = d->m_ultimate_keys; gpgrt_lock_unlock (&fpr_map_lock); return ret; } /* static */ bool KeyCache::import_pgp_key_data (const GpgME::Data &data) { TSTART; if (data.isNull()) { STRANGEPOINT; TRETURN false; } auto ctx = GpgME::Context::create(GpgME::OpenPGP); if (!ctx) { STRANGEPOINT; TRETURN false; } const auto type = data.type(); if (type != GpgME::Data::PGPKey) { log_debug ("%s:%s: Data does not look like PGP Keys", SRCNAME, __func__); TRETURN false; } const auto keys = data.toKeys(); if (keys.empty()) { log_debug ("%s:%s: Data does not contain PGP Keys", SRCNAME, __func__); TRETURN false; } if (opt.enable_debug & DBG_DATA) { std::stringstream ss; for (const auto &key: keys) { ss << key << '\n'; } log_debug ("Importing keys: %s", ss.str().c_str()); } const auto result = ctx->importKeys(data); if ((opt.enable_debug & DBG_DATA)) { std::stringstream ss; ss << result; log_debug ("%s:%s: Import result: %s details:\n %s", SRCNAME, __func__, result.error ().asString (), ss.str().c_str()); if (result.error()) { GpgME::Data out; if (ctx->getAuditLog(out, GpgME::Context::DiagnosticAuditLog)) { log_error ("%s:%s: Failed to get diagnostics", SRCNAME, __func__); } else { log_debug ("%s:%s: Diagnostics: \n%s\n", SRCNAME, __func__, out.toString().c_str()); } } } else { log_debug ("%s:%s: Import result: %s", SRCNAME, __func__, result.error ().asString ()); } TRETURN !result.error(); } diff --git a/src/keycache.h b/src/keycache.h index 0563e24..c6f949f 100644 --- a/src/keycache.h +++ b/src/keycache.h @@ -1,153 +1,155 @@ #ifndef KEYCACHE_H #define KEYCACHE_H /* @file keycache.h * @brief Internal keycache * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include #include #include namespace GpgME { class Key; class Data; }; class Mail; class KeyCache { protected: /** Internal ctor */ explicit KeyCache (); public: /** Get the KeyCache */ static KeyCache* instance (); /* Try to find a key for signing in the internal secret key cache. If no proper key is found a Null key is returned.*/ GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto) const; /* Get the keys for recipents. The keys are taken from the internal cache. If one recipient can't be resolved an empty list is returned. */ std::vector getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) const; /* Start a key location in a background thread filling the key cache. The mail argument is used to add / remove the locator thread counter. async */ void startLocate (const std::vector &addrs, Mail *mail) const; /* Look for a secret key for the addr. async */ void startLocateSecret (const char *addr, Mail *mail) const; /* Start a key location in a background thread filling the key cache. async */ void startLocate (const char *addr, Mail *mail) const; /* Check that a mail is resolvable through the keycache. * * For OpenPGP only the recipients are checked as we can * generate a new key for the sender. **/ bool isMailResolvable (Mail *mail); /* Search / Update a key in the cache. This is meant to be called e.g. after a verify to update the key. async A known issue is that a get right after it might still return an outdated key but the get after that would return the updated one. This is acceptable as it only poses a minor problem with TOFU while we can show the correct state in the tooltip. */ void update (const char *fpr, GpgME::Protocol proto); /* Get a cached key. If block is true it will block if the key is currently searched for. This function will not search a key. Call update to insert keys into the cache */ GpgME::Key getByFpr (const char *fpr, bool block = true) const; /* Import key data from the address book for the address mbox. Keys imported this way take precedence over other keys for this mail address regardless of validity. The mail argument is used to add / remove the locator thread counter. async */ void importFromAddrBook (const std::string &mbox, const char *key_data, - Mail *mail) const; + Mail *mail, GpgME::Protocol proto) const; /* Get optional overrides for an address. */ - std::vector getOverrides (const std::string &mbox); + std::vector getOverrides (const std::string &mbox, + GpgME::Protocol proto); /* Populate the fingerprint and secret key maps */ void populate (); /* Get a vector of ultimately trusted keys. */ std::vector getUltimateKeys (); /* Import PGP Keys from a Data object. Returns true on success. */ static bool import_pgp_key_data(const GpgME::Data &data); // Internal for thread void setSmimeKey(const std::string &mbox, const GpgME::Key &key); void setPgpKey(const std::string &mbox, const GpgME::Key &key); void setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key); void setPgpKeySecret(const std::string &mbox, const GpgME::Key &key); void onUpdateJobDone(const char *fpr, const GpgME::Key &key); void onAddrBookImportJobDone (const std::string &fpr, - const std::vector &result_fprs); + const std::vector &result_fprs, + GpgME::Protocol proto); private: class Private; std::shared_ptr d; }; #endif