diff options
Diffstat (limited to 'kmail/keyresolver.cpp')
-rw-r--r-- | kmail/keyresolver.cpp | 1615 |
1 files changed, 1615 insertions, 0 deletions
diff --git a/kmail/keyresolver.cpp b/kmail/keyresolver.cpp new file mode 100644 index 000000000..2161031fb --- /dev/null +++ b/kmail/keyresolver.cpp @@ -0,0 +1,1615 @@ +/* -*- c++ -*- + keyresolver.cpp + + This file is part of libkleopatra, the KDE keymanagement library + Copyright (c) 2004 Klarälvdalens Datakonsult AB + + Based on kpgp.cpp + Copyright (C) 2001,2002 the KPGP authors + See file libkdenetwork/AUTHORS.kpgp for details + + Libkleopatra is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + Libkleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "keyresolver.h" + +#include "kcursorsaver.h" +#include "kleo_util.h" + +#include <libemailfunctions/email.h> +#include <ui/keyselectiondialog.h> +#include <kleo/cryptobackendfactory.h> +#include <kleo/keylistjob.h> +#include <kleo/dn.h> + +#include <gpgmepp/key.h> +#include <gpgmepp/keylistresult.h> + +#include <kabc/stdaddressbook.h> +#include <klocale.h> +#include <kdebug.h> +#include <kinputdialog.h> +#include <kmessagebox.h> + +#include <qstringlist.h> +#include <qtl.h> + +#include <time.h> + +#include <algorithm> +#include <memory> +#include <iterator> +#include <functional> +#include <map> +#include <set> +#include <iostream> +#include <cassert> + + +// +// some predicates to be used in STL algorithms: +// + +static inline bool EmptyKeyList( const Kleo::KeyApprovalDialog::Item & item ) { + return item.keys.empty(); +} + +static inline QString ItemDotAddress( const Kleo::KeyResolver::Item & item ) { + return item.address; +} + +static inline bool ApprovalNeeded( const Kleo::KeyResolver::Item & item ) { + return item.pref == Kleo::UnknownPreference || item.pref == Kleo::NeverEncrypt || item.keys.empty() ; +} + +static inline Kleo::KeyResolver::Item +CopyKeysAndEncryptionPreferences( const Kleo::KeyResolver::Item & oldItem, + const Kleo::KeyApprovalDialog::Item & newItem ) { + return Kleo::KeyResolver::Item( oldItem.address, newItem.keys, newItem.pref, oldItem.signPref, oldItem.format ); +} + +static inline bool ByKeyID( const GpgME::Key & left, const GpgME::Key & right ) { + return qstrcmp( left.keyID(), right.keyID() ) < 0 ; +} + +static inline bool WithRespectToKeyID( const GpgME::Key & left, const GpgME::Key & right ) { + return qstrcmp( left.keyID(), right.keyID() ) == 0 ; +} + +static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { + if ( key.protocol() != GpgME::Context::OpenPGP ) { + return false; + } +#if 0 + if ( key.isRevoked() ) + kdWarning() << " is revoked" << endl; + if ( key.isExpired() ) + kdWarning() << " is expired" << endl; + if ( key.isDisabled() ) + kdWarning() << " is disabled" << endl; + if ( !key.canEncrypt() ) + kdWarning() << " can't encrypt" << endl; +#endif + if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() ) + return false; + const std::vector<GpgME::UserID> uids = key.userIDs(); + for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) { + if ( !it->isRevoked() && it->validity() != GpgME::UserID::Marginal ) + return true; +#if 0 + else + if ( it->isRevoked() ) + kdWarning() << "a userid is revoked" << endl; + else + kdWarning() << "bad validity " << it->validity() << endl; +#endif + } + return false; +} + +static bool ValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { + if ( key.protocol() != GpgME::Context::CMS ) + return false; + if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() ) + return false; + return true; +} + +static inline bool ValidTrustedEncryptionKey( const GpgME::Key & key ) { + switch ( key.protocol() ) { + case GpgME::Context::OpenPGP: + return ValidTrustedOpenPGPEncryptionKey( key ); + case GpgME::Context::CMS: + return ValidTrustedSMIMEEncryptionKey( key ); + default: + return false; + } +} + +static inline bool ValidSigningKey( const GpgME::Key & key ) { + if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() ) + return false; + return key.hasSecret(); +} + +static inline bool ValidOpenPGPSigningKey( const GpgME::Key & key ) { + return key.protocol() == GpgME::Context::OpenPGP && ValidSigningKey( key ); +} + +static inline bool ValidSMIMESigningKey( const GpgME::Key & key ) { + return key.protocol() == GpgME::Context::CMS && ValidSigningKey( key ); +} + +static inline bool NotValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { + return !ValidTrustedOpenPGPEncryptionKey( key ); +} + +static inline bool NotValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { + return !ValidTrustedSMIMEEncryptionKey( key ); +} + +static inline bool NotValidTrustedEncryptionKey( const GpgME::Key & key ) { + return !ValidTrustedEncryptionKey( key ); +} + +static inline bool NotValidSigningKey( const GpgME::Key & key ) { + return !ValidSigningKey( key ); +} + +static inline bool NotValidOpenPGPSigningKey( const GpgME::Key & key ) { + return !ValidOpenPGPSigningKey( key ); +} + +static inline bool NotValidSMIMESigningKey( const GpgME::Key & key ) { + return !ValidSMIMESigningKey( key ); +} + +static QStringList keysAsStrings( const std::vector<GpgME::Key>& keys ) { + QStringList strings; + for ( std::vector<GpgME::Key>::const_iterator it = keys.begin() ; it != keys.end() ; ++it ) { + assert( !(*it).userID(0).isNull() ); + QString keyLabel = QString::fromUtf8( (*it).userID(0).email() ); + if ( keyLabel.isEmpty() ) + keyLabel = QString::fromUtf8( (*it).userID(0).name() ); + if ( keyLabel.isEmpty() ) + keyLabel = QString::fromUtf8( (*it).userID(0).id() ); + strings.append( keyLabel ); + } + return strings; +} + +static inline std::vector<GpgME::Key> TrustedOrConfirmed( const std::vector<GpgME::Key> & keys ) { + + std::vector<GpgME::Key> fishies; + std::vector<GpgME::Key> ickies; + std::vector<GpgME::Key>::const_iterator it = keys.begin(); + const std::vector<GpgME::Key>::const_iterator end = keys.end(); + for ( ; it != end ; it++ ) { + const GpgME::Key key = *it; + assert( ValidTrustedEncryptionKey( key ) ); + const std::vector<GpgME::UserID> uids = key.userIDs(); + for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) { + if ( !it->isRevoked() && it->validity() == GpgME::UserID::Marginal ) { + fishies.push_back( key ); + break; + } + if ( !it->isRevoked() && it->validity() < GpgME::UserID::Never ) { + ickies.push_back( key ); + break; + } + } + } + + if ( fishies.empty() && ickies.empty() ) + return keys; + + // if some keys are not fully trusted, let the user confirm their use + QString msg = i18n("One or more of your configured OpenPGP encryption " + "keys or S/MIME certificates is not fully trusted " + "for encryption."); + + if ( !fishies.empty() ) { + // certificates can't have marginal trust + msg += i18n( "\nThe following keys are only marginally trusted: \n"); + msg += keysAsStrings( fishies ).join(","); + } + if ( !ickies.empty() ) { + msg += i18n( "\nThe following keys or certificates have unknown trust level: \n"); + msg += keysAsStrings( ickies ).join(","); + } + + if( KMessageBox::warningContinueCancel( 0, msg, i18n("Not Fully Trusted Encryption Keys"), + KStdGuiItem::cont(), + "not fully trusted encryption key warning" ) + == KMessageBox::Continue ) + return keys; + else + return std::vector<GpgME::Key>(); +} + +namespace { + struct IsNotForFormat : public std::unary_function<GpgME::Key,bool> { + IsNotForFormat( Kleo::CryptoMessageFormat f ) : format( f ) {} + + bool operator()( const GpgME::Key & key ) const { + return + ( isOpenPGP( format ) && key.protocol() != GpgME::Context::OpenPGP ) || + ( isSMIME( format ) && key.protocol() != GpgME::Context::CMS ); + } + + const Kleo::CryptoMessageFormat format; + }; +} + + + +class Kleo::KeyResolver::SigningPreferenceCounter : public std::unary_function<Kleo::KeyResolver::Item,void> { +public: + SigningPreferenceCounter() + : mTotal( 0 ), + mUnknownSigningPreference( 0 ), + mNeverSign( 0 ), + mAlwaysSign( 0 ), + mAlwaysSignIfPossible( 0 ), + mAlwaysAskForSigning( 0 ), + mAskSigningWheneverPossible( 0 ) + { + + } + void operator()( const Kleo::KeyResolver::Item & item ); +#define make_int_accessor(x) unsigned int num##x() const { return m##x; } + make_int_accessor(UnknownSigningPreference) + make_int_accessor(NeverSign) + make_int_accessor(AlwaysSign) + make_int_accessor(AlwaysSignIfPossible) + make_int_accessor(AlwaysAskForSigning) + make_int_accessor(AskSigningWheneverPossible) + make_int_accessor(Total) +#undef make_int_accessor +private: + unsigned int mTotal; + unsigned int mUnknownSigningPreference, mNeverSign, mAlwaysSign, + mAlwaysSignIfPossible, mAlwaysAskForSigning, mAskSigningWheneverPossible; +}; + +void Kleo::KeyResolver::SigningPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) { + switch ( item.signPref ) { +#define CASE(x) case x: ++m##x; break + CASE(UnknownSigningPreference); + CASE(NeverSign); + CASE(AlwaysSign); + CASE(AlwaysSignIfPossible); + CASE(AlwaysAskForSigning); + CASE(AskSigningWheneverPossible); +#undef CASE + } + ++mTotal; +} + + + +class Kleo::KeyResolver::EncryptionPreferenceCounter : public std::unary_function<Item,void> { + const Kleo::KeyResolver * _this; +public: + EncryptionPreferenceCounter( const Kleo::KeyResolver * kr, EncryptionPreference defaultPreference ) + : _this( kr ), + mDefaultPreference( defaultPreference ), + mTotal( 0 ), + mNoKey( 0 ), + mNeverEncrypt( 0 ), + mUnknownPreference( 0 ), + mAlwaysEncrypt( 0 ), + mAlwaysEncryptIfPossible( 0 ), + mAlwaysAskForEncryption( 0 ), + mAskWheneverPossible( 0 ) + { + + } + void operator()( Item & item ); + +#define make_int_accessor(x) unsigned int num##x() const { return m##x; } + make_int_accessor(NoKey) + make_int_accessor(NeverEncrypt) + make_int_accessor(UnknownPreference) + make_int_accessor(AlwaysEncrypt) + make_int_accessor(AlwaysEncryptIfPossible) + make_int_accessor(AlwaysAskForEncryption) + make_int_accessor(AskWheneverPossible) + make_int_accessor(Total) +#undef make_int_accessor +private: + EncryptionPreference mDefaultPreference; + unsigned int mTotal; + unsigned int mNoKey; + unsigned int mNeverEncrypt, mUnknownPreference, mAlwaysEncrypt, + mAlwaysEncryptIfPossible, mAlwaysAskForEncryption, mAskWheneverPossible; +}; + +void Kleo::KeyResolver::EncryptionPreferenceCounter::operator()( Item & item ) { + if ( item.needKeys ) + item.keys = _this->getEncryptionKeys( item.address, true ); + if ( item.keys.empty() ) { + ++mNoKey; + return; + } + switch ( !item.pref ? mDefaultPreference : item.pref ) { +#define CASE(x) case Kleo::x: ++m##x; break + CASE(NeverEncrypt); + CASE(UnknownPreference); + CASE(AlwaysEncrypt); + CASE(AlwaysEncryptIfPossible); + CASE(AlwaysAskForEncryption); + CASE(AskWheneverPossible); +#undef CASE + } + ++mTotal; +} + +namespace { + + class FormatPreferenceCounterBase : public std::unary_function<Kleo::KeyResolver::Item,void> { + public: + FormatPreferenceCounterBase() + : mTotal( 0 ), + mInlineOpenPGP( 0 ), + mOpenPGPMIME( 0 ), + mSMIME( 0 ), + mSMIMEOpaque( 0 ) + { + + } + +#define make_int_accessor(x) unsigned int num##x() const { return m##x; } + make_int_accessor(Total) + make_int_accessor(InlineOpenPGP) + make_int_accessor(OpenPGPMIME) + make_int_accessor(SMIME) + make_int_accessor(SMIMEOpaque) +#undef make_int_accessor + + unsigned int numOf( Kleo::CryptoMessageFormat f ) const { + switch ( f ) { +#define CASE(x) case Kleo::x##Format: return m##x + CASE(InlineOpenPGP); + CASE(OpenPGPMIME); + CASE(SMIME); + CASE(SMIMEOpaque); +#undef CASE + default: return 0; + } + } + + protected: + unsigned int mTotal; + unsigned int mInlineOpenPGP, mOpenPGPMIME, mSMIME, mSMIMEOpaque; + }; + + class EncryptionFormatPreferenceCounter : public FormatPreferenceCounterBase { + public: + EncryptionFormatPreferenceCounter() : FormatPreferenceCounterBase() {} + void operator()( const Kleo::KeyResolver::Item & item ); + }; + + class SigningFormatPreferenceCounter : public FormatPreferenceCounterBase { + public: + SigningFormatPreferenceCounter() : FormatPreferenceCounterBase() {} + void operator()( const Kleo::KeyResolver::Item & item ); + }; + +#define CASE(x) if ( item.format & Kleo::x##Format ) ++m##x; + void EncryptionFormatPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) { + if ( item.format & (Kleo::InlineOpenPGPFormat|Kleo::OpenPGPMIMEFormat) && + std::find_if( item.keys.begin(), item.keys.end(), + ValidTrustedOpenPGPEncryptionKey ) != item.keys.end() ) { + CASE(OpenPGPMIME); + CASE(InlineOpenPGP); + } + if ( item.format & (Kleo::SMIMEFormat|Kleo::SMIMEOpaqueFormat) && + std::find_if( item.keys.begin(), item.keys.end(), + ValidTrustedSMIMEEncryptionKey ) != item.keys.end() ) { + CASE(SMIME); + CASE(SMIMEOpaque); + } + ++mTotal; + } + + void SigningFormatPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) { + CASE(InlineOpenPGP); + CASE(OpenPGPMIME); + CASE(SMIME); + CASE(SMIMEOpaque); + ++mTotal; + } +#undef CASE + +} // anon namespace + +static QString canonicalAddress( const QString & _address ) { + const QString address = KPIM::getEmailAddress( _address ); + if ( address.find('@') == -1 ) { + // local address + //char hostname[1024]; + //gethostname(hostname,1024); + //return address + '@' + hostname; + return address + "@localdomain"; + } + else + return address; +} + + +struct FormatInfo { + std::vector<Kleo::KeyResolver::SplitInfo> splitInfos; + std::vector<GpgME::Key> signKeys; +}; + +struct Kleo::KeyResolver::Private { + std::set<QCString> alreadyWarnedFingerprints; + + std::vector<GpgME::Key> mOpenPGPSigningKeys; // signing + std::vector<GpgME::Key> mSMIMESigningKeys; // signing + + std::vector<GpgME::Key> mOpenPGPEncryptToSelfKeys; // encryption to self + std::vector<GpgME::Key> mSMIMEEncryptToSelfKeys; // encryption to self + + std::vector<Item> mPrimaryEncryptionKeys; // encryption to To/CC + std::vector<Item> mSecondaryEncryptionKeys; // encryption to BCC + + std::map<CryptoMessageFormat,FormatInfo> mFormatInfoMap; + + // key=email address, value=crypto preferences for this contact (from kabc) + typedef std::map<QString, ContactPreferences> ContactPreferencesMap; + ContactPreferencesMap mContactPreferencesMap; +}; + + +Kleo::KeyResolver::KeyResolver( bool encToSelf, bool showApproval, bool oppEncryption, + unsigned int f, + int encrWarnThresholdKey, int signWarnThresholdKey, + int encrWarnThresholdRootCert, int signWarnThresholdRootCert, + int encrWarnThresholdChainCert, int signWarnThresholdChainCert ) + : mEncryptToSelf( encToSelf ), + mShowApprovalDialog( showApproval ), + mOpportunisticEncyption( oppEncryption ), + mCryptoMessageFormats( f ), + mEncryptKeyNearExpiryWarningThreshold( encrWarnThresholdKey ), + mSigningKeyNearExpiryWarningThreshold( signWarnThresholdKey ), + mEncryptRootCertNearExpiryWarningThreshold( encrWarnThresholdRootCert ), + mSigningRootCertNearExpiryWarningThreshold( signWarnThresholdRootCert ), + mEncryptChainCertNearExpiryWarningThreshold( encrWarnThresholdChainCert ), + mSigningChainCertNearExpiryWarningThreshold( signWarnThresholdChainCert ) +{ + d = new Private(); +} + +Kleo::KeyResolver::~KeyResolver() { + delete d; d = 0; +} + +Kpgp::Result Kleo::KeyResolver::checkKeyNearExpiry( const GpgME::Key & key, const char * dontAskAgainName, + bool mine, bool sign, bool ca, + int recur_limit, const GpgME::Key & orig ) const { + if ( recur_limit <= 0 ) { + kdDebug() << "Kleo::KeyResolver::checkKeyNearExpiry(): key chain too long (>100 certs)" << endl; + return Kpgp::Ok; + } + const GpgME::Subkey subkey = key.subkey(0); + if ( d->alreadyWarnedFingerprints.count( subkey.fingerprint() ) ) + return Kpgp::Ok; // already warned about this one (and so about it's issuers) + d->alreadyWarnedFingerprints.insert( subkey.fingerprint() ); + + if ( subkey.neverExpires() ) + return Kpgp::Ok; + static const double secsPerDay = 24 * 60 * 60; + const int daysTillExpiry = + 1 + int( ::difftime( subkey.expirationTime(), time(0) ) / secsPerDay ); + kdDebug() << "Key 0x" << key.shortKeyID() << " expires in less than " + << daysTillExpiry << " days" << endl; + const int threshold = + ca + ? ( key.isRoot() + ? ( sign + ? signingRootCertNearExpiryWarningThresholdInDays() + : encryptRootCertNearExpiryWarningThresholdInDays() ) + : ( sign + ? signingChainCertNearExpiryWarningThresholdInDays() + : encryptChainCertNearExpiryWarningThresholdInDays() ) ) + : ( sign + ? signingKeyNearExpiryWarningThresholdInDays() + : encryptKeyNearExpiryWarningThresholdInDays() ); + if ( threshold > -1 && daysTillExpiry <= threshold ) { + const QString msg = + key.protocol() == GpgME::Context::OpenPGP + ? ( mine ? sign + ? i18n("<p>Your OpenPGP signing key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than a day.</p>", + "<p>Your OpenPGP signing key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>Your OpenPGP encryption key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than a day.</p>", + "<p>Your OpenPGP encryption key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The OpenPGP key for</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than a day.</p>", + "<p>The OpenPGP key for</p><p align=center><b>%1</b> (KeyID 0x%2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) ).arg( QString::fromUtf8( key.userID(0).id() ), + key.shortKeyID() ) + : ( ca + ? ( key.isRoot() + ? ( mine ? sign + ? i18n("<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The root certificate</p><p align=center><b>%3</b></p>" + "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) ) + : ( mine ? sign + ? i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>" + "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) ) ).arg( Kleo::DN( orig.userID(0).id() ).prettyDN(), + orig.issuerSerial(), + Kleo::DN( key.userID(0).id() ).prettyDN() ) + : ( mine ? sign + ? i18n("<p>Your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>Your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>Your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>Your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) + : i18n("<p>The S/MIME certificate for</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than a day.</p>", + "<p>The S/MIME certificate for</p><p align=center><b>%1</b> (serial number %2)</p>" + "<p>expires in less than %n days.</p>", + daysTillExpiry ) ).arg( Kleo::DN( key.userID(0).id() ).prettyDN(), + key.issuerSerial() ) ); + if ( KMessageBox::warningContinueCancel( 0, msg, + key.protocol() == GpgME::Context::OpenPGP + ? i18n("OpenPGP Key Expires Soon" ) + : i18n("S/MIME Certificate Expires Soon" ), + KStdGuiItem::cont(), dontAskAgainName ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + } + if ( key.isRoot() ) + return Kpgp::Ok; + else if ( const char * chain_id = key.chainID() ) { + const std::vector<GpgME::Key> issuer = lookup( chain_id, false ); + if ( issuer.empty() ) + return Kpgp::Ok; + else + return checkKeyNearExpiry( issuer.front(), dontAskAgainName, mine, sign, + true, recur_limit-1, ca ? orig : key ); + } + return Kpgp::Ok; +} + +Kpgp::Result Kleo::KeyResolver::setEncryptToSelfKeys( const QStringList & fingerprints ) { + if ( !encryptToSelf() ) + return Kpgp::Ok; + + std::vector<GpgME::Key> keys = lookup( fingerprints ); + std::remove_copy_if( keys.begin(), keys.end(), + std::back_inserter( d->mOpenPGPEncryptToSelfKeys ), + NotValidTrustedOpenPGPEncryptionKey ); + std::remove_copy_if( keys.begin(), keys.end(), + std::back_inserter( d->mSMIMEEncryptToSelfKeys ), + NotValidTrustedSMIMEEncryptionKey ); + + if ( d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size() + < keys.size() ) { + // too few keys remain... + const QString msg = i18n("One or more of your configured OpenPGP encryption " + "keys or S/MIME certificates is not usable for " + "encryption. Please reconfigure your encryption keys " + "and certificates for this identity in the identity " + "configuration dialog.\n" + "If you choose to continue, and the keys are needed " + "later on, you will be prompted to specify the keys " + "to use."); + return KMessageBox::warningContinueCancel( 0, msg, i18n("Unusable Encryption Keys"), + KStdGuiItem::cont(), + "unusable own encryption key warning" ) + == KMessageBox::Continue ? Kpgp::Ok : Kpgp::Canceled ; + } + + // check for near-expiry: + + for ( std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPEncryptToSelfKeys.begin() ; it != d->mOpenPGPEncryptToSelfKeys.end() ; ++it ) { + const Kpgp::Result r = checkKeyNearExpiry( *it, "own encryption key expires soon warning", + true, false ); + if ( r != Kpgp::Ok ) + return r; + } + + for ( std::vector<GpgME::Key>::const_iterator it = d->mSMIMEEncryptToSelfKeys.begin() ; it != d->mSMIMEEncryptToSelfKeys.end() ; ++it ) { + const Kpgp::Result r = checkKeyNearExpiry( *it, "own encryption key expires soon warning", + true, false ); + if ( r != Kpgp::Ok ) + return r; + } + + return Kpgp::Ok; +} + +Kpgp::Result Kleo::KeyResolver::setSigningKeys( const QStringList & fingerprints ) { + std::vector<GpgME::Key> keys = lookup( fingerprints, true ); // secret keys + std::remove_copy_if( keys.begin(), keys.end(), + std::back_inserter( d->mOpenPGPSigningKeys ), + NotValidOpenPGPSigningKey ); + std::remove_copy_if( keys.begin(), keys.end(), + std::back_inserter( d->mSMIMESigningKeys ), + NotValidSMIMESigningKey ); + + if ( d->mOpenPGPSigningKeys.size() + d->mSMIMESigningKeys.size() < keys.size() ) { + // too few keys remain... + const QString msg = i18n("One or more of your configured OpenPGP signing keys " + "or S/MIME signing certificates is not usable for " + "signing. Please reconfigure your signing keys " + "and certificates for this identity in the identity " + "configuration dialog.\n" + "If you choose to continue, and the keys are needed " + "later on, you will be prompted to specify the keys " + "to use."); + return KMessageBox::warningContinueCancel( 0, msg, i18n("Unusable Signing Keys"), + KStdGuiItem::cont(), + "unusable signing key warning" ) + == KMessageBox::Continue ? Kpgp::Ok : Kpgp::Canceled ; + } + + // check for near expiry: + + for ( std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPSigningKeys.begin() ; it != d->mOpenPGPSigningKeys.end() ; ++it ) { + const Kpgp::Result r = checkKeyNearExpiry( *it, "signing key expires soon warning", + true, true ); + if ( r != Kpgp::Ok ) + return r; + } + + for ( std::vector<GpgME::Key>::const_iterator it = d->mSMIMESigningKeys.begin() ; it != d->mSMIMESigningKeys.end() ; ++it ) { + const Kpgp::Result r = checkKeyNearExpiry( *it, "signing key expires soon warning", + true, true ); + if ( r != Kpgp::Ok ) + return r; + } + + return Kpgp::Ok; +} + +void Kleo::KeyResolver::setPrimaryRecipients( const QStringList & addresses ) { + d->mPrimaryEncryptionKeys = getEncryptionItems( addresses ); +} + +void Kleo::KeyResolver::setSecondaryRecipients( const QStringList & addresses ) { + d->mSecondaryEncryptionKeys = getEncryptionItems( addresses ); +} + +std::vector<Kleo::KeyResolver::Item> Kleo::KeyResolver::getEncryptionItems( const QStringList & addresses ) { + std::vector<Item> items; + items.reserve( addresses.size() ); + for ( QStringList::const_iterator it = addresses.begin() ; it != addresses.end() ; ++it ) { + QString addr = canonicalAddress( *it ).lower(); + const ContactPreferences pref = lookupContactPreferences( addr ); + + items.push_back( Item( *it, /*getEncryptionKeys( *it, true ),*/ + pref.encryptionPreference, + pref.signingPreference, + pref.cryptoMessageFormat ) ); + } + return items; +} + +static Kleo::Action action( bool doit, bool ask, bool dont, bool requested ) { + if ( requested && !dont ) + return Kleo::DoIt; + if ( doit && !ask && !dont ) + return Kleo::DoIt; + if ( !doit && ask && !dont ) + return Kleo::Ask; + if ( !doit && !ask && dont ) + return requested ? Kleo::Conflict : Kleo::DontDoIt ; + if ( !doit && !ask && !dont ) + return Kleo::DontDoIt ; + return Kleo::Conflict; +} + +Kleo::Action Kleo::KeyResolver::checkSigningPreferences( bool signingRequested ) const { + + if ( signingRequested && d->mOpenPGPSigningKeys.empty() && d->mSMIMESigningKeys.empty() ) + return Impossible; + + SigningPreferenceCounter count; + count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + count ); + count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + count ); + + unsigned int sign = count.numAlwaysSign(); + unsigned int ask = count.numAlwaysAskForSigning(); + const unsigned int dontSign = count.numNeverSign(); + if ( signingPossible() ) { + sign += count.numAlwaysSignIfPossible(); + ask += count.numAskSigningWheneverPossible(); + } + + return action( sign, ask, dontSign, signingRequested ); +} + +bool Kleo::KeyResolver::signingPossible() const { + return !d->mOpenPGPSigningKeys.empty() || !d->mSMIMESigningKeys.empty() ; +} + +Kleo::Action Kleo::KeyResolver::checkEncryptionPreferences( bool encryptionRequested ) const { + + if ( d->mPrimaryEncryptionKeys.empty() && d->mSecondaryEncryptionKeys.empty() ) + return DontDoIt; + + if ( encryptionRequested && encryptToSelf() && + d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty() ) + return Impossible; + + EncryptionPreferenceCounter count( this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference ); + count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + count ); + count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + count ); + + unsigned int encrypt = count.numAlwaysEncrypt(); + unsigned int ask = count.numAlwaysAskForEncryption(); + const unsigned int dontEncrypt = count.numNeverEncrypt() + count.numNoKey(); + if ( encryptionPossible() ) { + encrypt += count.numAlwaysEncryptIfPossible(); + ask += count.numAskWheneverPossible(); + } + + const Action act = action( encrypt, ask, dontEncrypt, encryptionRequested ); + if ( act != Ask || + std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + EncryptionPreferenceCounter( this, UnknownPreference ) ) ).numAlwaysAskForEncryption() ) + return act; + else + return AskOpportunistic; +} + +bool Kleo::KeyResolver::encryptionPossible() const { + return std::find_if( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + EmptyKeyList ) == d->mPrimaryEncryptionKeys.end() + && std::find_if( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + EmptyKeyList ) == d->mSecondaryEncryptionKeys.end() ; +} + +Kpgp::Result Kleo::KeyResolver::resolveAllKeys( bool& signingRequested, bool& encryptionRequested ) { + if ( !encryptionRequested && !signingRequested ) { + // make a dummy entry with all recipients, but no signing or + // encryption keys to avoid special-casing on the caller side: + dump(); + d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back( SplitInfo( allRecipients() ) ); + dump(); + return Kpgp::Ok; + } + Kpgp::Result result = Kpgp::Ok; + if ( encryptionRequested ) + result = resolveEncryptionKeys( signingRequested ); + if ( result != Kpgp::Ok ) + return result; + if ( signingRequested ) + if ( encryptionRequested ) + result = resolveSigningKeysForEncryption(); + else { + result = resolveSigningKeysForSigningOnly(); + if ( result == Kpgp::Failure ) { + signingRequested = false; + return Kpgp::Ok; + } + } + return result; +} + +Kpgp::Result Kleo::KeyResolver::resolveEncryptionKeys( bool signingRequested ) { + // + // 1. Get keys for all recipients: + // + + for ( std::vector<Item>::iterator it = d->mPrimaryEncryptionKeys.begin() ; it != d->mPrimaryEncryptionKeys.end() ; ++it ) { + if ( !it->needKeys ) + continue; + it->keys = getEncryptionKeys( it->address, false ); + if ( it->keys.empty() ) + return Kpgp::Canceled; + QString addr = canonicalAddress( it->address ).lower(); + const ContactPreferences pref = lookupContactPreferences( addr ); + it->pref = pref.encryptionPreference; + it->signPref = pref.signingPreference; + it->format = pref.cryptoMessageFormat; + } + + for ( std::vector<Item>::iterator it = d->mSecondaryEncryptionKeys.begin() ; it != d->mSecondaryEncryptionKeys.end() ; ++it ) { + if ( !it->needKeys ) + continue; + it->keys = getEncryptionKeys( it->address, false ); + if ( it->keys.empty() ) + return Kpgp::Canceled; + QString addr = canonicalAddress( it->address ).lower(); + const ContactPreferences pref = lookupContactPreferences( addr ); + it->pref = pref.encryptionPreference; + it->signPref = pref.signingPreference; + it->format = pref.cryptoMessageFormat; + } + + // 1a: Present them to the user + + const Kpgp::Result res = showKeyApprovalDialog(); + if ( res != Kpgp::Ok ) + return res; + + // + // 2. Check what the primary recipients need + // + + // 2a. Try to find a common format for all primary recipients, + // else use as many formats as needed + + const EncryptionFormatPreferenceCounter primaryCount + = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + EncryptionFormatPreferenceCounter() ); + + CryptoMessageFormat commonFormat = AutoFormat; + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + if ( !( concreteCryptoMessageFormats[i] & mCryptoMessageFormats ) ) + continue; + if ( signingRequested && signingKeysFor( concreteCryptoMessageFormats[i] ).empty() ) + continue; + if ( encryptToSelf() && encryptToSelfKeysFor( concreteCryptoMessageFormats[i] ).empty() ) + continue; + if ( primaryCount.numOf( concreteCryptoMessageFormats[i] ) == primaryCount.numTotal() ) { + commonFormat = concreteCryptoMessageFormats[i]; + break; + } + } + if ( commonFormat != AutoFormat ) + addKeys( d->mPrimaryEncryptionKeys, commonFormat ); + else + addKeys( d->mPrimaryEncryptionKeys ); + + collapseAllSplitInfos(); // these can be encrypted together + + // 2b. Just try to find _something_ for each secondary recipient, + // with a preference to a common format (if that exists) + + const EncryptionFormatPreferenceCounter secondaryCount + = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + EncryptionFormatPreferenceCounter() ); + + if ( commonFormat != AutoFormat && + secondaryCount.numOf( commonFormat ) == secondaryCount.numTotal() ) + addKeys( d->mSecondaryEncryptionKeys, commonFormat ); + else + addKeys( d->mSecondaryEncryptionKeys ); + + // 3. Check for expiry: + + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + const std::vector<SplitInfo> si_list = encryptionItems( concreteCryptoMessageFormats[i] ); + for ( std::vector<SplitInfo>::const_iterator sit = si_list.begin() ; sit != si_list.end() ; ++sit ) + for ( std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin() ; kit != sit->keys.end() ; ++kit ) { + const Kpgp::Result r = checkKeyNearExpiry( *kit, "other encryption key near expiry warning", + false, false ); + if ( r != Kpgp::Ok ) + return r; + } + } + + // 4. Check that we have the right keys for encryptToSelf() + + if ( !encryptToSelf() ) + return Kpgp::Ok; + + // 4a. Check for OpenPGP keys + + if ( !encryptionItems( InlineOpenPGPFormat ).empty() || + !encryptionItems( OpenPGPMIMEFormat ).empty() ) { + // need them + if ( d->mOpenPGPEncryptToSelfKeys.empty() ) { + const QString msg = i18n("Examination of recipient's encryption preferences " + "yielded that the message should be encrypted using " + "OpenPGP, at least for some recipients;\n" + "however, you have not configured valid trusted " + "OpenPGP encryption keys for this identity.\n" + "You may continue without encrypting to yourself, " + "but be aware that you will not be able to read your " + "own messages if you do so."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Unusable Encryption Keys"), + KStdGuiItem::cont(), + "encrypt-to-self will fail warning" ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + // FIXME: Allow selection + } + addToAllSplitInfos( d->mOpenPGPEncryptToSelfKeys, + InlineOpenPGPFormat|OpenPGPMIMEFormat ); + } + + // 4b. Check for S/MIME certs: + + if ( !encryptionItems( SMIMEFormat ).empty() || + !encryptionItems( SMIMEOpaqueFormat ).empty() ) { + // need them + if ( d->mSMIMEEncryptToSelfKeys.empty() ) { + // don't have one + const QString msg = i18n("Examination of recipient's encryption preferences " + "yielded that the message should be encrypted using " + "S/MIME, at least for some recipients;\n" + "however, you have not configured valid " + "S/MIME encryption certificates for this identity.\n" + "You may continue without encrypting to yourself, " + "but be aware that you will not be able to read your " + "own messages if you do so."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Unusable Encryption Keys"), + KStdGuiItem::cont(), + "encrypt-to-self will fail warning" ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + // FIXME: Allow selection + } + addToAllSplitInfos( d->mSMIMEEncryptToSelfKeys, + SMIMEFormat|SMIMEOpaqueFormat ); + } + + // FIXME: Present another message if _both_ OpenPGP and S/MIME keys + // are missing. + + return Kpgp::Ok; +} + +Kpgp::Result Kleo::KeyResolver::resolveSigningKeysForEncryption() { + if ( ( !encryptionItems( InlineOpenPGPFormat ).empty() || + !encryptionItems( OpenPGPMIMEFormat ).empty() ) + && d->mOpenPGPSigningKeys.empty() ) { + const QString msg = i18n("Examination of recipient's signing preferences " + "yielded that the message should be signed using " + "OpenPGP, at least for some recipients;\n" + "however, you have not configured valid " + "OpenPGP signing certificates for this identity."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Unusable Signing Keys"), + i18n("Do Not OpenPGP-Sign"), + "signing will fail warning" ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + // FIXME: Allow selection + } + if ( ( !encryptionItems( SMIMEFormat ).empty() || + !encryptionItems( SMIMEOpaqueFormat ).empty() ) + && d->mSMIMESigningKeys.empty() ) { + const QString msg = i18n("Examination of recipient's signing preferences " + "yielded that the message should be signed using " + "S/MIME, at least for some recipients;\n" + "however, you have not configured valid " + "S/MIME signing certificates for this identity."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Unusable Signing Keys"), + i18n("Do Not S/MIME-Sign"), + "signing will fail warning" ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + // FIXME: Allow selection + } + + // FIXME: Present another message if _both_ OpenPGP and S/MIME keys + // are missing. + + for ( std::map<CryptoMessageFormat,FormatInfo>::iterator it = d->mFormatInfoMap.begin() ; it != d->mFormatInfoMap.end() ; ++it ) + if ( !it->second.splitInfos.empty() ) { + dump(); + it->second.signKeys = signingKeysFor( it->first ); + dump(); + } + + return Kpgp::Ok; +} + +Kpgp::Result Kleo::KeyResolver::resolveSigningKeysForSigningOnly() { + // + // we don't need to distinguish between primary and secondary + // recipients here: + // + SigningFormatPreferenceCounter count; + count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + count ); + count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + count ); + + // try to find a common format that works for all (and that we have signing keys for): + + CryptoMessageFormat commonFormat = AutoFormat; + + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + if ( !(mCryptoMessageFormats & concreteCryptoMessageFormats[i]) ) + continue; // skip + if ( signingKeysFor( concreteCryptoMessageFormats[i] ).empty() ) + continue; // skip + if ( count.numOf( concreteCryptoMessageFormats[i] ) == count.numTotal() ) { + commonFormat = concreteCryptoMessageFormats[i]; + break; + } + } + + if ( commonFormat != AutoFormat ) { // found + dump(); + FormatInfo & fi = d->mFormatInfoMap[ commonFormat ]; + fi.signKeys = signingKeysFor( commonFormat ); + fi.splitInfos.resize( 1 ); + fi.splitInfos.front() = SplitInfo( allRecipients() ); + dump(); + return Kpgp::Ok; + } + + const QString msg = i18n("Examination of recipient's signing preferences " + "showed no common type of signature matching your " + "available signing keys.\n" + "Send message without signing?" ); + if ( KMessageBox::warningContinueCancel( 0, msg, i18n("No signing possible"), + KStdGuiItem::cont() ) + == KMessageBox::Continue ) { + d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back( SplitInfo( allRecipients() ) ); + return Kpgp::Failure; // means "Ok, but without signing" + } + return Kpgp::Canceled; +} + +std::vector<GpgME::Key> Kleo::KeyResolver::signingKeysFor( CryptoMessageFormat f ) const { + if ( isOpenPGP( f ) ) + return d->mOpenPGPSigningKeys; + if ( isSMIME( f ) ) + return d->mSMIMESigningKeys; + return std::vector<GpgME::Key>(); +} + +std::vector<GpgME::Key> Kleo::KeyResolver::encryptToSelfKeysFor( CryptoMessageFormat f ) const { + if ( isOpenPGP( f ) ) + return d->mOpenPGPEncryptToSelfKeys; + if ( isSMIME( f ) ) + return d->mSMIMEEncryptToSelfKeys; + return std::vector<GpgME::Key>(); +} + +QStringList Kleo::KeyResolver::allRecipients() const { + QStringList result; + std::transform( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + std::back_inserter( result ), ItemDotAddress ); + std::transform( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + std::back_inserter( result ), ItemDotAddress ); + return result; +} + +void Kleo::KeyResolver::collapseAllSplitInfos() { + dump(); + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + std::map<CryptoMessageFormat,FormatInfo>::iterator pos = + d->mFormatInfoMap.find( concreteCryptoMessageFormats[i] ); + if ( pos == d->mFormatInfoMap.end() ) + continue; + std::vector<SplitInfo> & v = pos->second.splitInfos; + if ( v.size() < 2 ) + continue; + SplitInfo & si = v.front(); + for ( std::vector<SplitInfo>::const_iterator it = v.begin() + 1; it != v.end() ; ++it ) { + si.keys.insert( si.keys.end(), it->keys.begin(), it->keys.end() ); + qCopy( it->recipients.begin(), it->recipients.end(), std::back_inserter( si.recipients ) ); + } + v.resize( 1 ); + } + dump(); +} + +void Kleo::KeyResolver::addToAllSplitInfos( const std::vector<GpgME::Key> & keys, unsigned int f ) { + dump(); + if ( !f || keys.empty() ) + return; + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + if ( !( f & concreteCryptoMessageFormats[i] ) ) + continue; + std::map<CryptoMessageFormat,FormatInfo>::iterator pos = + d->mFormatInfoMap.find( concreteCryptoMessageFormats[i] ); + if ( pos == d->mFormatInfoMap.end() ) + continue; + std::vector<SplitInfo> & v = pos->second.splitInfos; + for ( std::vector<SplitInfo>::iterator it = v.begin() ; it != v.end() ; ++it ) + it->keys.insert( it->keys.end(), keys.begin(), keys.end() ); + } + dump(); +} + +void Kleo::KeyResolver::dump() const { +#ifndef NDEBUG + if ( d->mFormatInfoMap.empty() ) + std::cerr << "Keyresolver: Format info empty" << std::endl; + for ( std::map<CryptoMessageFormat,FormatInfo>::const_iterator it = d->mFormatInfoMap.begin() ; it != d->mFormatInfoMap.end() ; ++it ) { + std::cerr << "Format info for " << Kleo::cryptoMessageFormatToString( it->first ) + << ":" << std::endl + << " Signing keys: "; + for ( std::vector<GpgME::Key>::const_iterator sit = it->second.signKeys.begin() ; sit != it->second.signKeys.end() ; ++sit ) + std::cerr << sit->shortKeyID() << " "; + std::cerr << std::endl; + unsigned int i = 0; + for ( std::vector<SplitInfo>::const_iterator sit = it->second.splitInfos.begin() ; sit != it->second.splitInfos.end() ; ++sit, ++i ) { + std::cerr << " SplitInfo #" << i << " encryption keys: "; + for ( std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin() ; kit != sit->keys.end() ; ++kit ) + std::cerr << kit->shortKeyID() << " "; + std::cerr << std::endl + << " SplitInfo #" << i << " recipients: " + << sit->recipients.join(", ").utf8() << std::endl; + } + } +#endif +} + +Kpgp::Result Kleo::KeyResolver::showKeyApprovalDialog() { + const bool showKeysForApproval = showApprovalDialog() + || std::find_if( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + ApprovalNeeded ) != d->mPrimaryEncryptionKeys.end() + || std::find_if( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + ApprovalNeeded ) != d->mSecondaryEncryptionKeys.end() ; + + if ( !showKeysForApproval ) + return Kpgp::Ok; + + std::vector<Kleo::KeyApprovalDialog::Item> items; + items.reserve( d->mPrimaryEncryptionKeys.size() + + d->mSecondaryEncryptionKeys.size() ); + std::copy( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + std::back_inserter( items ) ); + std::copy( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + std::back_inserter( items ) ); + + std::vector<GpgME::Key> senderKeys; + senderKeys.reserve( d->mOpenPGPEncryptToSelfKeys.size() + + d->mSMIMEEncryptToSelfKeys.size() ); + std::copy( d->mOpenPGPEncryptToSelfKeys.begin(), d->mOpenPGPEncryptToSelfKeys.end(), + std::back_inserter( senderKeys ) ); + std::copy( d->mSMIMEEncryptToSelfKeys.begin(), d->mSMIMEEncryptToSelfKeys.end(), + std::back_inserter( senderKeys ) ); + + const KCursorSaver idle( KBusyPtr::idle() ); + + Kleo::KeyApprovalDialog dlg( items, senderKeys ); + + if ( dlg.exec() == QDialog::Rejected ) + return Kpgp::Canceled; + + items = dlg.items(); + senderKeys = dlg.senderKeys(); + + if ( dlg.preferencesChanged() ) { + for ( uint i = 0; i < items.size(); ++i ) { + ContactPreferences pref = lookupContactPreferences( items[i].address ); + pref.encryptionPreference = items[i].pref; + pref.pgpKeyFingerprints.clear(); + pref.smimeCertFingerprints.clear(); + const std::vector<GpgME::Key> & keys = items[i].keys; + for ( std::vector<GpgME::Key>::const_iterator it = keys.begin(), end = keys.end() ; it != end ; ++it ) { + if ( it->protocol() == GpgME::Context::OpenPGP ) { + if ( const char * fpr = it->primaryFingerprint() ) + pref.pgpKeyFingerprints.push_back( fpr ); + } else if ( it->protocol() == GpgME::Context::CMS ) { + if ( const char * fpr = it->primaryFingerprint() ) + pref.smimeCertFingerprints.push_back( fpr ); + } + } + saveContactPreference( items[i].address, pref ); + } + } + + // show a warning if the user didn't select an encryption key for + // herself: + if ( encryptToSelf() && senderKeys.empty() ) { + const QString msg = i18n("You did not select an encryption key for yourself " + "(encrypt to self). You will not be able to decrypt " + "your own message if you encrypt it."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Missing Key Warning"), + i18n("&Encrypt") ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + else + mEncryptToSelf = false; + } + + // count empty key ID lists + const unsigned int emptyListCount = + std::count_if( items.begin(), items.end(), EmptyKeyList ); + + // show a warning if the user didn't select an encryption key for + // some of the recipients + if ( items.size() == emptyListCount ) { + const QString msg = ( d->mPrimaryEncryptionKeys.size() + + d->mSecondaryEncryptionKeys.size() == 1 ) + ? i18n("You did not select an encryption key for the " + "recipient of this message; therefore, the message " + "will not be encrypted.") + : i18n("You did not select an encryption key for any of the " + "recipients of this message; therefore, the message " + "will not be encrypted."); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Missing Key Warning"), + i18n("Send &Unencrypted") ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + } else if ( emptyListCount > 0 ) { + const QString msg = ( emptyListCount == 1 ) + ? i18n("You did not select an encryption key for one of " + "the recipients: this person will not be able to " + "decrypt the message if you encrypt it.") + : i18n("You did not select encryption keys for some of " + "the recipients: these persons will not be able to " + "decrypt the message if you encrypt it." ); + KCursorSaver idle( KBusyPtr::idle() ); + if ( KMessageBox::warningContinueCancel( 0, msg, + i18n("Missing Key Warning"), + i18n("&Encrypt") ) + == KMessageBox::Cancel ) + return Kpgp::Canceled; + } + + std::transform( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), + items.begin(), + d->mPrimaryEncryptionKeys.begin(), + CopyKeysAndEncryptionPreferences ); + std::transform( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(), + items.begin() + d->mPrimaryEncryptionKeys.size(), + d->mSecondaryEncryptionKeys.begin(), + CopyKeysAndEncryptionPreferences ); + + d->mOpenPGPEncryptToSelfKeys.clear(); + d->mSMIMEEncryptToSelfKeys.clear(); + + std::remove_copy_if( senderKeys.begin(), senderKeys.end(), + std::back_inserter( d->mOpenPGPEncryptToSelfKeys ), + NotValidTrustedOpenPGPEncryptionKey ); + std::remove_copy_if( senderKeys.begin(), senderKeys.end(), + std::back_inserter( d->mSMIMEEncryptToSelfKeys ), + NotValidTrustedSMIMEEncryptionKey ); + + return Kpgp::Ok; +} + +std::vector<Kleo::KeyResolver::SplitInfo> Kleo::KeyResolver::encryptionItems( Kleo::CryptoMessageFormat f ) const { + dump(); + std::map<CryptoMessageFormat,FormatInfo>::const_iterator it = + d->mFormatInfoMap.find( f ); + return it != d->mFormatInfoMap.end() ? it->second.splitInfos : std::vector<SplitInfo>() ; +} + +std::vector<GpgME::Key> Kleo::KeyResolver::signingKeys( CryptoMessageFormat f ) const { + dump(); + std::map<CryptoMessageFormat,FormatInfo>::const_iterator it = + d->mFormatInfoMap.find( f ); + return it != d->mFormatInfoMap.end() ? it->second.signKeys : std::vector<GpgME::Key>() ; +} + +// +// +// Private helper methods below: +// +// + + +std::vector<GpgME::Key> Kleo::KeyResolver::selectKeys( const QString & person, const QString & msg, const std::vector<GpgME::Key> & selectedKeys ) const { + Kleo::KeySelectionDialog dlg( i18n("Encryption Key Selection"), + msg, selectedKeys, + Kleo::KeySelectionDialog::ValidEncryptionKeys, + true, true ); // multi-selection and "remember choice" box + + if ( dlg.exec() != QDialog::Accepted ) + return std::vector<GpgME::Key>(); + std::vector<GpgME::Key> keys = dlg.selectedKeys(); + keys.erase( std::remove_if( keys.begin(), keys.end(), + NotValidTrustedEncryptionKey ), + keys.end() ); + if ( !keys.empty() && dlg.rememberSelection() ) + setKeysForAddress( person, dlg.pgpKeyFingerprints(), dlg.smimeFingerprints() ); + return keys; +} + + +std::vector<GpgME::Key> Kleo::KeyResolver::getEncryptionKeys( const QString & person, bool quiet ) const { + + const QString address = canonicalAddress( person ).lower(); + + // First look for this person's address in the address->key dictionary + const QStringList fingerprints = keysForAddress( address ); + + if ( !fingerprints.empty() ) { + kdDebug() << "Using encryption keys 0x" + << fingerprints.join( ", 0x" ) + << " for " << person << endl; + std::vector<GpgME::Key> keys = lookup( fingerprints ); + if ( !keys.empty() ) { + // Check if all of the keys are trusted and valid encryption keys + if ( std::find_if( keys.begin(), keys.end(), + NotValidTrustedEncryptionKey ) != keys.end() ) { + + // not ok, let the user select: this is not conditional on !quiet, + // since it's a bug in the configuration and the user should be + // notified about it as early as possible: + keys = selectKeys( person, + i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "There is a problem with the " + "encryption key(s) for \"%1\".\n\n" + "Please re-select the key(s) which should " + "be used for this recipient.").arg(person), + keys ); + } + keys = TrustedOrConfirmed( keys ); + + if ( !keys.empty() ) + return keys; + // hmmm, should we not return the keys in any case here? + } + } + + // Now search all public keys for matching keys + std::vector<GpgME::Key> matchingKeys = lookup( person ); + matchingKeys.erase( std::remove_if( matchingKeys.begin(), matchingKeys.end(), + NotValidTrustedEncryptionKey ), + matchingKeys.end() ); + // if no keys match the complete address look for keys which match + // the canonical mail address + if ( matchingKeys.empty() ) { + matchingKeys = lookup( address ); + matchingKeys.erase( std::remove_if( matchingKeys.begin(), matchingKeys.end(), + NotValidTrustedEncryptionKey ), + matchingKeys.end() ); + } + + // if called with quite == true (from EncryptionPreferenceCounter), we only want to + // check if there are keys for this recipients, not (yet) their validity, so + // don't show the untrusted encryption key warning in that case + if ( !quiet ) + matchingKeys = TrustedOrConfirmed( matchingKeys ); + if ( quiet || matchingKeys.size() == 1 ) + return matchingKeys; + + // no match until now, or more than one key matches; let the user + // choose the key(s) + // FIXME: let user get the key from keyserver + return TrustedOrConfirmed( selectKeys( person, + matchingKeys.empty() + ? i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "No valid and trusted encryption key was " + "found for \"%1\".\n\n" + "Select the key(s) which should " + "be used for this recipient.").arg(person) + : i18n("if in your language something like " + "'key(s)' isn't possible please " + "use the plural in the translation", + "More than one key matches \"%1\".\n\n" + "Select the key(s) which should " + "be used for this recipient.").arg(person), + matchingKeys ) ); +} + + +std::vector<GpgME::Key> Kleo::KeyResolver::lookup( const QStringList & patterns, bool secret ) const { + if ( patterns.empty() ) + return std::vector<GpgME::Key>(); + kdDebug() << "Kleo::KeyResolver::lookup( \"" << patterns.join( "\", \"" ) + << "\", " << secret << " )" << endl; + std::vector<GpgME::Key> result; + if ( mCryptoMessageFormats & (InlineOpenPGPFormat|OpenPGPMIMEFormat) ) + if ( const Kleo::CryptoBackend::Protocol * p = Kleo::CryptoBackendFactory::instance()->openpgp() ) { + std::auto_ptr<Kleo::KeyListJob> job( p->keyListJob( false, false, true ) ); // use validating keylisting + if ( job.get() ) { + std::vector<GpgME::Key> keys; + job->exec( patterns, secret, keys ); + result.insert( result.end(), keys.begin(), keys.end() ); + } + } + if ( mCryptoMessageFormats & (SMIMEFormat|SMIMEOpaqueFormat) ) + if ( const Kleo::CryptoBackend::Protocol * p = Kleo::CryptoBackendFactory::instance()->smime() ) { + std::auto_ptr<Kleo::KeyListJob> job( p->keyListJob( false, false, true ) ); // use validating keylisting + if ( job.get() ) { + std::vector<GpgME::Key> keys; + job->exec( patterns, secret, keys ); + result.insert( result.end(), keys.begin(), keys.end() ); + } + } + kdDebug() << " returned " << result.size() << " keys" << endl; + return result; +} + +void Kleo::KeyResolver::addKeys( const std::vector<Item> & items, CryptoMessageFormat f ) { + dump(); + for ( std::vector<Item>::const_iterator it = items.begin() ; it != items.end() ; ++it ) { + SplitInfo si( it->address ); + std::remove_copy_if( it->keys.begin(), it->keys.end(), + std::back_inserter( si.keys ), IsNotForFormat( f ) ); + dump(); + kdWarning( si.keys.empty() ) + << "Kleo::KeyResolver::addKeys(): Fix EncryptionFormatPreferenceCounter. " + << "It detected a common format, but the list of such keys for recipient \"" + << it->address << "\" is empty!" << endl; + d->mFormatInfoMap[ f ].splitInfos.push_back( si ); + } + dump(); +} + +void Kleo::KeyResolver::addKeys( const std::vector<Item> & items ) { + dump(); + for ( std::vector<Item>::const_iterator it = items.begin() ; it != items.end() ; ++it ) { + SplitInfo si( it->address ); + CryptoMessageFormat f = AutoFormat; + for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { + if ( concreteCryptoMessageFormats[i] & it->format ) { + f = concreteCryptoMessageFormats[i]; + break; + } + } + if ( f == AutoFormat ) + kdWarning() << "Kleo::KeyResolver::addKeys(): Something went wrong. Didn't find a format for \"" + << it->address << "\"" << endl; + else + std::remove_copy_if( it->keys.begin(), it->keys.end(), + std::back_inserter( si.keys ), IsNotForFormat( f ) ); + d->mFormatInfoMap[ f ].splitInfos.push_back( si ); + } + dump(); +} + +Kleo::KeyResolver::ContactPreferences Kleo::KeyResolver::lookupContactPreferences( const QString& address ) const +{ + const Private::ContactPreferencesMap::iterator it = + d->mContactPreferencesMap.find( address ); + if ( it != d->mContactPreferencesMap.end() ) + return it->second; + + KABC::AddressBook *ab = KABC::StdAddressBook::self( true ); + const KABC::Addressee::List res = ab->findByEmail( address ); + ContactPreferences pref; + if ( !res.isEmpty() ) { + KABC::Addressee addr = res.first(); + QString encryptPref = addr.custom( "KADDRESSBOOK", "CRYPTOENCRYPTPREF" ); + pref.encryptionPreference = Kleo::stringToEncryptionPreference( encryptPref ); + QString signPref = addr.custom( "KADDRESSBOOK", "CRYPTOSIGNPREF" ); + pref.signingPreference = Kleo::stringToSigningPreference( signPref ); + QString cryptoFormats = addr.custom( "KADDRESSBOOK", "CRYPTOPROTOPREF" ); + pref.cryptoMessageFormat = Kleo::stringToCryptoMessageFormat( cryptoFormats ); + pref.pgpKeyFingerprints = QStringList::split( ',', addr.custom( "KADDRESSBOOK", "OPENPGPFP" ) ); + pref.smimeCertFingerprints = QStringList::split( ',', addr.custom( "KADDRESSBOOK", "SMIMEFP" ) ); + } + // insert into map and grab resulting iterator + d->mContactPreferencesMap.insert( std::make_pair( address, pref ) ); + return pref; +} + +void Kleo::KeyResolver::saveContactPreference( const QString& email, const ContactPreferences& pref ) const +{ + d->mContactPreferencesMap.insert( std::make_pair( email, pref ) ); + KABC::AddressBook *ab = KABC::StdAddressBook::self( true ); + KABC::Addressee::List res = ab->findByEmail( email ); + + KABC::Addressee addr; + if ( res.isEmpty() ) { + bool ok = true; + QString fullName = KInputDialog::getText( i18n( "Name Selection" ), i18n( "Which name shall the contact '%1' have in your addressbook?" ).arg( email ), QString::null, &ok ); + if ( ok ) { + addr.setNameFromString( fullName ); + addr.insertEmail( email, true ); + } else + return; + } else + addr = res.first(); + + addr.insertCustom( "KADDRESSBOOK", "CRYPTOENCRYPTPREF", Kleo::encryptionPreferenceToString( pref.encryptionPreference ) ); + addr.insertCustom( "KADDRESSBOOK", "CRYPTOSIGNPREF", Kleo::signingPreferenceToString( pref.signingPreference ) ); + addr.insertCustom( "KADDRESSBOOK", "CRYPTOPROTOPREF", cryptoMessageFormatToString( pref.cryptoMessageFormat ) ); + addr.insertCustom( "KADDRESSBOOK", "OPENPGPFP", pref.pgpKeyFingerprints.join( "," ) ); + addr.insertCustom( "KADDRESSBOOK", "SMIMEFP", pref.smimeCertFingerprints.join( "," ) ); + + ab->insertAddressee( addr ); + KABC::Ticket *ticket = ab->requestSaveTicket( addr.resource() ); + if ( ticket ) + ab->save( ticket ); + + // Assumption: 'pref' comes from d->mContactPreferencesMap already, no need to update that +} + +Kleo::KeyResolver::ContactPreferences::ContactPreferences() + : encryptionPreference( UnknownPreference ), + signingPreference( UnknownSigningPreference ), + cryptoMessageFormat( AutoFormat ) +{ +} + +QStringList Kleo::KeyResolver::keysForAddress( const QString & address ) const { + if( address.isEmpty() ) { + return QStringList(); + } + QString addr = canonicalAddress( address ).lower(); + const ContactPreferences pref = lookupContactPreferences( addr ); + return pref.pgpKeyFingerprints + pref.smimeCertFingerprints; +} + +void Kleo::KeyResolver::setKeysForAddress( const QString& address, const QStringList& pgpKeyFingerprints, const QStringList& smimeCertFingerprints ) const { + if( address.isEmpty() ) { + return; + } + QString addr = canonicalAddress( address ).lower(); + ContactPreferences pref = lookupContactPreferences( addr ); + pref.pgpKeyFingerprints = pgpKeyFingerprints; + pref.smimeCertFingerprints = smimeCertFingerprints; + saveContactPreference( addr, pref ); +} |