diff --git a/src/keycache.cpp b/src/keycache.cpp
index 446c06f..3f83e47 100644
--- a/src/keycache.cpp
+++ b/src/keycache.cpp
@@ -1,1604 +1,1607 @@
/* @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 ()));
// 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;
}
if (type != GpgME::Data::PGPKey && type != GpgME::Data::X509Cert)
{
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 = 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);
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,
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 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 (&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)
{
const auto overrides = getOverrides (recip.c_str (), proto);
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, 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);
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\" %s already in progress.",
SRCNAME, __func__, anonstr (mbox.c_str ()),
to_cstr (proto));
gpgol_unlock (&import_lock);
}
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,
GpgME::Protocol proto)
{
TSTART;
gpgol_lock (&keycache_lock);
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
{
override_map->insert (std::make_pair (mbox, result_fprs));
}
gpgol_unlock (&keycache_lock);
gpgol_lock (&import_lock);
const auto job_it = job_set->find(mbox);
if (job_it == job_set->end())
{
log_error ("%s:%s import for \"%s\" %s already finished.",
SRCNAME, __func__, anonstr (mbox.c_str ()),
to_cstr (proto));
gpgol_unlock (&import_lock);
TRETURN;
}
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_pgp_overrides;
std::unordered_map >
m_cms_overrides;
std::vector m_ultimate_keys;
std::set m_update_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;
}
+ GpgME::Key sigKey = getSigningKey (sender.c_str(), GpgME::OpenPGP);
std::vector encKeys = getEncryptionKeys (recps,
GpgME::OpenPGP);
- if (!encKeys.empty())
+ /* If S/MIME is prefrerred we only toggle auto encrypt for PGP if
+ we both have a signing key and encryption keys. */
+ if (!encKeys.empty() && (!opt.prefer_smime || !sigKey.isNull()))
{
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);
+ sigKey = getSigningKey (sender.c_str(), 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, GpgME::Protocol proto) const
{
return d->importFromAddrBook (mbox, key_data, mail, proto);
}
void
KeyCache::onAddrBookImportJobDone (const std::string &mbox,
const std::vector &result_fprs,
GpgME::Protocol proto)
{
return d->onAddrBookImportJobDone (mbox, result_fprs, proto);
}
std::vector
KeyCache::getOverrides (const std::string &mbox, GpgME::Protocol proto)
{
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();
}