diff --git a/src/commands/command.h b/src/commands/command.h index 8b0df44a0..73f99698d 100644 --- a/src/commands/command.h +++ b/src/commands/command.h @@ -1,141 +1,139 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/command.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_COMMAND_H__ #define __KLEOPATRA_COMMANDS_COMMAND_H__ #include #include // for WId #include #include // for ExecutionContext #include class QModelIndex; template class QList; class QAbstractItemView; namespace GpgME { class Key; } namespace Kleo { class KeyListController; class AbstractKeyListSortFilterProxyModel; class Command : public QObject, public ExecutionContext { Q_OBJECT public: explicit Command(KeyListController *parent); explicit Command(QAbstractItemView *view, KeyListController *parent); explicit Command(const GpgME::Key &key); explicit Command(const std::vector &keys); ~Command() override; enum Restriction { NoRestriction = 0, NeedSelection = 1, OnlyOneKey = 2, NeedSecretKey = 4, MustNotBeSecretKey = 8, MustBeOpenPGP = 16, MustBeCMS = 32, // esoteric: MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate = 64, // for set-owner-trust AnyCardHasNullPin = 128, AnyCardCanLearnKeys = 256, MustBeRoot = 512, MustBeTrustedRoot = 1024 | MustBeRoot, MustBeUntrustedRoot = 2048 | MustBeRoot, - NeedsSmartCard = 4096, - _AllRestrictions_Helper, AllRestrictions = 2 * (_AllRestrictions_Helper - 1) - 1 }; Q_DECLARE_FLAGS(Restrictions, Restriction) static Restrictions restrictions() { return NoRestriction; } /** Classify the files and return the most appropriate commands. * * @param files: A list of files. * * @returns null QString on success. Error message otherwise. */ static QVectorcommandsForFiles(const QStringList &files); /** Get a command for a query. * * @param query: A keyid / fingerprint or any string to use in the search. */ static Command *commandForQuery(const QString &query); void setParentWidget(QWidget *widget); void setParentWId(WId wid); void setView(QAbstractItemView *view); void setIndex(const QModelIndex &idx); void setIndexes(const QList &idx); void setKey(const GpgME::Key &key); void setKeys(const std::vector &keys); void setAutoDelete(bool on); bool autoDelete() const; void setWarnWhenRunningAtShutdown(bool warn); bool warnWhenRunningAtShutdown() const; public Q_SLOTS: void start(); void cancel(); Q_SIGNALS: void info(const QString &message, int timeout = 0); void progress(const QString &message, int current, int total); void finished(); void canceled(); private: virtual void doStart() = 0; virtual void doCancel() = 0; private: void applyWindowID(QWidget *wid) const override; protected: void addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &tabToolTip = QString()); protected: class Private; kdtools::pimpl_ptr d; protected: explicit Command(Private *pp); explicit Command(QAbstractItemView *view, Private *pp); explicit Command(const std::vector &keys, Private *pp); explicit Command(const GpgME::Key &key, Private *pp); }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::Command::Restrictions) #endif /* __KLEOPATRA_COMMANDS_COMMAND_H__ */ diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 0fce33f1d..75921754d 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,615 +1,580 @@ -/* commands/setinitialpincommand.cpp +/* commands/keytocardcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytocardcommand.h" #include "kleopatra_debug.h" #include "command_p.h" #include "smartcard/readerstatus.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "utils/writecertassuantransaction.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class KeyToCardCommand::Private : public Command::Private { friend class ::Kleo::Commands::KeyToCardCommand; KeyToCardCommand *q_func() const { return static_cast(q); } public: - explicit Private(KeyToCardCommand *qq, KeyListController *c); explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &key, const std::string &serialno); explicit Private(KeyToCardCommand *qq, const std::string &cardSlot, const std::string &serialno); ~Private(); private: void start(); void startTransferToOpenPGPCard(); void startTransferToPIVCard(); void startKeyToPIVCard(); void startCertificateToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); private: std::string mSerial; GpgME::Subkey mSubkey; std::string cardSlot; bool overwriteExistingAlreadyApproved = false; bool hasBeenCanceled = false; }; KeyToCardCommand::Private *KeyToCardCommand::d_func() { return static_cast(d.get()); } const KeyToCardCommand::Private *KeyToCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() -KeyToCardCommand::Private::Private(KeyToCardCommand *qq, KeyListController *c) - : Command::Private(qq, c) -{ -} - KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &key, const std::string &serialno) : Command::Private(qq, nullptr), mSerial(serialno), mSubkey(key) { } KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &cardSlot_, const std::string &serialno) : Command::Private(qq, nullptr) , mSerial(serialno) , cardSlot(cardSlot_) { } KeyToCardCommand::Private::~Private() { } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; // for now we always use the first smart card if (mSerial.empty()) { const auto cards = SmartCard::ReaderStatus::instance()->getCards(); if (!cards.size() || cards[0]->serialNumber().empty()) { error(i18n("Failed to find a smart card.")); finished(); return; } mSerial = cards[0]->serialNumber(); } const auto card = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } switch (card->appType()) { case SmartCard::Card::OpenPGPApplication: { startTransferToOpenPGPCard(); } break; case SmartCard::Card::PivApplication: { startTransferToPIVCard(); } break; default: { error(i18n("Sorry! Transferring keys to this card is not supported.")); finished(); return; } } } namespace { -static GpgME::Subkey getSubkeyToTransferToOpenPGPCard(const std::vector &keys) -{ - if (keys.size() == 1 && keys.front().hasSecret()) { - return keys.front().subkey(0); - } - return GpgME::Subkey(); -} - static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) { // Signing only return 1; } if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) { // Encrypt only return 2; } if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) { // Auth only return 3; } // Multiple uses, ask user. QStringList options; if (subKey.canSign() || subKey.canCertify()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Signature (%1)", 1); } if (subKey.canEncrypt()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Encryption (%1)", 2); } if (subKey.canAuthenticate()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Authentication (%1)", 3); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); const int slot = options.indexOf(choice) + 1; return ok ? slot : -1; } } void KeyToCardCommand::Private::startTransferToOpenPGPCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToOpenPGPCard()"; const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } - if (mSubkey.isNull()) { - mSubkey = getSubkeyToTransferToOpenPGPCard(keys()); - } if (mSubkey.isNull()) { finished(); return; } if (mSubkey.parent().protocol() != GpgME::OpenPGP) { error(i18n("Sorry! This key cannot be transferred to an OpenPGP card.")); finished(); return; } const auto slot = getOpenPGPCardSlotForKey(mSubkey, parentWidgetOrView()); if (slot < 1) { finished(); return; } // Check if we need to do the overwrite warning. std::string existingKey; QString encKeyWarning; if (slot == 1) { existingKey = pgpCard->sigFpr(); } else if (slot == 2) { existingKey = pgpCard->encFpr(); encKeyWarning = i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."); } else if (slot == 3) { existingKey = pgpCard->authFpr(); } if (!existingKey.empty()) { const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the fingerprint:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + encKeyWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } } // Now do the deed const auto time = QDateTime::fromSecsSinceEpoch(mSubkey.creationTime()); const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss")); const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 OPENPGP.%3 %4") .arg(QString::fromLatin1(mSubkey.keyGrip()), QString::fromStdString(mSerial)) .arg(slot) .arg(timestamp); ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), q_func(), "keyToOpenPGPCardDone"); } namespace { -static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::vector &keys, const std::string &cardSlot, const std::shared_ptr &card) +static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &card) { if (!cardSlot.empty()) { if (cardSlot == PIVCard::digitalSignatureKeyRef()) { // get signing certificate matching the key grip const std::string cardKeygrip = card->keyGrip(cardSlot); const auto subkey = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip); if (subkey.canSign() && subkey.parent().protocol() == GpgME::CMS) { return subkey; } } if (cardSlot == PIVCard::keyManagementKeyRef()) { // get encryption certificate with secret subkey } return GpgME::Subkey(); } - if (keys.size() == 1) { - return keys.front().subkey(0); - } return GpgME::Subkey(); } static std::string getPIVCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if (subKey.canSign() && !subKey.canEncrypt()) { // Signing only return PIVCard::digitalSignatureKeyRef(); } if (subKey.canEncrypt() && !subKey.canSign()) { // Encrypt only return PIVCard::keyManagementKeyRef(); } // Multiple uses, ask user. QMap options; if (subKey.canSign()) { options.insert(i18nc("Placeholder 1 is the name of a key, e.g. 'Digital Signature Key'; " "placeholder 2 is the identifier of a slot on a smart card", "%1 (%2)", PIVCard::keyDisplayName(PIVCard::digitalSignatureKeyRef()), QString::fromStdString(PIVCard::digitalSignatureKeyRef())), PIVCard::digitalSignatureKeyRef()); } if (subKey.canEncrypt()) { options.insert(i18nc("Placeholder 1 is the name of a key, e.g. 'Digital Signature Key'; " "placeholder 2 is the identifier of a slot on a smart card", "%1 (%2)", PIVCard::keyDisplayName(PIVCard::keyManagementKeyRef()), QString::fromStdString(PIVCard::keyManagementKeyRef())), PIVCard::keyManagementKeyRef()); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), i18n("Please select the card slot the certificate should be written to:"), options.keys(), /* current= */ 0, /* editable= */ false, &ok); const std::string slot = options.value(choice); return ok ? slot : std::string(); } } void KeyToCardCommand::Private::startTransferToPIVCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } if (mSubkey.isNull()) { - mSubkey = getSubkeyToTransferToPIVCard(keys(), cardSlot, pivCard); + mSubkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard); } if (mSubkey.isNull()) { if (!cardSlot.empty()) { error(i18n("Sorry! No suitable certificate to write to this card slot was found.")); } finished(); return; } if (mSubkey.parent().protocol() != GpgME::CMS) { error(i18n("Sorry! This key cannot be transferred to a PIV card.")); finished(); return; } if (!mSubkey.canEncrypt() && !mSubkey.canSign()) { error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card.")); finished(); return; } // get card slot unless it was already selected before authentication was performed if (cardSlot.empty()) { cardSlot = getPIVCardSlotForKey(mSubkey, parentWidgetOrView()); if (cardSlot.empty()) { finished(); return; } } if (cardSlot == PIVCard::keyManagementKeyRef()) { startKeyToPIVCard(); } else { // skip key to card because it's only supported for encryption keys startCertificateToPIVCard(); } } void KeyToCardCommand::Private::startKeyToPIVCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } // Check if we need to do the overwrite warning. if (!overwriteExistingAlreadyApproved) { const std::string existingKey = pivCard->keyGrip(cardSlot); if (!existingKey.empty() && (existingKey != mSubkey.keyGrip())) { const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : QString(); const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the key grip:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + decryptionWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } overwriteExistingAlreadyApproved = true; } } const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3") .arg(QString::fromLatin1(mSubkey.keyGrip()), QString::fromStdString(mSerial)) .arg(QString::fromStdString(cardSlot)); ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), q_func(), "keyToPIVCardDone"); } void KeyToCardCommand::Private::startCertificateToPIVCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startCertificateToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } // verify that public keys on the card and in the certificate match const std::string cardKeygrip = pivCard->keyGrip(cardSlot); const std::string certificateKeygrip = mSubkey.keyGrip(); if (cardKeygrip != certificateKeygrip) { error(i18n("

