diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp index eddc9f7..1065f6e 100644 --- a/src/cryptcontroller.cpp +++ b/src/cryptcontroller.cpp @@ -1,1031 +1,1035 @@ /* @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 #include #include #include "common.h" #include #define DEBUG_RESOLVER 1 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) { log_debug ("%s:%s: CryptController ctor for %p encrypt %i sign %i inline %i.", SRCNAME, __func__, mail, encrypt, sign, mail->do_pgp_inline ()); m_recipient_addrs = mail->take_cached_recipients (); } CryptController::~CryptController() { log_debug ("%s:%s:%p", SRCNAME, __func__, m_mail); release_cArray (m_recipient_addrs); } int CryptController::collect_data () { /* 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->take_cached_plain_body (); if (body && !*body) { xfree (body); body = nullptr; } LPMESSAGE message = get_oom_base_message (m_mail->item ()); if (!message) { log_error ("%s:%s: Failed to get base 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) { gpgol_message_box (m_mail->get_window(), utf8_gettext ("Can't encrypt / sign an empty message."), utf8_gettext ("GpgOL"), MB_OK); gpgol_release (message); xfree (body); return -1; } bool do_inline = m_mail->do_pgp_inline (); 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->set_do_pgp_inline (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 */ if (add_body_and_attachments (sink, message, att_table, m_mail, body, n_att_usable)) { log_error ("%s:%s: Collecting body and attachments failed.", SRCNAME, __func__); gpgol_release (message); return -1; } /* Message is no longer needed */ gpgol_release (message); /* Set the input buffer to start. */ m_input.seek (0, SEEK_SET); return 0; } int CryptController::lookup_fingerprints (const std::string &sigFpr, const std::vector recpFprs) { 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"); return -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__, sigFpr.c_str (), m_proto == GpgME::CMS ? "smime" : m_proto == GpgME::OpenPGP ? "openpgp" : "unknown"); return -1; } // reset context ctx = std::shared_ptr (GpgME::Context::createForProtocol (m_proto)); ctx->setKeyListMode (GpgME::Local); } if (!recpFprs.size()) { return 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__); return -1; } do { m_recipients.push_back(ctx->nextKey(err)); } while (!err); m_recipients.pop_back(); release_cArray (cRecps); return 0; } int CryptController::parse_output (GpgME::Data &resolverOutput) { // 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__); return -2; } if (line == "unencrypted") { log_debug ("%s:%s: FIXME resolver wants unencrypted", SRCNAME, __func__); return -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__); return -1; } return lookup_fingerprints (sigFpr, recpFprs); } int CryptController::resolve_keys_cached() { const auto cache = KeyCache::instance(); bool fallbackToSMIME = false; if (m_encrypt) { - m_recipients = cache->getEncryptionKeys((const char **)m_recipient_addrs, GpgME::OpenPGP); + const auto cached_sender = m_mail->get_cached_sender (); + auto recps = cArray_to_vector ((const char**) m_recipient_addrs); + recps.push_back (cached_sender); + + m_recipients = cache->getEncryptionKeys(recps, GpgME::OpenPGP); m_proto = GpgME::OpenPGP; if (m_recipients.empty() && opt.enable_smime) { - m_recipients = cache->getEncryptionKeys((const char **)m_recipient_addrs, GpgME::CMS); + m_recipients = cache->getEncryptionKeys(recps, GpgME::CMS); fallbackToSMIME = true; m_proto = GpgME::CMS; } if (m_recipients.empty()) { log_debug ("%s:%s: Failed to resolve keys through cache", SRCNAME, __func__); m_proto = GpgME::UnknownProtocol; return 1; } } if (m_sign) { if (!fallbackToSMIME) { m_signer_key = cache->getSigningKey (m_mail->get_cached_sender ().c_str (), GpgME::OpenPGP); m_proto = GpgME::OpenPGP; } if (m_signer_key.isNull() && opt.enable_smime) { m_signer_key = cache->getSigningKey (m_mail->get_cached_sender ().c_str (), GpgME::CMS); m_proto = GpgME::CMS; } if (m_signer_key.isNull()) { log_debug ("%s:%s: Failed to resolve signer key through cache", SRCNAME, __func__); m_recipients.clear(); m_proto = GpgME::UnknownProtocol; return 1; } } return 0; } int CryptController::resolve_keys () { m_recipients.clear(); if (opt.autoresolve && !resolve_keys_cached ()) { log_debug ("%s:%s: resolved keys through the cache", SRCNAME, __func__); start_crypto_overlay(); return 0; } std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; return -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->get_window (); 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->get_cached_sender (); 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 (m_encrypt) { args.push_back (std::string ("--encrypt")); // Get the recipients that are cached from OOM for (size_t i = 0; m_recipient_addrs && m_recipient_addrs[i]; i++) { args.push_back (GpgME::UserID::addrSpecFromString (m_recipient_addrs[i])); } } args.push_back (std::string ("--lang")); args.push_back (std::string (gettext_localename ())); // Args are prepared. Spawn the resolver. auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen TRACEPOINT; return -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); #ifdef DEBUG_RESOLVER log_debug ("Spawning args:"); for (size_t i = 0; cargs && cargs[i]; i++) { log_debug (SIZE_T_FORMAT ": '%s'", i, cargs[i]); } #endif 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(); #ifdef DEBUG_RESOLVER log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); #endif 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_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ()); log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ()); return -1; } return ret; } int CryptController::do_crypto () { log_debug ("%s:%s", SRCNAME, __func__); /* Start a WKS check if necessary. */ WKSHelper::instance()->start_check (m_mail->get_cached_sender ()); int ret = resolve_keys (); if (ret == -1) { //error log_debug ("%s:%s: Failure to resolve keys.", SRCNAME, __func__); return -1; } if (ret == -2) { // Cancel return -2; } bool do_inline = m_mail->do_pgp_inline (); 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->set_do_pgp_inline (false); m_bodyInput = GpgME::Data(GpgME::Data::null); } auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(m_proto)); if (!ctx) { log_error ("%s:%s: Failure to create context.", SRCNAME, __func__); gpgol_message_box (m_mail->get_window (), "Failure to create context.", utf8_gettext ("GpgOL"), MB_OK); return -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); if (result_pair.first.error() || result_pair.second.error()) { log_error ("%s:%s: Encrypt / Sign error %s %s.", SRCNAME, __func__, result_pair.first.error().asString(), result_pair.second.error().asString()); return -1; } if (result_pair.first.error().isCanceled() || result_pair.second.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_encrypt && m_sign) { // First sign then encrypt const auto sigResult = ctx->sign (m_input, m_output, GpgME::Detached); if (sigResult.error()) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, sigResult.error().asString()); return -1; } if (sigResult.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -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; return -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); if (encResult.error()) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, encResult.error().asString()); return -1; } if (encResult.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -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); if (result.error()) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_sign) { const auto result = ctx->sign (do_inline ? m_bodyInput : m_input, m_output, do_inline ? GpgME::Clearsigned : GpgME::Detached); if (result.error()) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -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; return 0; } static int write_data (sink_t sink, GpgME::Data &data) { if (!sink || !sink->writefnc) { return -1; } char buf[4096]; size_t nread; data.seek (0, SEEK_SET); while ((nread = data.read (buf, 4096)) > 0) { sink->writefnc (sink, buf, nread); } return 0; } int create_sign_attach (sink_t sink, protocol_t protocol, GpgME::Data &signature, GpgME::Data &signedData, const char *micalg) { 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; return rc; } /* Write the boundary so that it is not included in the hashing. */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } /* Write the signed mime structure */ if ((rc = write_data (sink, signedData))) { TRACEPOINT; return rc; } /* Write the signature attachment */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } if (protocol == PROTOCOL_OPENPGP) { rc = write_string (sink, "Content-Type: application/pgp-signature\r\n"); } else { rc = write_string (sink, "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-signature\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; return rc; } if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return 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; return rc; } } else if ((rc = write_data (sink, signature))) { TRACEPOINT; return rc; } // Add an extra linefeed with should not harm. if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return rc; } /* Write the final boundary. */ if ((rc = write_boundary (sink, boundary, 1))) { TRACEPOINT; return rc; } return rc; } static int create_encrypt_attach (sink_t sink, protocol_t protocol, GpgME::Data &encryptedData) { char boundary[BOUNDARYSIZE+1]; int rc = create_top_encryption_header (sink, protocol, boundary, false); // From here on use goto failure pattern. if (rc) { log_error ("%s:%s: Failed to create top header.", SRCNAME, __func__); return rc; } if (protocol == PROTOCOL_OPENPGP) { 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__); return 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__); } return rc; } int CryptController::update_mail_mapi () { log_debug ("%s:%s", SRCNAME, __func__); if (m_mail->do_pgp_inline ()) { // Nothing to do for inline. log_debug ("%s:%s: Inline mail. No MAPI update.", SRCNAME, __func__); return 0; } LPMESSAGE message = get_oom_base_message (m_mail->item()); if (!message) { log_error ("%s:%s: Failed to obtain message.", SRCNAME, __func__); return -1; } mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0); // 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 multipart/encrypted // content type. Otherwise newer (2016) exchange servers will throw // an M2MCVT.StorageError.Exeption (See GnuPG-Bug-Id: T3853 ) std::string overrideMimeTag; if (m_proto == GpgME::CMS && m_encrypt) { 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); return -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); } else if (m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output); } 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); } // only on error. if (rc) { cancel_mapi_attachment (&attach, sink); } // cleanup mapi_release_attach_table (att_table); gpgol_release (attach); gpgol_release (message); return rc; } std::string CryptController::get_inline_data () { std::string ret; if (!m_mail->do_pgp_inline ()) { return 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); } return ret; } void CryptController::parse_micalg (const GpgME::SigningResult &result) { if (result.isNull()) { TRACEPOINT; return; } const auto signature = result.createdSignature(0); if (signature.isNull()) { TRACEPOINT; return; } const char *hashAlg = signature.hashAlgorithmAsString (); if (!hashAlg) { TRACEPOINT; return; } 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 ()); } void CryptController::start_crypto_overlay () { auto wid = m_mail->get_window (); std::string text; if (m_encrypt) { text = _("Encrypting..."); } else if (m_sign) { text = _("Signing..."); } m_overlay = std::unique_ptr (new Overlay (wid, text)); } diff --git a/src/keycache.cpp b/src/keycache.cpp index 73011aa..29382d5 100644 --- a/src/keycache.cpp +++ b/src/keycache.cpp @@ -1,566 +1,566 @@ /* @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 #include #include #include #include GPGRT_LOCK_DEFINE (keycache_lock); static KeyCache* singleton = nullptr; class KeyCache::Private { public: Private() { } void setPgpKey(const char *mbox, const GpgME::Key &key) { const std::string sMbox(mbox); gpgrt_lock_lock (&keycache_lock); auto it = m_pgp_key_map.find (sMbox); if (it == m_pgp_key_map.end ()) { m_pgp_key_map.insert (std::pair (sMbox, key)); } else { it->second = key; } gpgrt_lock_unlock (&keycache_lock); } void setSmimeKey(const char *mbox, const GpgME::Key &key) { const std::string sMbox(mbox); gpgrt_lock_lock (&keycache_lock); auto it = m_smime_key_map.find (sMbox); if (it == m_smime_key_map.end ()) { m_smime_key_map.insert (std::pair (sMbox, key)); } else { it->second = key; } gpgrt_lock_unlock (&keycache_lock); } void setPgpKeySecret(const char *mbox, const GpgME::Key &key) { const std::string sMbox(mbox); gpgrt_lock_lock (&keycache_lock); auto it = m_pgp_skey_map.find (sMbox); if (it == m_pgp_skey_map.end ()) { m_pgp_skey_map.insert (std::pair (sMbox, key)); } else { it->second = key; } gpgrt_lock_unlock (&keycache_lock); } void setSmimeKeySecret(const char *mbox, const GpgME::Key &key) { const std::string sMbox(mbox); gpgrt_lock_lock (&keycache_lock); auto it = m_smime_skey_map.find (sMbox); if (it == m_smime_skey_map.end ()) { m_smime_skey_map.insert (std::pair (sMbox, key)); } else { it->second = key; } gpgrt_lock_unlock (&keycache_lock); } GpgME::Key getKey (const char *addr, GpgME::Protocol proto) { if (!addr) { return GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgrt_lock_lock (&keycache_lock); const auto it = m_pgp_key_map.find (mbox); if (it == m_pgp_key_map.end ()) { gpgrt_lock_unlock (&keycache_lock); return GpgME::Key(); } const auto ret = it->second; gpgrt_lock_unlock (&keycache_lock); return ret; } gpgrt_lock_lock (&keycache_lock); const auto it = m_smime_key_map.find (mbox); if (it == m_smime_key_map.end ()) { gpgrt_lock_unlock (&keycache_lock); return GpgME::Key(); } const auto ret = it->second; gpgrt_lock_unlock (&keycache_lock); return ret; } GpgME::Key getSKey (const char *addr, GpgME::Protocol proto) { if (!addr) { return GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgrt_lock_lock (&keycache_lock); const auto it = m_pgp_skey_map.find (mbox); if (it == m_pgp_skey_map.end ()) { gpgrt_lock_unlock (&keycache_lock); return GpgME::Key(); } const auto ret = it->second; gpgrt_lock_unlock (&keycache_lock); return ret; } gpgrt_lock_lock (&keycache_lock); const auto it = m_smime_skey_map.find (mbox); if (it == m_smime_skey_map.end ()) { gpgrt_lock_unlock (&keycache_lock); return GpgME::Key(); } const auto ret = it->second; gpgrt_lock_unlock (&keycache_lock); return ret; } GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto) { const auto key = getSKey (addr, proto); if (key.isNull()) { log_mime_parser ("%s:%s: secret key for %s is null", SRCNAME, __func__, addr); return key; } if (!key.canReallySign()) { log_mime_parser ("%s:%s: Discarding key for %s because it can't sign", SRCNAME, __func__, addr); return GpgME::Key(); } if (!key.hasSecret()) { log_mime_parser ("%s:%s: Discarding key for %s because it has no secret", SRCNAME, __func__, addr); return GpgME::Key(); } if (in_de_vs_mode () && !key.isDeVs()) { log_mime_parser ("%s:%s: signing key for %s is not deVS", SRCNAME, __func__, addr); return GpgME::Key(); } return key; } - std::vector getEncryptionKeys (const char **recipients, + std::vector getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) { std::vector ret; - if (!recipients) + if (recipients.empty ()) { TRACEPOINT; return ret; } - for (int i = 0; recipients[i]; i++) + for (const auto &recip: recipients) { - const auto key = getKey (recipients[i], proto); + const auto key = getKey (recip.c_str (), proto); if (key.isNull()) { log_mime_parser ("%s:%s: No key for %s. no internal encryption", - SRCNAME, __func__, recipients[i]); + SRCNAME, __func__, recip.c_str ()); return std::vector(); } if (!key.canEncrypt() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_mime_parser ("%s:%s: Invalid key for %s. no internal encryption", - SRCNAME, __func__, recipients[i]); + SRCNAME, __func__, recip.c_str ()); return std::vector(); } if (in_de_vs_mode () && key.isDeVs ()) { log_mime_parser ("%s:%s: key for %s is not deVS", - SRCNAME, __func__, recipients[i]); + SRCNAME, __func__, recip.c_str ()); return std::vector(); } bool validEnough = false; /* Here we do the check if the key is valid for this recipient */ - const auto addrSpec = GpgME::UserID::addrSpecFromString (recipients[i]); + 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) { validEnough = true; break; } } if (!validEnough) { log_mime_parser ("%s:%s: UID for %s does not have at least marginal trust", - SRCNAME, __func__, recipients[i]); + SRCNAME, __func__, recip.c_str ()); return std::vector(); } // Accepting key ret.push_back (key); } return ret; } std::map m_pgp_key_map; std::map m_smime_key_map; std::map m_pgp_skey_map; std::map m_smime_skey_map; }; 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 char **recipients, GpgME::Protocol proto) const +KeyCache::getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) const { return d->getEncryptionKeys (recipients, proto); } static DWORD WINAPI do_locate (LPVOID arg) { char *addr = (char*) arg; log_mime_parser ("%s:%s searching key for addr: \"%s\"", SRCNAME, __func__, addr); const auto k = GpgME::Key::locate (addr); if (!k.isNull ()) { log_mime_parser ("%s:%s found key for addr: \"%s\":%s", SRCNAME, __func__, addr, k.primaryFingerprint()); KeyCache::instance ()->setPgpKey (addr, k); } if (opt.enable_smime) { auto ctx = GpgME::Context::createForProtocol (GpgME::CMS); if (!ctx) { TRACEPOINT; xfree (addr); return 0; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); GpgME::Error e = ctx->startKeyListing (addr); if (e) { TRACEPOINT; xfree (addr); return 0; } std::vector keys; GpgME::Error err; do { keys.push_back(ctx->nextKey(err)); } while (!err); keys.pop_back(); delete ctx; GpgME::Key candidate; for (const auto &key: keys) { if (key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_mime_parser ("%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; } } } if (!candidate.isNull()) { log_mime_parser ("%s:%s found SMIME key for addr: \"%s\":%s", SRCNAME, __func__, addr, candidate.primaryFingerprint()); KeyCache::instance()->setSmimeKey (addr, candidate); } } xfree (addr); log_debug ("%s:%s locator thread done", SRCNAME, __func__); return 0; } static void locate_secret (char *addr, GpgME::Protocol proto) { auto ctx = GpgME::Context::createForProtocol (proto); if (!ctx) { TRACEPOINT; return; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); GpgME::Error e = ctx->startKeyListing (addr, true); if (e) { TRACEPOINT; xfree (addr); return; } 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()) { log_mime_parser ("%s:%s: Skipping invalid secret key", SRCNAME, __func__); continue; } if (proto == GpgME::OpenPGP) { log_mime_parser ("%s:%s found pgp skey for addr: \"%s\":%s", SRCNAME, __func__, addr, key.primaryFingerprint()); KeyCache::instance()->setPgpKeySecret (addr, key); delete ctx; return; } if (proto == GpgME::CMS) { log_mime_parser ("%s:%s found cms skey for addr: \"%s\":%s", SRCNAME, __func__, addr, key.primaryFingerprint()); KeyCache::instance()->setSmimeKeySecret (addr, key); delete ctx; return; } } while (!err); delete ctx; return; } static DWORD WINAPI do_locate_secret (LPVOID arg) { char *addr = (char*) arg; log_mime_parser ("%s:%s searching secret key for addr: \"%s\"", SRCNAME, __func__, addr); locate_secret (addr, GpgME::OpenPGP); if (opt.enable_smime) { locate_secret (addr, GpgME::CMS); } xfree (addr); log_debug ("%s:%s locator sthread thread done", SRCNAME, __func__); return 0; } void KeyCache::startLocate (char **recipients) const { if (!recipients) { TRACEPOINT; return; } for (int i = 0; recipients[i]; i++) { startLocate (recipients[i]); } } void KeyCache::startLocate (const char *addr) const { if (!addr) { TRACEPOINT; return; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { return; } gpgrt_lock_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__); HANDLE thread = CreateThread (NULL, 0, do_locate, (LPVOID) strdup (recp.c_str ()), 0, NULL); CloseHandle (thread); } gpgrt_lock_unlock (&keycache_lock); } void KeyCache::startLocateSecret (const char *addr) const { if (!addr) { TRACEPOINT; return; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { return; } gpgrt_lock_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__); HANDLE thread = CreateThread (NULL, 0, do_locate_secret, (LPVOID) strdup (recp.c_str ()), 0, NULL); CloseHandle (thread); } gpgrt_lock_unlock (&keycache_lock); } void KeyCache::setSmimeKey(const char *mbox, const GpgME::Key &key) { d->setSmimeKey(mbox, key); } void KeyCache::setPgpKey(const char *mbox, const GpgME::Key &key) { d->setPgpKey(mbox, key); } void KeyCache::setSmimeKeySecret(const char *mbox, const GpgME::Key &key) { d->setSmimeKeySecret(mbox, key); } void KeyCache::setPgpKeySecret(const char *mbox, const GpgME::Key &key) { d->setPgpKeySecret(mbox, key); } diff --git a/src/keycache.h b/src/keycache.h index 9a3aded..41e7255 100644 --- a/src/keycache.h +++ b/src/keycache.h @@ -1,83 +1,83 @@ #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 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 char **recipients, + std::vector getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) const; /* Start a key location in a background thread filling the key cache. cArray is a null terminated array of address strings. */ void startLocate (char **cArray) const; /* Look for a secret key for the addr. */ void startLocateSecret (const char *addr) const; /* Start a key location in a background thread filling the key cache. */ void startLocate (const char *addr) const; // Internal for thread void setSmimeKey(const char *mbox, const GpgME::Key &key); void setPgpKey(const char *mbox, const GpgME::Key &key); void setSmimeKeySecret(const char *mbox, const GpgME::Key &key); void setPgpKeySecret(const char *mbox, const GpgME::Key &key); private: class Private; std::shared_ptr d; }; #endif