diff --git a/src/keycache.cpp b/src/keycache.cpp
index e1c3d63..90cd7b4 100644
--- a/src/keycache.cpp
+++ b/src/keycache.cpp
@@ -1,1684 +1,1692 @@
/* @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;
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;
}
time_t getLastSubkeyCreation(const GpgME::Key &k)
{
TSTART;
time_t ret = 0;
for (const auto &sub: k.subkeys())
{
if (sub.isBad())
{
continue;
}
if (sub.creationTime() > ret)
{
ret = sub.creationTime();
}
}
TRETURN ret;
}
GpgME::Key compareSkeys(const GpgME::Key &old, const GpgME::Key &newKey)
{
TSTART;
if (newKey.isNull())
{
return old;
}
if (old.isNull())
{
return newKey;
}
if (old.primaryFingerprint() && newKey.primaryFingerprint() &&
!strcmp (old.primaryFingerprint(), newKey.primaryFingerprint()))
{
// Both are the same. Take the newer one.
return newKey;
}
if (old.canSign() && !newKey.canSign())
{
log_debug ("%s:%s Keeping old skey with "
"fpr %s over %s because it can sign.",
SRCNAME, __func__,
anonstr (old.primaryFingerprint()),
anonstr (newKey.primaryFingerprint()));
TRETURN old;
}
if (!old.canSign() && newKey.canSign())
{
log_debug ("%s:%s Using new skey with "
"fpr %s over %s because it can sign.",
SRCNAME, __func__,
anonstr (newKey.primaryFingerprint()),
anonstr (old.primaryFingerprint()));
TRETURN newKey;
}
// Both can or can't sign. Use the newest one.
if (getLastSubkeyCreation (old) >= getLastSubkeyCreation (newKey))
{
log_debug ("%s:%s Keeping old skey with "
"fpr %s over %s because it is newer.",
SRCNAME, __func__,
anonstr (old.primaryFingerprint()),
anonstr (newKey.primaryFingerprint()));
TRETURN old;
}
log_debug ("%s:%s Using new skey with "
"fpr %s over %s because it is newer.",
SRCNAME, __func__,
anonstr (newKey.primaryFingerprint()),
anonstr (old.primaryFingerprint()));
TRETURN newKey;
}
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 = compareSkeys (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 = compareSkeys (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 in proto %s. no internal encryption",
SRCNAME, __func__, anonstr (recip.c_str ()),
to_cstr (proto));
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);
}
+std::vector
+KeyCache::getEncryptionKeys (const std::string &recipient, GpgME::Protocol proto) const
+{
+ std::vector vec;
+ vec.push_back (recipient);
+ return d->getEncryptionKeys (vec, 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->getCachedRecipientAddresses ();
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 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);
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();
}
diff --git a/src/keycache.h b/src/keycache.h
index c6f949f..764be53 100644
--- a/src/keycache.h
+++ b/src/keycache.h
@@ -1,155 +1,157 @@
#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;
+ std::vector getEncryptionKeys (const std::string &recipient,
+ 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, GpgME::Protocol proto) const;
/* Get optional overrides for an address. */
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,
GpgME::Protocol proto);
private:
class Private;
std::shared_ptr d;
};
#endif