The certificate does not seem to correspond to the key on the card.

" "

Public key on card: %1
" "Public key of certificate: %2

", QString::fromStdString(cardKeygrip), QString::fromStdString(certificateKeygrip))); finished(); return; } auto ctx = Context::createForProtocol(GpgME::CMS); QGpgME::QByteArrayDataProvider dp; Data data(&dp); const Error err = ctx->exportPublicKeys(mSubkey.parent().primaryFingerprint(), data); if (err) { error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); finished(); return; } const QByteArray certificateData = dp.data(); const QString cmd = QStringLiteral("SCD WRITECERT %1") .arg(QString::fromStdString(cardSlot)); auto transaction = std::unique_ptr(new WriteCertAssuanTransaction(certificateData)); ReaderStatus::mutableInstance()->startTransaction(cmd.toUtf8(), q_func(), "certificateToPIVCardDone", std::move(transaction)); } void KeyToCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(mSerial, parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void KeyToCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startTransferToPIVCard(); } } void KeyToCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } -KeyToCardCommand::KeyToCardCommand(KeyListController *c) - : Command(new Private(this, c)) -{ -} - -KeyToCardCommand::KeyToCardCommand(QAbstractItemView *v, KeyListController *c) - : Command(v, new Private(this, c)) -{ -} - -KeyToCardCommand::KeyToCardCommand(const GpgME::Key &key) - : Command(key, new Private(this, nullptr)) -{ -} - KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialno) : Command(new Private(this, key, serialno)) { } KeyToCardCommand::KeyToCardCommand(const std::string& cardSlot, const std::string &serialno) : Command(new Private(this, cardSlot, serialno)) { } KeyToCardCommand::~KeyToCardCommand() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()"; } bool KeyToCardCommand::supported() { return true; } void KeyToCardCommand::keyToOpenPGPCardDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Moving the key to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { /* TODO DELETE_KEY is too strong, because it also deletes the stub * of the secret key. I could not find out how GnuPG does this. Question * to GnuPG Developers is pending an answer if (KMessageBox::questionYesNo(d->parentWidgetOrView(), i18n("Do you want to delete the key on this computer?"), i18nc("@title:window", "Key transferred to card")) == KMessageBox::Yes) { const QString cmd = QStringLiteral("DELETE_KEY --force %1").arg(d->mSubkey.keyGrip()); // Using readerstatus is a bit overkill but it's an easy way to talk to the agent. ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), this, "deleteDone"); } */ KMessageBox::information(d->parentWidgetOrView(), i18n("Successfully copied the key to the card."), i18nc("@title", "Success")); } d->finished(); } void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err) { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::keyToPIVCardDone():" << err.asString() << "(" << err.code() << ")"; if (!err && !err.isCanceled()) { d->startCertificateToPIVCard(); return; } if (err) { // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH" if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) { d->authenticate(); return; } d->error(i18nc("@info", "Moving the key to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } d->finished(); } void KeyToCardCommand::certificateToPIVCardDone(const GpgME::Error &err) { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::certificateToPIVCardDone():" << err.asString() << "(" << err.code() << ")"; if (err) { // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH" if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) { d->authenticate(); return; } d->error(i18nc("@info", "Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { KMessageBox::information(d->parentWidgetOrView(), i18n("Successfully copied the certificate to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::deleteDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Failed to delete the key: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } d->finished(); } void KeyToCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()"; d->start(); } void KeyToCardCommand::doCancel() { } #undef q_func #undef d_func diff --git a/src/commands/keytocardcommand.h b/src/commands/keytocardcommand.h index f86628e57..959d56ce6 100644 --- a/src/commands/keytocardcommand.h +++ b/src/commands/keytocardcommand.h @@ -1,60 +1,52 @@ /* commands/keytocardcommand.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #define __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ #include #include #include namespace Kleo { namespace Commands { class KeyToCardCommand : public Command { Q_OBJECT public: - explicit KeyToCardCommand(KeyListController *parent); - explicit KeyToCardCommand(QAbstractItemView *view, KeyListController *parent); - explicit KeyToCardCommand(const GpgME::Key &key); KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialno); KeyToCardCommand(const std::string& cardSlot, const std::string &serialno); ~KeyToCardCommand() override; - /* reimp */ static Restrictions restrictions() - { - return OnlyOneKey | NeedSecretKey | NeedsSmartCard; - } - static bool supported(); public Q_SLOTS: void keyToOpenPGPCardDone(const GpgME::Error &err); void keyToPIVCardDone(const GpgME::Error &err); void certificateToPIVCardDone(const GpgME::Error &err); void deleteDone(const GpgME::Error &err); private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; }; } } #endif /* __KLEOPATRA_COMMANDS_KEYTOCARDCOMMAND_H__ */ diff --git a/src/kleopatra.rc b/src/kleopatra.rc index 98faf7d51..39cee29f7 100644 --- a/src/kleopatra.rc +++ b/src/kleopatra.rc @@ -1,131 +1,127 @@ &File &View &Certificates - - &Tools &Settings &Window &Help Main Toolbar &Certificates - - diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index 7b7cca47d..a7013f7fa 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,815 +1,801 @@ /* -*- mode: c++; c-basic-offset:4 -*- controllers/keylistcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include "tooltippreferences.h" #include "kleopatra_debug.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "commands/exportsecretkeycommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/changepassphrasecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/reloadkeyscommand.h" #include "commands/refreshx509certscommand.h" #include "commands/refreshopenpgpcertscommand.h" #include "commands/detailscommand.h" #include "commands/deletecertificatescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "commands/signencryptfoldercommand.h" #include "commands/clearcrlcachecommand.h" #include "commands/dumpcrlcachecommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/importcrlcommand.h" #include "commands/changeexpirycommand.h" #include "commands/changeownertrustcommand.h" #include "commands/changeroottrustcommand.h" #include "commands/certifycertificatecommand.h" #include "commands/adduseridcommand.h" #include "commands/newcertificatecommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/exportpaperkeycommand.h" -#include "commands/keytocardcommand.h" #include #include #include -#include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class KeyListController::Private { friend class ::Kleo::KeyListController; KeyListController *const q; public: explicit Private(KeyListController *qq); ~Private(); void connectView(QAbstractItemView *view); void connectCommand(Command *cmd); void connectTabWidget(); void disconnectTabWidget(); void addCommand(Command *cmd) { connectCommand(cmd); commands.insert(std::lower_bound(commands.begin(), commands.end(), cmd), cmd); } void addView(QAbstractItemView *view) { connectView(view); views.insert(std::lower_bound(views.begin(), views.end(), view), view); } void removeView(QAbstractItemView *view) { view->disconnect(q); view->selectionModel()->disconnect(q); views.erase(std::remove(views.begin(), views.end(), view), views.end()); } public: void slotDestroyed(QObject *o) { qCDebug(KLEOPATRA_LOG) << (void *)o; views.erase(std::remove(views.begin(), views.end(), o), views.end()); commands.erase(std::remove(commands.begin(), commands.end(), o), commands.end()); } void slotDoubleClicked(const QModelIndex &idx); void slotActivated(const QModelIndex &idx); void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_); void slotContextMenu(const QPoint &pos); void slotCommandFinished(); void slotAddKey(const Key &key); void slotAboutToRemoveKey(const Key &key); void slotProgress(const QString &what, int current, int total) { Q_EMIT q->progress(current, total); if (!what.isEmpty()) { Q_EMIT q->message(what); } } void slotActionTriggered(); void slotCurrentViewChanged(QAbstractItemView *view) { if (view && !std::binary_search(views.cbegin(), views.cend(), view)) { qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!"; addView(view); } currentView = view; q->enableDisableActions(view ? view->selectionModel() : nullptr); } private: int toolTipOptions() const; private: static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm); private: struct action_item { QPointer action; Command::Restrictions restrictions; Command *(*createCommand)(QAbstractItemView *, KeyListController *); }; std::vector actions; std::vector views; std::vector commands; QPointer parentWidget; QPointer tabWidget; QPointer currentView; QPointer flatModel, hierarchicalModel; }; KeyListController::Private::Private(KeyListController *qq) : q(qq), actions(), views(), commands(), parentWidget(), tabWidget(), flatModel(), hierarchicalModel() { connect(KeyCache::mutableInstance().get(), SIGNAL(added(GpgME::Key)), q, SLOT(slotAddKey(GpgME::Key))); connect(KeyCache::mutableInstance().get(), SIGNAL(aboutToRemove(GpgME::Key)), q, SLOT(slotAboutToRemoveKey(GpgME::Key))); } KeyListController::Private::~Private() {} KeyListController::KeyListController(QObject *p) : QObject(p), d(new Private(this)) { } KeyListController::~KeyListController() {} void KeyListController::Private::slotAddKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->addKey(key); } if (hierarchicalModel) { hierarchicalModel->addKey(key); } } void KeyListController::Private::slotAboutToRemoveKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->removeKey(key); } if (hierarchicalModel) { hierarchicalModel->removeKey(key); } } void KeyListController::addView(QAbstractItemView *view) { if (!view || std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->addView(view); } void KeyListController::removeView(QAbstractItemView *view) { if (!view || !std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->removeView(view); } void KeyListController::setCurrentView(QAbstractItemView *view) { d->slotCurrentViewChanged(view); } std::vector KeyListController::views() const { return d->views; } void KeyListController::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setTabWidget(TabWidget *tabWidget) { if (tabWidget == d->tabWidget) { return; } d->disconnectTabWidget(); d->tabWidget = tabWidget; d->connectTabWidget(); d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr); } void KeyListController::setParentWidget(QWidget *parent) { d->parentWidget = parent; } QWidget *KeyListController::parentWidget() const { return d->parentWidget; } static const struct { const char *signal; const char *slot; } tabs2controller[] = { { SIGNAL(viewAdded(QAbstractItemView*)), SLOT(addView(QAbstractItemView*)) }, { SIGNAL(viewAboutToBeRemoved(QAbstractItemView*)), SLOT(removeView(QAbstractItemView*)) }, { SIGNAL(currentViewChanged(QAbstractItemView*)), SLOT(slotCurrentViewChanged(QAbstractItemView*)) }, }; static const unsigned int numTabs2Controller = sizeof tabs2controller / sizeof * tabs2controller; void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); for (unsigned int i = 0; i < numTabs2Controller; ++i) { connect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } for (unsigned int i = 0; i < numTabs2Controller; ++i) { disconnect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { removeView(view); }); } AbstractKeyListModel *KeyListController::flatModel() const { return d->flatModel; } AbstractKeyListModel *KeyListController::hierarchicalModel() const { return d->hierarchicalModel; } QAbstractItemView *KeyListController::currentView() const { return d->currentView; } TabWidget *KeyListController::tabWidget() const { return d->tabWidget; } void KeyListController::createActions(KActionCollection *coll) { const action_data action_data[] = { // File menu { "file_new_certificate", i18n("New Key Pair..."), QString(), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), false, true }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), false, true }, { "file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"), "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"), false, true }, { "file_export_secret_keys", i18n("Export Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), false, true }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), false, true }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), false, true }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), false, true }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString(), false, true }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), false, true }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, SLOT(cancelCommands()), QStringLiteral("Escape"), false, false }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), false, true }, // Certificate menu { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), false, true }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), false, true }, { "certificates_change_expiry", i18n("Change Expiry Date..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_owner_trust", i18n("Change Certification Trust..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_add_userid", i18n("Add User-ID..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, - { - "certificates_transfer_to_card", i18n("Transfer to Smartcard"), QString(), - "send-to-symbolic", nullptr, nullptr, QString(), false, true - }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh X.509 Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; make_actions_from_data(action_data, coll); if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) { connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled); } // ### somehow make this better... registerActionForCommand(coll->action(QStringLiteral("file_new_certificate"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_lookup_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_import_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_export_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_export_secret_keys"))); registerActionForCommand(coll->action(QStringLiteral("file_export_paper_key"))); registerActionForCommand(coll->action(QStringLiteral("file_export_certificates_to_server"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_decrypt_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_folder"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_checksum_create_files"))); registerActionForCommand(coll->action(QStringLiteral("file_checksum_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("view_redisplay"))); //coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data() registerActionForCommand(coll->action(QStringLiteral("view_certificate_details"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_owner_trust"))); registerActionForCommand(coll->action(QStringLiteral("certificates_trust_root"))); registerActionForCommand(coll->action(QStringLiteral("certificates_distrust_root"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_certify_certificate"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_expiry"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_passphrase"))); registerActionForCommand(coll->action(QStringLiteral("certificates_add_userid"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_delete"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_dump_certificate"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_x509_certificates"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_openpgp_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_import_crl"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_clear_crl_cache"))); registerActionForCommand(coll->action(QStringLiteral("crl_dump_crl_cache"))); - //--- -#if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 - registerActionForCommand(coll->action(QStringLiteral("certificates_transfer_to_card"))); -#endif enableDisableActions(nullptr); } void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command * (*create)(QAbstractItemView *, KeyListController *)) { if (!action) { return; } Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow const Private::action_item ai = { action, restrictions, create }; connect(action, SIGNAL(triggered()), this, SLOT(slotActionTriggered())); d->actions.push_back(ai); } void KeyListController::registerCommand(Command *cmd) { if (!cmd || std::binary_search(d->commands.cbegin(), d->commands.cend(), cmd)) { return; } d->addCommand(cmd); qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (d->commands.size() == 1) { Q_EMIT commandsExecuting(true); } } bool KeyListController::hasRunningCommands() const { return !d->commands.empty(); } bool KeyListController::shutdownWarningRequired() const { return std::any_of(d->commands.cbegin(), d->commands.cend(), std::mem_fn(&Command::warnWhenRunningAtShutdown)); } // slot void KeyListController::cancelCommands() { std::for_each(d->commands.begin(), d->commands.end(), std::mem_fn(&Command::cancel)); } void KeyListController::Private::connectView(QAbstractItemView *view) { connect(view, SIGNAL(destroyed(QObject*)), q, SLOT(slotDestroyed(QObject*))); connect(view, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(slotDoubleClicked(QModelIndex))); connect(view, SIGNAL(activated(QModelIndex)), q, SLOT(slotActivated(QModelIndex))); connect(view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, SIGNAL(customContextMenuRequested(QPoint)), q, SLOT(slotContextMenu(QPoint))); } void KeyListController::Private::connectCommand(Command *cmd) { if (!cmd) { return; } connect(cmd, SIGNAL(destroyed(QObject*)), q, SLOT(slotDestroyed(QObject*))); connect(cmd, SIGNAL(finished()), q, SLOT(slotCommandFinished())); //connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) ); connect(cmd, &Command::info, q, &KeyListController::message); connect(cmd, SIGNAL(progress(QString,int,int)), q, SLOT(slotProgress(QString,int,int))); } void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } DetailsCommand *const c = new DetailsCommand(view, q); if (parentWidget) { c->setParentWidget(parentWidget); } c->setIndex(idx); c->start(); } void KeyListController::Private::slotActivated(const QModelIndex &idx) { Q_UNUSED(idx); QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } } void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_) { Q_UNUSED(old); Q_UNUSED(new_); const QItemSelectionModel *const sm = qobject_cast(q->sender()); if (!sm) { return; } q->enableDisableActions(sm); } void KeyListController::Private::slotContextMenu(const QPoint &p) { QAbstractItemView *const view = qobject_cast(q->sender()); if (view && std::binary_search(views.cbegin(), views.cend(), view)) { Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p)); } else { qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!"; } } void KeyListController::Private::slotCommandFinished() { Command *const cmd = qobject_cast(q->sender()); if (!cmd || !std::binary_search(commands.cbegin(), commands.cend(), cmd)) { return; } qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (commands.size() == 1) { Q_EMIT q->commandsExecuting(false); } } void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const { const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm); Q_FOREACH (const Private::action_item &ai, d->actions) if (ai.action) { ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask)); } } static bool all_secret_are_not_owner_trust_ultimate(const std::vector &keys) { for (const Key &key : keys) if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) { return false; } return true; } Command::Restrictions find_root_restrictions(const std::vector &keys) { bool trusted = false, untrusted = false; for (const Key &key : keys) if (key.isRoot()) if (key.userID(0).validity() == UserID::Ultimate) { trusted = true; } else { untrusted = true; } else { return Command::NoRestriction; } if (trusted) if (untrusted) { return Command::NoRestriction; } else { return Command::MustBeTrustedRoot; } else if (untrusted) { return Command::MustBeUntrustedRoot; } else { return Command::NoRestriction; } } Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm) { if (!sm) { return Command::NoRestriction; } const KeyListModelInterface *const m = dynamic_cast(sm->model()); if (!m) { return Command::NoRestriction; } const std::vector keys = m->keys(sm->selectedRows()); if (keys.empty()) { return Command::NoRestriction; } Command::Restrictions result = Command::NeedSelection; if (keys.size() == 1) { result |= Command::OnlyOneKey; } if (std::all_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::hasSecret))) { result |= Command::NeedSecretKey; } else if (!std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::hasSecret))) { result |= Command::MustNotBeSecretKey; } if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == OpenPGP; })) { result |= Command::MustBeOpenPGP; } else if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == CMS; })) { result |= Command::MustBeCMS; } if (all_secret_are_not_owner_trust_ultimate(keys)) { result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate; } result |= find_root_restrictions(keys); if (const ReaderStatus *rs = ReaderStatus::instance()) { - const auto cards = rs->getCards(); - if (cards.size() && !cards[0]->serialNumber().empty()) { - result |= Command::NeedsSmartCard; - } if (rs->anyCardHasNullPin()) { result |= Command::AnyCardHasNullPin; } if (rs->anyCardCanLearnKeys()) { result |= Command::AnyCardCanLearnKeys; } } return result; } void KeyListController::Private::slotActionTriggered() { if (const QObject *const s = q->sender()) { const auto it = std::find_if(actions.cbegin(), actions.cend(), [this](const action_item &item) { return item.action == q->sender(); }); if (it != actions.end()) if (Command *const c = it->createCommand(this->currentView, q)) { if (parentWidget) { c->setParentWidget(parentWidget); } c->start(); } else qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" << qPrintable(s->objectName()) << "\""; else { qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(s->objectName()); } } else { qCDebug(KLEOPATRA_LOG) << "not called through a signal/slot connection (sender() == NULL)"; } } int KeyListController::Private::toolTipOptions() const { using namespace Kleo::Formatting; static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage; static const int ownerFlags = Subject | UserIDs | OwnerTrust; static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint; const TooltipPreferences prefs; int flags = KeyID; flags |= prefs.showValidity() ? validityFlags : 0; flags |= prefs.showOwnerInformation() ? ownerFlags : 0; flags |= prefs.showCertificateDetails() ? detailsFlags : 0; return flags; } void KeyListController::updateConfig() { const int opts = d->toolTipOptions(); if (d->flatModel) { d->flatModel->setToolTipOptions(opts); } if (d->hierarchicalModel) { d->hierarchicalModel->setToolTipOptions(opts); } } #include "moc_keylistcontroller.cpp"