diff --git a/src/commands/command.h b/src/commands/command.h index f0260563d..b3bee32cc 100644 --- a/src/commands/command.h +++ b/src/commands/command.h @@ -1,140 +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 */ #pragma once #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 { // clang-format off NoRestriction = 0x0000, NeedSelection = 0x0001, OnlyOneKey = 0x0002, NeedSecretKey = 0x0004, //< command performs secret key operations NeedSecretKeyData = 0x0008, //< command needs access to the secret key data MustBeOpenPGP = 0x0010, MustBeCMS = 0x0020, // esoteric: MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate = 0x0040, // for set-owner-trust AnyCardHasNullPin = 0x0080, - AnyCardCanLearnKeys = 0x0100, MustBeRoot = 0x0200, MustBeTrustedRoot = 0x0400 | MustBeRoot, MustBeUntrustedRoot = 0x0800 | MustBeRoot, MustBeValid = 0x1000, //< key is neither revoked nor expired nor otherwise "bad" _AllRestrictions_Helper, AllRestrictions = 2 * (_AllRestrictions_Helper - 1) - 1, // clang-format on }; 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 QList commandsForFiles(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 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(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) diff --git a/src/commands/learncardkeyscommand.h b/src/commands/learncardkeyscommand.h index 44640800b..ecb85bf20 100644 --- a/src/commands/learncardkeyscommand.h +++ b/src/commands/learncardkeyscommand.h @@ -1,52 +1,47 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/learncardkeyscommand.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include namespace Kleo { namespace Commands { class LearnCardKeysCommand : public GnuPGProcessCommand { Q_OBJECT public: explicit LearnCardKeysCommand(GpgME::Protocol proto); ~LearnCardKeysCommand() override; GpgME::Protocol protocol() const; - /* reimp */ static Restrictions restrictions() - { - return AnyCardCanLearnKeys; - } - private: void doStart() override; QStringList arguments() const override; QString errorCaption() const override; QString successCaption() const override; QString crashExitMessage(const QStringList &) const override; QString errorExitMessage(const QStringList &) const override; QString successMessage(const QStringList &) const override; private: GpgME::Protocol m_protocol; }; } } diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index b137e9f13..cffa701d7 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,873 +1,870 @@ /* kleopatraapplication.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kleopatraapplication.h" #include "kleopatra_options.h" #include "mainwindow.h" #include "settings.h" #include "smimevalidationpreferences.h" #include "systrayicon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/detailscommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/newcertificatesigningrequestcommand.h" #include "commands/newopenpgpcertificatecommand.h" #include "commands/signencryptfilescommand.h" #include "dialogs/updatenotification.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #if QT_CONFIG(graphicseffect) #include #endif #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList default_logging_options() { QList result; result.push_back("io"); return result; } namespace { class FocusFrame : public QFocusFrame { Q_OBJECT public: using QFocusFrame::QFocusFrame; protected: void paintEvent(QPaintEvent *event) override; }; static QRect effectiveWidgetRect(const QWidget *w) { // based on QWidgetPrivate::effectiveRectFor #if QT_CONFIG(graphicseffect) if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled()) return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect(); #endif // QT_CONFIG(graphicseffect) return w->rect(); } static QRect clipRect(const QWidget *w) { // based on QWidgetPrivate::clipRect if (!w->isVisible()) { return QRect(); } QRect r = effectiveWidgetRect(w); int ox = 0; int oy = 0; while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) { ox -= w->x(); oy -= w->y(); w = w->parentWidget(); r &= QRect(ox, oy, w->width(), w->height()); } return r; } void FocusFrame::paintEvent(QPaintEvent *) { if (!widget()) { return; } QStylePainter p(this); QStyleOptionFocusRect option; initStyleOption(&option); const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option); const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option); const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2); p.setClipRect(rect); p.drawPrimitive(QStyle::PE_FrameFocusRect, option); } } class KleopatraApplication::Private { friend class ::KleopatraApplication; KleopatraApplication *const q; public: explicit Private(KleopatraApplication *qq) : q(qq) , ignoreNewInstance(true) , firstNewInstance(true) , sysTray(nullptr) , groupConfig{std::make_shared(QStringLiteral("kleopatragroupsrc"))} { } ~Private() { #ifndef QT_NO_SYSTEMTRAYICON delete sysTray; #endif } void setUpSysTrayIcon() { #ifndef QT_NO_SYSTEMTRAYICON Q_ASSERT(readerStatus); sysTray = new SysTrayIcon(); sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin()); - sysTray->setAnyCardCanLearnKeys(readerStatus->anyCardCanLearnKeys()); - connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin); - connect(readerStatus.get(), &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys); #endif } private: void connectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } void disconnectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } public: bool ignoreNewInstance; bool firstNewInstance; QPointer focusFrame; QPointer configureDialog; QPointer groupsConfigDialog; QPointer mainWindow; std::unique_ptr readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr groupConfig; std::shared_ptr keyCache; std::shared_ptr log; std::shared_ptr watcher; std::shared_ptr distroSettings; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval()); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPaths(gnupgFolderWhitelist()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); keyCache->setGroupConfig(groupConfig); keyCache->setGroupsEnabled(Settings().groupsEnabled()); // always enable remarks (aka tags); in particular, this triggers a // relisting of the keys with signatures and signature notations // after the initial (fast) key listing keyCache->enableRemarks(true); } void setUpFilterManager() { if (!Settings{}.cmsEnabled()) { KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP); } } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(','); const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR"); if (dirNative.isEmpty()) { return; } const QString dir = QFile::decodeName(dirNative); const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid())); std::unique_ptr logFile(new QFile(logFileName)); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) { qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled"; return; } log->setOutputDirectory(dir); if (logAll || options.contains("io")) { log->setIOLoggingEnabled(true); } qInstallMessageHandler(Log::messageHandler); if (logAll || options.contains("pipeio")) { KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug); } UiServer::setLogStream(log->logFile()); } void updateFocusFrame(QWidget *focusWidget) { if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) { if (!focusFrame) { focusFrame = new FocusFrame{focusWidget}; } focusFrame->setWidget(focusWidget); } else if (focusFrame) { focusFrame->setWidget(nullptr); } } }; KleopatraApplication::KleopatraApplication(int &argc, char *argv[]) : QApplication(argc, argv) , d(new Private(this)) { // disable parent<->child navigation in tree views with left/right arrow keys // because this interferes with column by column navigation that is required // for accessibility setStyleSheet(QStringLiteral("QTreeView { arrow-keys-navigate-into-children: 0; }")); connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) { d->updateFocusFrame(now); }); } void KleopatraApplication::init() { #ifdef Q_OS_WIN QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); #endif const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes(); for (const auto &scheme : blockedUrlSchemes) { QDesktopServices::setUrlHandler(scheme, this, "blockUrl"); } add_resources(); DN::setAttributeOrder(Settings{}.attributeOrder()); /* Start the gpg-agent early, this is done explicitly * because on an empty keyring our keylistings wont start * the agent. In that case any assuan-connect calls to * the agent will fail. The requested start via the * connection is additionally done in case the gpg-agent * is killed while Kleopatra is running. */ startGpgAgent(); d->readerStatus.reset(new SmartCard::ReaderStatus); connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent); d->setupKeyCache(); d->setUpSysTrayIcon(); d->setUpFilterManager(); d->setupLogging(); #ifdef Q_OS_WIN if (!SystemInfo::isHighContrastModeActive()) { /* In high contrast mode we do not want our own colors */ new KColorSchemeManager(this); } #else new KColorSchemeManager(this); #endif #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->show(); #endif setQuitOnLastWindowClosed(false); // Sync config when we are about to quit connect(this, &QApplication::aboutToQuit, this, []() { KSharedConfig::openConfig()->sync(); }); } KleopatraApplication::~KleopatraApplication() { delete d->groupsConfigDialog; delete d->mainWindow; } namespace { using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol); } void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory) { QCommandLineParser parser; kleopatra_options(&parser); QString err; if (!arguments.isEmpty() && !parser.parse(arguments)) { err = parser.errorText(); } else if (arguments.isEmpty()) { // KDBusServices omits the application name if no other // arguments are provided. In that case the parser prints // a warning. parser.parse(QStringList() << QCoreApplication::applicationFilePath()); } if (err.isEmpty()) { err = newInstance(parser, workingDirectory); } if (!err.isEmpty()) { KMessageBox::error(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command")); Q_EMIT setExitValue(1); return; } Q_EMIT setExitValue(0); } QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory) { if (d->ignoreNewInstance) { qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance"; return QString(); } QStringList files; const QDir cwd = QDir(workingDirectory); bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search")); // Query and Search treat positional arguments differently, see below. if (!queryMode) { const auto positionalArguments = parser.positionalArguments(); for (const QString &file : positionalArguments) { // We do not check that file exists here. Better handle // these errors in the UI. if (QFileInfo(file).isAbsolute()) { files << file; } else { files << cwd.absoluteFilePath(file); } } } GpgME::Protocol protocol = GpgME::UnknownProtocol; if (parser.isSet(QStringLiteral("openpgp"))) { qCDebug(KLEOPATRA_LOG) << "found OpenPGP"; protocol = GpgME::OpenPGP; } if (parser.isSet(QStringLiteral("cms"))) { qCDebug(KLEOPATRA_LOG) << "found CMS"; if (protocol == GpgME::OpenPGP) { return i18n("Ambiguous protocol: --openpgp and --cms"); } protocol = GpgME::CMS; } // Check for Parent Window id WId parentId = 0; if (parser.isSet(QStringLiteral("parent-windowid"))) { #ifdef Q_OS_WIN // WId is not a portable type as it is a pointer type on Windows. // casting it from an integer is ok though as the values are guaranteed to // be compatible in the documentation. parentId = reinterpret_cast(parser.value(QStringLiteral("parent-windowid")).toUInt()); #else parentId = parser.value(QStringLiteral("parent-windowid")).toUInt(); #endif } // Handle openpgp4fpr URI scheme QString needle; if (queryMode) { needle = parser.positionalArguments().join(QLatin1Char(' ')); } if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) { needle.remove(0, 12); } // Check for --search command. if (parser.isSet(QStringLiteral("search"))) { // This is an extra command instead of a combination with the // similar query to avoid changing the older query commands behavior // and query's "show details if a certificate exist or search on a // keyserver" logic is hard to explain and use consistently. if (needle.isEmpty()) { return i18n("No search string specified for --search"); } auto const cmd = new LookupCertificatesCommand(needle, nullptr); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --query command if (parser.isSet(QStringLiteral("query"))) { if (needle.isEmpty()) { return i18n("No fingerprint argument specified for --query"); } auto cmd = Command::commandForQuery(needle); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --gen-key command if (parser.isSet(QStringLiteral("gen-key"))) { if (protocol == GpgME::CMS) { const Kleo::Settings settings{}; if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) { auto cmd = new NewCertificateSigningRequestCommand; cmd->setParentWId(parentId); cmd->start(); } else { return i18n("You are not allowed to create S/MIME certificate signing requests."); } } else { auto cmd = new NewOpenPGPCertificateCommand; cmd->setParentWId(parentId); cmd->start(); } return QString(); } // Check for --config command if (parser.isSet(QStringLiteral("config"))) { openConfigDialogWithForeignParent(parentId); return QString(); } struct FuncInfo { QString optionName; Func func; }; // While most of these options can be handled by the content autodetection // below it might be useful to override the autodetection if the input is in // doubt and you e.g. only want to import .asc files or fail and not decrypt them // if they are actually encrypted data. static const std::vector funcMap{ {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile}, {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles}, {QStringLiteral("sign"), &KleopatraApplication::signFiles}, {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles}, {QStringLiteral("verify"), &KleopatraApplication::verifyFiles}, {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles}, {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles}, }; QString found; Func foundFunc = nullptr; for (const auto &[opt, fn] : funcMap) { if (parser.isSet(opt) && found.isEmpty()) { found = opt; foundFunc = fn; } else if (parser.isSet(opt)) { return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt); } } QStringList errors; if (!found.isEmpty()) { if (files.empty()) { return i18n("No files specified for \"%1\" command", found); } qCDebug(KLEOPATRA_LOG) << "found" << found; (this->*foundFunc)(files, protocol); } else { if (files.empty()) { if (!(d->firstNewInstance && isSessionRestored())) { qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow"; openOrRaiseMainWindow(); } } else { for (const QString &fileName : std::as_const(files)) { QFileInfo fi(fileName); if (!fi.isReadable()) { errors << i18n("Cannot read \"%1\"", fileName); } } handleFiles(files, parentId); } } d->firstNewInstance = false; #ifdef Q_OS_WIN // On Windows we might be started from the // explorer in any working directory. E.g. // a double click on a file. To avoid preventing // the folder from deletion we set the // working directory to the users homedir. QDir::setCurrent(QDir::homePath()); #endif return errors.join(QLatin1Char('\n')); } void KleopatraApplication::handleFiles(const QStringList &files, WId parentId) { const QList allCmds = Command::commandsForFiles(files); for (Command *cmd : allCmds) { if (parentId) { cmd->setParentWId(parentId); } else { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } cmd->setParentWidget(mw); } if (dynamic_cast(cmd)) { openOrRaiseMainWindow(); } cmd->start(); } } #ifndef QT_NO_SYSTEMTRAYICON const SysTrayIcon *KleopatraApplication::sysTrayIcon() const { return d->sysTray; } SysTrayIcon *KleopatraApplication::sysTrayIcon() { return d->sysTray; } #endif const MainWindow *KleopatraApplication::mainWindow() const { return d->mainWindow; } MainWindow *KleopatraApplication::mainWindow() { return d->mainWindow; } void KleopatraApplication::setMainWindow(MainWindow *mainWindow) { if (mainWindow == d->mainWindow) { return; } d->disconnectConfigureDialog(); d->mainWindow = mainWindow; #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->setMainWindow(mainWindow); #endif d->connectConfigureDialog(); } static void open_or_raise(QWidget *w) { #ifdef Q_OS_WIN if (w->isMinimized()) { qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window"; w->raise(); } else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "raising window"; w->raise(); #else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "activating window"; KWindowSystem::updateStartupId(w->windowHandle()); KWindowSystem::activateWindow(w->windowHandle()); #endif } else { qCDebug(KLEOPATRA_LOG) << __func__ << "showing window"; w->show(); } } void KleopatraApplication::toggleMainWindowVisibility() { if (mainWindow()) { mainWindow()->setVisible(!mainWindow()->isVisible()); } else { openOrRaiseMainWindow(); } if (mainWindow()->isVisible()) { mainWindow()->exportWindow(); } else { mainWindow()->unexportWindow(); } } void KleopatraApplication::restoreMainWindow() { qCDebug(KLEOPATRA_LOG) << "restoring main window"; // Sanity checks if (!isSessionRestored()) { qCDebug(KLEOPATRA_LOG) << "Not in session restore"; return; } if (mainWindow()) { qCDebug(KLEOPATRA_LOG) << "Already have main window"; return; } auto mw = new MainWindow; if (KMainWindow::canBeRestored(1)) { // restore to hidden state, Mainwindow::readProperties() will // restore saved visibility. mw->restore(1, false); } mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } void KleopatraApplication::openOrRaiseMainWindow() { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } open_or_raise(mw); UpdateNotification::checkUpdate(mw); } void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId) { if (!d->configureDialog) { d->configureDialog = new ConfigureDialog; d->configureDialog->setAttribute(Qt::WA_DeleteOnClose); d->connectConfigureDialog(); } // This is similar to what the commands do. if (parentWId) { if (QWidget *pw = QWidget::find(parentWId)) { d->configureDialog->setParent(pw, d->configureDialog->windowFlags()); } else { d->configureDialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId); } } open_or_raise(d->configureDialog); // If we have a parent we want to raise over it. if (parentWId) { d->configureDialog->raise(); } } void KleopatraApplication::openOrRaiseConfigDialog() { openConfigDialogWithForeignParent(0); } void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent) { if (!d->groupsConfigDialog) { d->groupsConfigDialog = new GroupsConfigDialog{parent}; d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose); } else { // reparent the dialog to ensure it's shown on top of the (modal) parent d->groupsConfigDialog->setParent(parent, Qt::Dialog); } open_or_raise(d->groupsConfigDialog); } #ifndef QT_NO_SYSTEMTRAYICON void KleopatraApplication::startMonitoringSmartCard() { Q_ASSERT(d->readerStatus); d->readerStatus->startMonitoring(); } #endif // QT_NO_SYSTEMTRAYICON void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/) { openOrRaiseMainWindow(); if (!files.empty()) { mainWindow()->importCertificatesFromFile(files); } } void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setEncryptionPolicy(Force); cmd->setSigningPolicy(Allow); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setSigningPolicy(Force); cmd->setEncryptionPolicy(Deny); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->start(); } void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/) { QStringList verifyFiles, createFiles; for (const QString &file : files) { if (isChecksumFile(file)) { verifyFiles << file; } else { createFiles << file; } } if (!verifyFiles.isEmpty()) { auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr); cmd->start(); } if (!createFiles.isEmpty()) { auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr); cmd->start(); } } void KleopatraApplication::setIgnoreNewInstance(bool ignore) { d->ignoreNewInstance = ignore; } bool KleopatraApplication::ignoreNewInstance() const { return d->ignoreNewInstance; } void KleopatraApplication::blockUrl(const QUrl &url) { qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url; KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18n("Prohibited")); } void KleopatraApplication::startGpgAgent() { Kleo::launchGpgAgent(); } void KleopatraApplication::setDistributionSettings(const std::shared_ptr &settings) { d->distroSettings = settings; } std::shared_ptr KleopatraApplication::distributionSettings() const { return d->distroSettings; } #include "kleopatraapplication.moc" #include "moc_kleopatraapplication.cpp" diff --git a/src/smartcard/card.cpp b/src/smartcard/card.cpp index 014732362..36cadb9b9 100644 --- a/src/smartcard/card.cpp +++ b/src/smartcard/card.cpp @@ -1,362 +1,351 @@ /* smartcard/card.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 */ #include "card.h" #include "readerstatus.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; namespace { static QString formatVersion(int value) { if (value < 0) { return QString(); } const unsigned int a = ((value >> 24) & 0xff); const unsigned int b = ((value >> 16) & 0xff); const unsigned int c = ((value >> 8) & 0xff); const unsigned int d = ((value)&0xff); if (a) { return QStringLiteral("%1.%2.%3.%4").arg(QString::number(a), QString::number(b), QString::number(c), QString::number(d)); } else if (b) { return QStringLiteral("%1.%2.%3").arg(QString::number(b), QString::number(c), QString::number(d)); } else if (c) { return QStringLiteral("%1.%2").arg(QString::number(c), QString::number(d)); } return QString::number(d); } } Card::Card() { } Card::~Card() { } void Card::setStatus(Status s) { mStatus = s; } Card::Status Card::status() const { return mStatus; } void Card::setSerialNumber(const std::string &sn) { mSerialNumber = sn; } std::string Card::serialNumber() const { return mSerialNumber; } QString Card::displaySerialNumber() const { return mDisplaySerialNumber; } void Card::setDisplaySerialNumber(const QString &serialNumber) { mDisplaySerialNumber = serialNumber; } std::string Card::appName() const { return mAppName; } void Card::setAppName(const std::string &name) { mAppName = name; } void Card::setAppVersion(int version) { mAppVersion = version; } int Card::appVersion() const { return mAppVersion; } QString Card::displayAppVersion() const { return formatVersion(mAppVersion); } void Card::setManufacturer(const std::string &value) { if (!manufacturer().empty()) { qCDebug(KLEOPATRA_LOG) << "Card manufacturer is already set; overwriting existing value"; mCardInfo.erase("MANUFACTURER"); } mCardInfo.insert({"MANUFACTURER", value}); } std::string Card::manufacturer() const { return cardInfo("MANUFACTURER"); } std::string Card::cardType() const { return mCardType; } int Card::cardVersion() const { return mCardVersion; } QString Card::displayCardVersion() const { return formatVersion(mCardVersion); } QString Card::cardHolder() const { return mCardHolder; } void Card::setSigningKeyRef(const std::string &keyRef) { mSigningKeyRef = keyRef; } std::string Card::signingKeyRef() const { return mSigningKeyRef; } bool Card::hasSigningKey() const { return !keyInfo(mSigningKeyRef).grip.empty(); } void Card::setEncryptionKeyRef(const std::string &keyRef) { mEncryptionKeyRef = keyRef; } std::string Card::encryptionKeyRef() const { return mEncryptionKeyRef; } bool Card::hasEncryptionKey() const { return !keyInfo(mEncryptionKeyRef).grip.empty(); } void Card::setAuthenticationKeyRef(const std::string &keyRef) { mAuthenticationKeyRef = keyRef; } std::string Card::authenticationKeyRef() const { return mAuthenticationKeyRef; } bool Card::hasAuthenticationKey() const { return !keyInfo(mAuthenticationKeyRef).grip.empty(); } std::vector Card::pinStates() const { return mPinStates; } void Card::setPinStates(const std::vector &pinStates) { mPinStates = pinStates; } bool Card::hasNullPin() const { return mHasNullPin; } void Card::setHasNullPin(bool value) { mHasNullPin = value; } -bool Card::canLearnKeys() const -{ - return mCanLearn; -} - -void Card::setCanLearnKeys(bool value) -{ - mCanLearn = value; -} - bool Card::operator==(const Card &other) const { - return mCanLearn == other.mCanLearn && mHasNullPin == other.mHasNullPin && mStatus == other.mStatus && mSerialNumber == other.mSerialNumber - && mAppName == other.mAppName && mAppVersion == other.mAppVersion && mCardType == other.mCardType && mCardVersion == other.mCardVersion - && mCardHolder == other.mCardHolder && mSigningKeyRef == other.mSigningKeyRef && mEncryptionKeyRef == other.mEncryptionKeyRef - && mAuthenticationKeyRef == other.mAuthenticationKeyRef && mPinStates == other.mPinStates && mErrMsg == other.mErrMsg && mKeyInfos == other.mKeyInfos - && mCardInfo == other.mCardInfo; + return mHasNullPin == other.mHasNullPin && mStatus == other.mStatus && mSerialNumber == other.mSerialNumber && mAppName == other.mAppName + && mAppVersion == other.mAppVersion && mCardType == other.mCardType && mCardVersion == other.mCardVersion && mCardHolder == other.mCardHolder + && mSigningKeyRef == other.mSigningKeyRef && mEncryptionKeyRef == other.mEncryptionKeyRef && mAuthenticationKeyRef == other.mAuthenticationKeyRef + && mPinStates == other.mPinStates && mErrMsg == other.mErrMsg && mKeyInfos == other.mKeyInfos && mCardInfo == other.mCardInfo; } bool Card::operator!=(const Card &other) const { return !operator==(other); } void Card::setErrorMsg(const QString &msg) { mErrMsg = msg; } QString Card::errorMsg() const { return mErrMsg; } void Card::setInitialKeyInfos(const std::vector &infos) { mKeyInfos = infos; } const std::vector &Card::keyInfos() const { return mKeyInfos; } const KeyPairInfo &Card::keyInfo(const std::string &keyRef) const { static const KeyPairInfo nullKey; for (const KeyPairInfo &k : mKeyInfos) { if (k.keyRef == keyRef) { return k; } } return nullKey; } void Card::setCardInfo(const std::vector> &infos) { qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:"; for (const auto &pair : infos) { qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str(); parseCardInfo(pair.first, pair.second); } processCardInfo(); } namespace { static int parseHexEncodedVersionTuple(const std::string &s) { // s is a hex-encoded, unsigned int-packed version tuple, // i.e. each byte represents one part of the version tuple bool ok; const auto version = QByteArray::fromStdString(s).toUInt(&ok, 16); return ok ? version : -1; } } void Card::parseCardInfo(const std::string &name, const std::string &value) { if (name == "APPVERSION") { mAppVersion = parseHexEncodedVersionTuple(value); } else if (name == "CARDTYPE") { mCardType = value; } else if (name == "CARDVERSION") { mCardVersion = parseHexEncodedVersionTuple(value); } else if (name == "DISP-NAME") { auto list = QString::fromUtf8(QByteArray::fromStdString(value)).split(QStringLiteral("<<"), Qt::SkipEmptyParts); std::reverse(list.begin(), list.end()); mCardHolder = list.join(QLatin1Char(' ')).replace(QLatin1Char('<'), QLatin1Char(' ')); } else if (name == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(value); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(value); setStatus(Card::CardError); } else { updateKeyInfo(info); } } else if (name == "KEY-FPR") { // handle OpenPGP key fingerprints const auto values = QString::fromStdString(value).split(QLatin1Char(' ')); if (values.size() < 2) { qCWarning(KLEOPATRA_LOG) << "Invalid KEY-FPR status line" << QString::fromStdString(value); setStatus(Card::CardError); } const auto keyNumber = values[0]; const std::string keyRef = "OPENPGP." + keyNumber.toStdString(); const auto fpr = values[1].toStdString(); if (keyNumber == QLatin1Char('1') || keyNumber == QLatin1Char('2') || keyNumber == QLatin1Char('3')) { addCardInfo("KLEO-FPR-" + keyRef, fpr); } else { // Maybe more keyslots in the future? qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot" << keyNumber; } } else if (name == "MANUFACTURER") { // the value of MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range // for PKCS#15 cards the manufacturer ID is always 0, e.g. // 0 www.atos.net/cardos [R&S] const auto startOfManufacturerName = value.find(' '); if (startOfManufacturerName != std::string::npos) { addCardInfo(name, value.substr(startOfManufacturerName + 1)); } } else { mCardInfo.insert({name, value}); } } void Card::processCardInfo() { } void Card::addCardInfo(const std::string &name, const std::string &value) { mCardInfo.insert({name, value}); } std::string Card::cardInfo(const std::string &name) const { const auto range = mCardInfo.equal_range(name); return range.first != range.second ? range.first->second : std::string(); } void Card::updateKeyInfo(const KeyPairInfo &keyPairInfo) { for (KeyPairInfo &k : mKeyInfos) { if (k.keyRef == keyPairInfo.keyRef) { k.update(keyPairInfo); return; } } mKeyInfos.push_back(keyPairInfo); } std::string Card::keyFingerprint(const std::string &keyRef) const { return cardInfo("KLEO-FPR-" + keyRef); } diff --git a/src/smartcard/card.h b/src/smartcard/card.h index 504c28441..d4fb49c27 100644 --- a/src/smartcard/card.h +++ b/src/smartcard/card.h @@ -1,147 +1,143 @@ #pragma once /* smartcard/card.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 */ #include "keypairinfo.h" #include #include #include #include namespace Kleo { namespace SmartCard { /** Class representing an application on a smartcard or similar hardware token. */ class Card { public: enum PinState { UnknownPinState, NullPin, PinBlocked, NoPin, PinOk, NumPinStates }; enum Status { NoCard, CardPresent, CardActive, CardUsable, _NumScdStates, CardError = _NumScdStates, NumStates }; Card(); virtual ~Card(); virtual bool operator==(const Card &other) const; bool operator!=(const Card &other) const; void setStatus(Status s); Status status() const; void setSerialNumber(const std::string &sn); std::string serialNumber() const; void setCardInfo(const std::vector> &infos); QString displaySerialNumber() const; void setDisplaySerialNumber(const QString &sn); std::string appName() const; void setAppVersion(int version); int appVersion() const; QString displayAppVersion() const; void setManufacturer(const std::string &manufacturer); std::string manufacturer() const; std::string cardType() const; int cardVersion() const; QString displayCardVersion() const; QString cardHolder() const; void setSigningKeyRef(const std::string &keyRef); std::string signingKeyRef() const; bool hasSigningKey() const; void setEncryptionKeyRef(const std::string &keyRef); std::string encryptionKeyRef() const; bool hasEncryptionKey() const; void setAuthenticationKeyRef(const std::string &keyRef); std::string authenticationKeyRef() const; bool hasAuthenticationKey() const; std::vector pinStates() const; void setPinStates(const std::vector &pinStates); bool hasNullPin() const; void setHasNullPin(bool value); - bool canLearnKeys() const; - void setCanLearnKeys(bool value); - QString errorMsg() const; void setErrorMsg(const QString &msg); const std::vector &keyInfos() const; const KeyPairInfo &keyInfo(const std::string &keyRef) const; std::string keyFingerprint(const std::string &keyRef) const; protected: void setAppName(const std::string &name); void setInitialKeyInfos(const std::vector &infos); virtual void processCardInfo(); void addCardInfo(const std::string &name, const std::string &value); std::string cardInfo(const std::string &name) const; private: void parseCardInfo(const std::string &name, const std::string &value); void updateKeyInfo(const KeyPairInfo &keyPairInfo); private: - bool mCanLearn = false; bool mHasNullPin = false; Status mStatus = NoCard; std::string mSerialNumber; QString mDisplaySerialNumber; std::string mAppName; int mAppVersion = -1; std::string mCardType; int mCardVersion = -1; QString mCardHolder; std::string mSigningKeyRef; std::string mEncryptionKeyRef; std::string mAuthenticationKeyRef; std::vector mPinStates; QString mErrMsg; std::vector mKeyInfos; std::multimap mCardInfo; }; } // namespace Smartcard } // namespace Kleopatra diff --git a/src/smartcard/netkeycard.cpp b/src/smartcard/netkeycard.cpp index 18cc6c48c..e9eca3cc9 100644 --- a/src/smartcard/netkeycard.cpp +++ b/src/smartcard/netkeycard.cpp @@ -1,155 +1,152 @@ /* smartcard/netkeycard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "netkeycard.h" #include "keypairinfo.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; // static const std::string NetKeyCard::AppName = "nks"; namespace { static GpgME::Key lookup_key(GpgME::Context *ctx, const std::string &keyGrip) { if (!ctx || keyGrip.empty()) { return GpgME::Key(); } const std::string pattern = '&' + keyGrip; qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: pattern=" << pattern.c_str(); if (const auto err = ctx->startKeyListing(pattern.c_str())) { qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << Formatting::errorAsString(err); return GpgME::Key(); } GpgME::Error e; const auto key = ctx->nextKey(e); ctx->endKeyListing(); qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: e=" << e.code() << "; key.isNull()" << key.isNull(); return key; } } // namespace NetKeyCard::NetKeyCard(const Card &card) : Card(card) { setAppName(AppName); } // static std::string NetKeyCard::nksPinKeyRef() { return std::string("PW1.CH"); } // static std::string NetKeyCard::sigGPinKeyRef() { return std::string("PW1.CH.SIG"); } void NetKeyCard::processCardInfo() { setKeyPairInfo(keyInfos()); } void NetKeyCard::setKeyPairInfo(const std::vector &infos) { // check that any of the keys are new const std::unique_ptr klc(GpgME::Context::createForProtocol(GpgME::CMS)); if (!klc.get()) { return; } klc->setKeyListMode(GpgME::Ephemeral); klc->addKeyListMode(GpgME::Validate); - setCanLearnKeys(false); mKeys.clear(); for (const auto &info : infos) { const auto key = lookup_key(klc.get(), info.grip); - if (key.isNull()) { - setCanLearnKeys(true); - } else { + if (!key.isNull()) { mKeys.push_back(key); } } } // State 0 -> NKS PIN Retry counter // State 1 -> NKS PUK Retry counter // State 2 -> SigG PIN Retry counter // State 3 -> SigG PUK Retry counter bool NetKeyCard::hasNKSNullPin() const { const auto states = pinStates(); if (states.size() < 2) { qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size(); return false; } return states[0] == Card::NullPin; } bool NetKeyCard::hasSigGNullPin() const { const auto states = pinStates(); if (states.size() < 4) { qCWarning(KLEOPATRA_LOG) << "Invalid size of pin states:" << states.size(); return false; } return states[2] == Card::NullPin; } std::vector NetKeyCard::keys() const { return mKeys; } bool NetKeyCard::operator==(const Card &other) const { static const _detail::ByFingerprint keysHaveSameFingerprint; if (!Card::operator==(other)) { qCDebug(KLEOPATRA_LOG) << "NetKeyCard" << __func__ << "Card don't match"; return false; } const auto otherNetKeyCard = dynamic_cast(&other); if (!otherNetKeyCard) { qCWarning(KLEOPATRA_LOG) << "Failed to cast other card to NetKeyCard"; return false; } if (mKeys.size() != otherNetKeyCard->mKeys.size()) { qCDebug(KLEOPATRA_LOG) << "NetKeyCard" << __func__ << "Number of keys doesn't match"; return false; } const auto otherHasKey = [otherNetKeyCard](const GpgME::Key &key) { return Kleo::any_of(otherNetKeyCard->mKeys, [key](const GpgME::Key &otherKey) { return keysHaveSameFingerprint(key, otherKey); }); }; const bool result = Kleo::all_of(mKeys, otherHasKey); qCDebug(KLEOPATRA_LOG) << "NetKeyCard" << __func__ << "Keys match?" << result; return result; } diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index 2aaa22725..21219f79a 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,1221 +1,1201 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" #include "deviceinfowatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "netkeycard.h" #include "openpgpcard.h" #include "p15card.h" #include "pivcard.h" #include #include #include #include #include #include "utils/kdtoolsglobal.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1)) Q_DECLARE_METATYPE(GpgME::Error) namespace { static bool gpgHasMultiCardMultiAppSupport() { return !(engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.0"); } static QDebug operator<<(QDebug s, const std::vector> &v) { using pair = std::pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } struct CardApp { std::string serialNumber; std::string appName; }; static void logUnexpectedStatusLine(const std::pair &line, const std::string &prefix = std::string(), const std::string &command = std::string()) { qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString()) << "Unexpected status line" << (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1StringView(":")) << QString::fromStdString(line.first) << QString::fromStdString(line.second); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return Assuan::sendStatusCommand(gpgAgent, cmd.c_str(), err); } static const std::string getAttribute(std::shared_ptr &gpgAgent, const char *attribute, const char *versionHint) { Error err; const auto result = scd_getattr_status(gpgAgent, attribute, err); if (err) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "Querying for attribute" << attribute << "not yet supported; needs GnuPG" << versionHint; } else { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR " << attribute << " failed:" << err; } return std::string(); } return result; } enum GetCardsAndAppsOptions { WithReportedAppOrder, WithStableAppOrder, }; static std::vector getCardsAndApps(std::shared_ptr &gpgAgent, GetCardsAndAppsOptions options, Error &err) { std::vector result; if (gpgHasMultiCardMultiAppSupport()) { const std::string command = "SCD GETINFO all_active_apps"; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return result; } for (const auto &statusLine : statusLines) { if (statusLine.first == "SERIALNO") { const auto serialNumberAndApps = QByteArray::fromStdString(statusLine.second).split(' '); if (serialNumberAndApps.size() >= 2) { const auto serialNumber = serialNumberAndApps[0]; auto apps = serialNumberAndApps.mid(1); if (options == WithStableAppOrder) { // sort the apps to get a stable order independently of the currently selected application std::sort(apps.begin(), apps.end()); } for (const auto &app : apps) { qCDebug(KLEOPATRA_LOG) << "getCardsAndApps(): Found card" << serialNumber << "with app" << app; result.push_back({serialNumber.toStdString(), app.toStdString()}); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } } else { // use SCD SERIALNO to get the currently active card const auto serialNumber = Assuan::sendStatusCommand(gpgAgent, "SCD SERIALNO", err); if (err) { return result; } // use SCD GETATTR APPTYPE to find out which app is active auto appName = scd_getattr_status(gpgAgent, "APPTYPE", err); std::transform(appName.begin(), appName.end(), appName.begin(), [](unsigned char c) { return std::tolower(c); }); if (err) { return result; } result.push_back({serialNumber, appName}); } return result; } static std::string switchCard(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const std::string command = "SCD SWITCHCARD " + serialNumber; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) { return serialNumber; } qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber << ")"; return std::string(); } static std::string switchApp(std::shared_ptr &gpgAgent, const std::string &serialNumber, const std::string &appName, Error &err) { const std::string command = "SCD SWITCHAPP " + appName; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second.find(serialNumber + ' ' + appName) == 0) { return appName; } qCWarning(KLEOPATRA_LOG) << "switchApp():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber + ' ' + appName + "..." << ")"; return std::string(); } static std::vector getCardApps(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const auto cardApps = getCardsAndApps(gpgAgent, WithReportedAppOrder, err); if (err) { return {}; } std::vector apps; kdtools::transform_if( cardApps.begin(), cardApps.end(), std::back_inserter(apps), [](const auto &cardApp) { return cardApp.appName; }, [serialNumber](const auto &cardApp) { return cardApp.serialNumber == serialNumber; }); qCDebug(KLEOPATRA_LOG) << __func__ << "apps:" << apps; return apps; } static void switchCardBackToOpenPGPApp(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { if (!gpgHasMultiCardMultiAppSupport()) { return; } const auto apps = getCardApps(gpgAgent, serialNumber, err); if (err || apps.empty() || apps[0] == OpenPGPCard::AppName) { return; } if (Kleo::contains(apps, OpenPGPCard::AppName)) { switchApp(gpgAgent, serialNumber, OpenPGPCard::AppName, err); } } static const char *get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16) * 256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static std::vector get_openpgp_card_supported_algorithms_announced_by_card(std::shared_ptr &gpgAgent) { static constexpr std::string_view cardSlotPrefix = "OPENPGP.1 "; static const std::map algoMapping = { {"cv25519", "curve25519"}, {"cv448", "curve448"}, {"ed25519", "curve25519"}, {"ed448", "curve448"}, {"x448", "curve448"}, }; Error err; const auto lines = Assuan::sendStatusLinesCommand(gpgAgent, "SCD GETATTR KEY-ATTR-INFO", err); if (err) { return {}; } std::vector algos; kdtools::transform_if( lines.cbegin(), lines.cend(), std::back_inserter(algos), [](const auto &line) { auto algo = line.second.substr(cardSlotPrefix.size()); // map a few algorithms to the standard names used by us const auto mapping = algoMapping.find(algo); if (mapping != algoMapping.end()) { algo = mapping->second; } return algo; }, [](const auto &line) { // only consider KEY-ATTR-INFO status lines for the first card slot; // for now, we assume that all card slots support the same algorithms return line.first == "KEY-ATTR-INFO" && line.second.starts_with(cardSlotPrefix); }); // remove duplicate algorithms std::sort(algos.begin(), algos.end()); algos.erase(std::unique(algos.begin(), algos.end()), algos.end()); qCDebug(KLEOPATRA_LOG) << __func__ << "returns" << algos; return algos; } static std::vector get_openpgp_card_supported_algorithms(Card *card, std::shared_ptr &gpgAgent) { // first ask the smart card for the supported algorithms const std::vector announcedAlgos = get_openpgp_card_supported_algorithms_announced_by_card(gpgAgent); if (!announcedAlgos.empty()) { return announcedAlgos; } // otherwise, fall back to hard-coded lists if ((card->cardType() == "yubikey") && (card->cardVersion() >= 0x050203)) { return { "rsa2048", "rsa3072", "rsa4096", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "curve25519", }; } else if ((card->cardType() == "zeitcontrol") && (card->appVersion() >= 0x0304)) { return { "rsa2048", "rsa3072", "rsa4096", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", }; } return {"rsa2048", "rsa3072", "rsa4096"}; } static bool isOpenPGPCardSerialNumber(const std::string &serialNumber) { return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401"; } static const std::string getDisplaySerialNumber(std::shared_ptr &gpgAgent, Error &err) { const auto displaySerialNumber = scd_getattr_status(gpgAgent, "$DISPSERIALNO", err); if (err && err.code() != GPG_ERR_INV_NAME) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR $DISPSERIALNO failed:" << err; } return displaySerialNumber; } static void setDisplaySerialNumber(Card *card, std::shared_ptr &gpgAgent) { static const QRegularExpression leadingZeros(QStringLiteral("^0*")); Error err; const QString displaySerialNumber = QString::fromStdString(getDisplaySerialNumber(gpgAgent, err)); if (err) { card->setDisplaySerialNumber(QString::fromStdString(card->serialNumber())); return; } if (isOpenPGPCardSerialNumber(card->serialNumber()) && displaySerialNumber.size() == 12) { // add a space between manufacturer id and card id for OpenPGP cards card->setDisplaySerialNumber(displaySerialNumber.left(4) + QLatin1Char(' ') + displaySerialNumber.right(8)); } else { card->setDisplaySerialNumber(displaySerialNumber); } return; } static void learnCardKeyStubs(const Card *card, std::shared_ptr &gpg_agent) { for (const KeyPairInfo &keyInfo : card->keyInfos()) { if (!keyInfo.grip.empty()) { Error err; const auto command = std::string("READKEY --card --no-data -- ") + keyInfo.keyRef; (void)Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; } } } } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pgpCard = new OpenPGPCard(*ci); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err.code()) { ci->setStatus(Card::CardError); return; } pgpCard->setCardInfo(info); if (pgpCard->manufacturer().empty()) { // fallback in case MANUFACTURER is not yet included in the card info pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } setDisplaySerialNumber(pgpCard, gpg_agent); learnCardKeyStubs(pgpCard, gpg_agent); pgpCard->setSupportedAlgorithms(get_openpgp_card_supported_algorithms(pgpCard, gpg_agent)); ci.reset(pgpCard); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; const auto keyPairInfoLines = Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } // this adds the key algorithm (and the key creation date, but that seems to be unset for PIV) to the existing key pair information pivCard->setCardInfo(keyPairInfoLines); } static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READCERT ") + keyRef; const std::string certificateData = Assuan::sendDataCommand(gpg_agent, command.c_str(), err); if (err && err.code() != GPG_ERR_NOT_FOUND) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } if (certificateData.empty()) { qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card"; return; } qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card"; pivCard->setCertificateData(keyRef, certificateData); } static void handle_piv_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pivCard = new PIVCard(*ci); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); setDisplaySerialNumber(pivCard, gpg_agent); for (const KeyPairInfo &keyInfo : pivCard->keyInfos()) { if (!keyInfo.grip.empty()) { readKeyPairInfoFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); } } learnCardKeyStubs(pivCard, gpg_agent); ci.reset(pivCard); } static void handle_p15_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto p15Card = new P15Card(*ci); auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } const auto fprs = Assuan::sendStatusLinesCommand(gpg_agent, "SCD GETATTR KEY-FPR", err); if (!err) { info.insert(info.end(), fprs.begin(), fprs.end()); } p15Card->setCardInfo(info); learnCardKeyStubs(p15Card, gpg_agent); setDisplaySerialNumber(p15Card, gpg_agent); ci.reset(p15Card); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(*ci); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR NKS-VERSION failed:" << err; ci->setErrorMsg(QStringLiteral("NKS-VERSION failed: ") + Formatting::errorAsString(err)); return; } if (ci->appVersion() < 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 (or later) card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } setDisplaySerialNumber(nkCard, gpg_agent); // the following only works for NKS v3... const auto chvStatus = QString::fromStdString(scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "Running SCD GETATTR CHV-STATUS failed:" << err; ci->setErrorMsg(QStringLiteral("CHV-Status failed: ") + Formatting::errorAsString(err)); return; } std::vector states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state : chvStatus) { const auto parsed = parse_pin_state(state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } nkCard->setCardInfo(info); learnCardKeyStubs(nkCard, gpg_agent); } static std::shared_ptr get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr(new Card()); if (gpgHasMultiCardMultiAppSupport()) { // select card Error err; const auto result = switchCard(gpg_agent, serialNumber, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching card failed"; ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); } else { ci->setStatus(Card::CardPresent); } if (gpgHasMultiCardMultiAppSupport()) { // select app Error err; const auto result = switchApp(gpg_agent, serialNumber, appName, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching app failed"; ci->setStatus(Card::CardError); return ci; } } ci->setSerialNumber(serialNumber); ci->setSigningKeyRef(getAttribute(gpg_agent, "$SIGNKEYID", "2.2.18")); ci->setEncryptionKeyRef(getAttribute(gpg_agent, "$ENCRKEYID", "2.2.18")); // Handle different card types if (appName == NetKeyCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); } else if (appName == OpenPGPCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; ci->setAuthenticationKeyRef(OpenPGPCard::pgpAuthKeyRef()); handle_openpgp_card(ci, gpg_agent); } else if (appName == PIVCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); } else if (appName == P15Card::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found P15 card" << ci->serialNumber().c_str() << "end"; handle_p15_card(ci, gpg_agent); } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName; } if (gpgHasMultiCardMultiAppSupport() && appName != OpenPGPCard::AppName) { // switch the card app back to OpenPGP; errors are ignored GpgME::Error dummy; switchCardBackToOpenPGPApp(gpg_agent, serialNumber, dummy); } return ci; } static bool isCardNotPresentError(const GpgME::Error &err) { // see fixup_scd_errors() in gpg-card.c return err && ((err.code() == GPG_ERR_CARD_NOT_PRESENT) || ((err.code() == GPG_ERR_ENODEV || err.code() == GPG_ERR_CARD_REMOVED) && (err.sourceID() == GPG_ERR_SOURCE_SCD))); } static std::vector> update_cardinfo(std::shared_ptr &gpgAgent) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo()"; // ensure that a card is present and that all cards are properly set up { Error err; const char *command = (gpgHasMultiCardMultiAppSupport()) ? "SCD SERIALNO --all" : "SCD SERIALNO"; const std::string serialno = Assuan::sendStatusCommand(gpgAgent, command, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector>(); } else { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } } Error err; const std::vector cardApps = getCardsAndApps(gpgAgent, WithStableAppOrder, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector>(); } else { qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } std::vector> cards; for (const auto &cardApp : cardApps) { const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); cards.push_back(card); } return cards; } } // namespace struct Transaction { CardApp cardApp; QByteArray command; QPointer receiver; ReaderStatus::TransactionFunc slot; AssuanTransaction *assuanTransaction; }; static const Transaction updateTransaction = {{"__all__", "__all__"}, "__update__", nullptr, nullptr, nullptr}; static const Transaction quitTransaction = {{"__all__", "__all__"}, "__quit__", nullptr, nullptr, nullptr}; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent) , m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector> cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); - void anyCardCanLearnKeysChanged(bool); void cardAdded(const std::string &serialNumber, const std::string &appName); void cardChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); void updateFinished(); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void deviceStatusChanged(const QByteArray &details) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")"; addTransaction(updateTransaction); } void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); for (const Transaction &t : std::as_const(ft)) if (t.receiver && t.slot) { QMetaObject::invokeMethod( t.receiver, [&t, &err]() { t.slot(err); }, Qt::DirectConnection); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; CardApp cardApp; QByteArray command; bool nullSlot = false; AssuanTransaction *assuanTransaction = nullptr; std::list item; std::vector> oldCards; while (!KeyCache::instance()->initialized()) { qCDebug(KLEOPATRA_LOG) << "Waiting for Keycache to be initialized."; sleep(1); } Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: cardApp = item.front().cardApp; command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { bool anyError = false; if (cardApp.serialNumber == "__all__" || cardApp.appName == "__all__") { std::vector> newCards = update_cardinfo(gpgAgent); KDAB_SYNCHRONIZED(m_mutex) { m_cardInfos = newCards; } - bool anyLC = false; std::string firstCardWithNullPin; for (const auto &newCard : newCards) { const auto serialNumber = newCard->serialNumber(); const auto appName = newCard->appName(); const auto matchingOldCard = std::find_if(oldCards.cbegin(), oldCards.cend(), [serialNumber, appName](const std::shared_ptr &card) { return card->serialNumber() == serialNumber && card->appName() == appName; }); if (matchingOldCard == oldCards.cend()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added"; Q_EMIT cardAdded(serialNumber, appName); } else { if (*newCard != **matchingOldCard) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed"; Q_EMIT cardChanged(serialNumber, appName); } oldCards.erase(matchingOldCard); } - if (newCard->canLearnKeys()) { - anyLC = true; - } if (newCard->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = newCard->serialNumber(); } if (newCard->status() == Card::CardError) { anyError = true; } } for (const auto &oldCard : oldCards) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << oldCard->serialNumber() << "with app" << oldCard->appName() << "was removed"; Q_EMIT cardRemoved(oldCard->serialNumber(), oldCard->appName()); } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); - Q_EMIT anyCardCanLearnKeysChanged(anyLC); } else { auto updatedCard = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); const auto serialNumber = updatedCard->serialNumber(); const auto appName = updatedCard->appName(); bool cardWasAdded = false; bool cardWasChanged = false; KDAB_SYNCHRONIZED(m_mutex) { const auto matchingCard = std::find_if(m_cardInfos.begin(), m_cardInfos.end(), [serialNumber, appName](const auto &card) { return card->serialNumber() == serialNumber && card->appName() == appName; }); if (matchingCard == m_cardInfos.end()) { m_cardInfos.push_back(updatedCard); cardWasAdded = true; } else { cardWasChanged = (*updatedCard != **matchingCard); m_cardInfos[std::distance(m_cardInfos.begin(), matchingCard)] = updatedCard; } if (updatedCard->status() == Card::CardError) { anyError = true; } } if (cardWasAdded) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added"; Q_EMIT cardAdded(serialNumber, appName); } else if (cardWasChanged) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed"; Q_EMIT cardChanged(serialNumber, appName); } } if (anyError) { gpgAgent.reset(); } Q_EMIT updateFinished(); } else { GpgME::Error err; if (gpgHasMultiCardMultiAppSupport()) { switchCard(gpgAgent, cardApp.serialNumber, err); if (!err) { switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err); } } if (!err) { if (assuanTransaction) { (void)Assuan::sendCommand(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)Assuan::sendCommand(gpgAgent, command.constData(), err); } } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; // protected by m_mutex: std::vector> m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq) , q(qq) , watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); connect(this, &::ReaderStatusThread::cardAdded, q, &ReaderStatus::cardAdded); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::cardRemoved, q, &ReaderStatus::cardRemoved); connect(this, &::ReaderStatusThread::updateFinished, q, &ReaderStatus::updateFinished); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); - connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); if (DeviceInfoWatcher::isSupported()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher"; connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged); } else { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher"; watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } } ~Private() override { stop(); if (!wait(100)) { terminate(); wait(); } } private: std::string firstCardWithNullPinImpl() const { const auto cis = cardInfos(); const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->hasNullPin(); }); return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string(); } - bool anyCardCanLearnKeysImpl() const - { - const auto cis = cardInfos(); - return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { - return ci->canLearnKeys(); - }); - } - private: FileSystemWatcher watcher; DeviceInfoWatcher devInfoWatcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent) , d(new Private(this)) { self = this; qRegisterMetaType("std::string"); } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); if (DeviceInfoWatcher::isSupported()) { connect(&d->devInfoWatcher, &DeviceInfoWatcher::startOfGpgAgentRequested, this, &ReaderStatus::startOfGpgAgentRequested); d->devInfoWatcher.start(); } } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } std::string ReaderStatus::firstCardWithNullPin() const { return d->firstCardWithNullPinImpl(); } -bool ReaderStatus::anyCardCanLearnKeys() const -{ - return d->anyCardCanLearnKeysImpl(); -} - void ReaderStatus::startSimpleTransaction(const std::shared_ptr &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot) { const CardApp cardApp = {card->serialNumber(), card->appName()}; const Transaction t = {cardApp, command, receiver, slot, nullptr}; d->addTransaction(t); } void ReaderStatus::startTransaction(const std::shared_ptr &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot, std::unique_ptr transaction) { const CardApp cardApp = {card->serialNumber(), card->appName()}; const Transaction t = {cardApp, command, receiver, slot, transaction.release()}; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } void ReaderStatus::updateCard(const std::string &serialNumber, const std::string &appName) { const CardApp cardApp = {serialNumber, appName}; const Transaction t = {cardApp, updateTransaction.command, nullptr, nullptr, nullptr}; d->addTransaction(t); } std::vector> ReaderStatus::getCards() const { return d->cardInfos(); } std::shared_ptr ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const { for (const auto &card : d->cardInfos()) { if (card->serialNumber() == serialNumber && card->appName() == appName) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName; return card; } } qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName; return std::shared_ptr(); } // static std::string ReaderStatus::switchCard(std::shared_ptr &ctx, const std::string &serialNumber, Error &err) { return ::switchCard(ctx, serialNumber, err); } // static std::string ReaderStatus::switchApp(std::shared_ptr &ctx, const std::string &serialNumber, const std::string &appName, Error &err) { return ::switchApp(ctx, serialNumber, appName, err); } // static Error ReaderStatus::switchCardAndApp(const std::string &serialNumber, const std::string &appName) { Error err; if (!(engineInfo(GpgEngine).engineVersion() < "2.3.0")) { std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(c.release()); const auto resultSerialNumber = switchCard(assuanContext, serialNumber, err); if (err || resultSerialNumber != serialNumber) { qCWarning(KLEOPATRA_LOG) << "Switching to card" << QString::fromStdString(serialNumber) << "failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } const auto resultAppName = switchApp(assuanContext, serialNumber, appName, err); if (err || resultAppName != appName) { qCWarning(KLEOPATRA_LOG) << "Switching card to" << QString::fromStdString(appName) << "app failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } } return err; } // static Error ReaderStatus::switchCardBackToOpenPGPApp(const std::string &serialNumber) { Error err; if (gpgHasMultiCardMultiAppSupport()) { std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(c.release()); ::switchCardBackToOpenPGPApp(assuanContext, serialNumber, err); } return err; } #include "readerstatus.moc" #include "moc_readerstatus.cpp" diff --git a/src/smartcard/readerstatus.h b/src/smartcard/readerstatus.h index 541d277a2..718962ba2 100644 --- a/src/smartcard/readerstatus.h +++ b/src/smartcard/readerstatus.h @@ -1,91 +1,89 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include "card.h" #include #include namespace GpgME { class AssuanTransaction; class Context; class Error; } namespace Kleo { namespace SmartCard { class ReaderStatus : public QObject { Q_OBJECT public: explicit ReaderStatus(QObject *parent = nullptr); ~ReaderStatus() override; static const ReaderStatus *instance(); static ReaderStatus *mutableInstance(); using TransactionFunc = std::function; void startSimpleTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const TransactionFunc &slot); void startTransaction(const std::shared_ptr &card, const QByteArray &cmd, QObject *receiver, const TransactionFunc &slot, std::unique_ptr transaction); Card::Status cardStatus(unsigned int slot) const; std::string firstCardWithNullPin() const; - bool anyCardCanLearnKeys() const; std::vector> getCards() const; std::shared_ptr getCard(const std::string &serialNumber, const std::string &appName) const; template std::shared_ptr getCard(const std::string &serialNumber) const { return std::dynamic_pointer_cast(getCard(serialNumber, T::AppName)); } static std::string switchCard(std::shared_ptr &ctx, const std::string &serialNumber, GpgME::Error &err); static std::string switchApp(std::shared_ptr &ctx, const std::string &serialNumber, const std::string &appName, GpgME::Error &err); static GpgME::Error switchCardAndApp(const std::string &serialNumber, const std::string &appName); static GpgME::Error switchCardBackToOpenPGPApp(const std::string &serialNumber); public Q_SLOTS: void updateStatus(); void updateCard(const std::string &serialNumber, const std::string &appName); void startMonitoring(); Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); - void anyCardCanLearnKeysChanged(bool); void cardAdded(const std::string &serialNumber, const std::string &appName); void cardChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); void updateFinished(); void startOfGpgAgentRequested(); private: class Private; std::shared_ptr d; }; } // namespace SmartCard } // namespace Kleo Q_DECLARE_METATYPE(Kleo::SmartCard::Card::Status) diff --git a/src/systrayicon.cpp b/src/systrayicon.cpp index 5622f1fa8..03990221b 100644 --- a/src/systrayicon.cpp +++ b/src/systrayicon.cpp @@ -1,258 +1,222 @@ /* -*- mode: c++; c-basic-offset:4 -*- systemtrayicon.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 "systrayicon.h" #ifndef QT_NO_SYSTEMTRAYICON #include "kleopatraapplication.h" #include "mainwindow.h" #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; class SysTrayIcon::Private { friend class ::SysTrayIcon; SysTrayIcon *const q; public: explicit Private(SysTrayIcon *qq); ~Private(); private: void slotAbout() { if (!aboutDialog) { aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData()); aboutDialog->setAttribute(Qt::WA_DeleteOnClose); } if (aboutDialog->isVisible()) { aboutDialog->raise(); } else { aboutDialog->show(); } } void enableDisableActions() { openCertificateManagerAction.setEnabled(!q->mainWindow() || !q->mainWindow()->isVisible()); setInitialPinAction.setEnabled(!firstCardWithNullPin.empty()); - learnCertificatesAction.setEnabled(anyCardCanLearnKeys); - q->setAttentionWanted((!firstCardWithNullPin.empty() || anyCardCanLearnKeys) && !q->attentionWindow()); + q->setAttentionWanted(!firstCardWithNullPin.empty() && !q->attentionWindow()); } void slotSetInitialPin() { if (!firstCardWithNullPin.empty()) { auto cmd = new SetInitialPinCommand(firstCardWithNullPin); q->setAttentionWindow(cmd->dialog()); startCommand(cmd); } } - void slotLearnCertificates() - { - auto cmd = new LearnCardKeysCommand(GpgME::CMS); - q->setAttentionWindow(cmd->dialog()); - startCommand(cmd); - } void startCommand(Command *cmd) { Q_ASSERT(cmd); cmd->setParent(q->mainWindow()); cmd->start(); } private: std::string firstCardWithNullPin; - bool anyCardCanLearnKeys = false; - bool learningInProgress = false; QMenu menu; QAction openCertificateManagerAction; QAction configureAction; QAction aboutAction; QAction quitAction; ClipboardMenu clipboardMenu; QMenu cardMenu; QAction updateCardStatusAction; QAction setInitialPinAction; - QAction learnCertificatesAction; QPointer aboutDialog; QEventLoopLocker eventLoopLocker; }; SysTrayIcon::Private::Private(SysTrayIcon *qq) : q(qq) , menu() , openCertificateManagerAction(i18nc("@action:inmenu", "&Open Certificate Manager..."), q) , configureAction(QIcon::fromTheme(QStringLiteral("configure")), xi18nc("@action:inmenu", "&Configure %1...", KAboutData::applicationData().displayName()), q) , aboutAction(QIcon::fromTheme(QStringLiteral("kleopatra")), xi18nc("@action:inmenu", "&About %1...", KAboutData::applicationData().displayName()), q) , quitAction(QIcon::fromTheme(QStringLiteral("application-exit")), xi18nc("@action:inmenu", "&Shutdown %1", KAboutData::applicationData().displayName()), q) , clipboardMenu(q) , cardMenu(i18nc("@title:menu", "SmartCard")) , updateCardStatusAction(i18nc("@action:inmenu", "Update Card Status"), q) , setInitialPinAction(i18nc("@action:inmenu", "Set NetKey v3 Initial PIN..."), q) - , learnCertificatesAction(i18nc("@action:inmenu", "Learn NetKey v3 Card Certificates"), q) , aboutDialog() { q->setNormalIcon(QIcon::fromTheme(QStringLiteral("kleopatra"))); q->setAttentionIcon(QIcon::fromTheme(QStringLiteral("auth-sim-locked"))); KDAB_SET_OBJECT_NAME(menu); KDAB_SET_OBJECT_NAME(openCertificateManagerAction); KDAB_SET_OBJECT_NAME(configureAction); KDAB_SET_OBJECT_NAME(aboutAction); KDAB_SET_OBJECT_NAME(quitAction); KDAB_SET_OBJECT_NAME(clipboardMenu); KDAB_SET_OBJECT_NAME(cardMenu); KDAB_SET_OBJECT_NAME(setInitialPinAction); - KDAB_SET_OBJECT_NAME(learnCertificatesAction); connect(&openCertificateManagerAction, SIGNAL(triggered()), qApp, SLOT(openOrRaiseMainWindow())); connect(&configureAction, SIGNAL(triggered()), qApp, SLOT(openOrRaiseConfigDialog())); connect(&aboutAction, SIGNAL(triggered()), q, SLOT(slotAbout())); connect(&quitAction, &QAction::triggered, QCoreApplication::instance(), &QCoreApplication::quit); connect(&updateCardStatusAction, &QAction::triggered, ReaderStatus::instance(), &ReaderStatus::updateStatus); connect(&setInitialPinAction, SIGNAL(triggered()), q, SLOT(slotSetInitialPin())); - connect(&learnCertificatesAction, SIGNAL(triggered()), q, SLOT(slotLearnCertificates())); menu.addAction(&openCertificateManagerAction); menu.addAction(&configureAction); menu.addAction(&aboutAction); menu.addSeparator(); menu.addMenu(clipboardMenu.clipboardMenu()->menu()); menu.addSeparator(); menu.addMenu(&cardMenu); cardMenu.addAction(&updateCardStatusAction); cardMenu.addAction(&setInitialPinAction); - cardMenu.addAction(&learnCertificatesAction); menu.addSeparator(); menu.addAction(&quitAction); q->setContextMenu(&menu); clipboardMenu.setMainWindow(q->mainWindow()); } SysTrayIcon::Private::~Private() { } SysTrayIcon::SysTrayIcon(QObject *p) : SystemTrayIcon(p) , d(new Private(this)) { slotEnableDisableActions(); } SysTrayIcon::~SysTrayIcon() { } MainWindow *SysTrayIcon::mainWindow() const { return static_cast(SystemTrayIcon::mainWindow()); } QDialog *SysTrayIcon::attentionWindow() const { return static_cast(SystemTrayIcon::attentionWindow()); } void SysTrayIcon::doActivated() { if (const QWidget *const aw = attentionWindow()) if (aw->isVisible()) { return; // ignore clicks while an attention window is open. } if (!d->firstCardWithNullPin.empty()) { d->slotSetInitialPin(); - } else if (d->anyCardCanLearnKeys) { - d->slotLearnCertificates(); } else { // Toggle visibility of MainWindow KleopatraApplication::instance()->toggleMainWindowVisibility(); } } void SysTrayIcon::setFirstCardWithNullPin(const std::string &serialNumber) { if (d->firstCardWithNullPin == serialNumber) { return; } d->firstCardWithNullPin = serialNumber; slotEnableDisableActions(); } -void SysTrayIcon::setAnyCardCanLearnKeys(bool on) -{ - if (d->anyCardCanLearnKeys == on || d->learningInProgress) { - return; - } - d->anyCardCanLearnKeys = on; - slotEnableDisableActions(); -} - void SysTrayIcon::slotEnableDisableActions() { d->enableDisableActions(); } -/* We need this as the readerstatus might update even - * while the loading is in progress. */ -void SysTrayIcon::setLearningInProgress(bool value) -{ - if (value) { - setAnyCardCanLearnKeys(false); - } - d->learningInProgress = value; -} - #include "moc_systrayicon.cpp" #endif // QT_NO_SYSTEMTRAYICON diff --git a/src/systrayicon.h b/src/systrayicon.h index 82b7efc6d..3a186ece1 100644 --- a/src/systrayicon.h +++ b/src/systrayicon.h @@ -1,48 +1,44 @@ /* -*- mode: c++; c-basic-offset:4 -*- systrayicon.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 */ #pragma once #include #ifndef QT_NO_SYSTEMTRAYICON #include class MainWindow; class QDialog; class SysTrayIcon : public Kleo::SystemTrayIcon { Q_OBJECT public: explicit SysTrayIcon(QObject *parent = nullptr); ~SysTrayIcon() override; MainWindow *mainWindow() const; QDialog *attentionWindow() const; - void setLearningInProgress(bool value); - public Q_SLOTS: void setFirstCardWithNullPin(const std::string &serialNumber); - void setAnyCardCanLearnKeys(bool); private: void doActivated() override; void slotEnableDisableActions() override; private: class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotAbout()) Q_PRIVATE_SLOT(d, void slotSetInitialPin()) - Q_PRIVATE_SLOT(d, void slotLearnCertificates()) }; #endif // QT_NO_SYSTEMTRAYICON diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index fd200c578..772717566 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,1022 +1,1019 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keylistcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "kleopatra_debug.h" #include "tooltippreferences.h" #include #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #include "commands/adduseridcommand.h" #include "commands/certifycertificatecommand.h" #include "commands/changeexpirycommand.h" #include "commands/changeownertrustcommand.h" #include "commands/changepassphrasecommand.h" #include "commands/changeroottrustcommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/clearcrlcachecommand.h" #include "commands/creategroupcommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/deletecertificatescommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/dumpcrlcachecommand.h" #include "commands/exportpaperkeycommand.h" #include "commands/exportsecretkeycommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/importcrlcommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/newcertificatesigningrequestcommand.h" #include "commands/newopenpgpcertificatecommand.h" #include "commands/refreshopenpgpcertscommand.h" #include "commands/refreshx509certscommand.h" #include "commands/reloadkeyscommand.h" #include "commands/revokecertificationcommand.h" #include "commands/revokekeycommand.h" #include "commands/signencryptfilescommand.h" #include "commands/signencryptfoldercommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #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 slotActionTriggered(QAction *action); 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; std::vector m_connections; }; KeyListController::Private::Private(KeyListController *qq) : q(qq) , actions() , views() , commands() , parentWidget() , tabWidget() , flatModel() , hierarchicalModel() { } KeyListController::Private::~Private() { } KeyListController::KeyListController(QObject *p) : QObject(p) , d(new Private(this)) { } KeyListController::~KeyListController() { } 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->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; if (model) { 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; } void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); m_connections.reserve(3); m_connections.push_back(connect(tabWidget, &TabWidget::viewAdded, q, &KeyListController::addView)); m_connections.push_back(connect(tabWidget, &TabWidget::viewAboutToBeRemoved, q, &KeyListController::removeView)); m_connections.push_back(connect(tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) { slotCurrentViewChanged(view); })); } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } for (const auto &connection : m_connections) { disconnect(connection); } m_connections.clear(); 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 std::vector common_and_openpgp_action_data = { // File menu { "file_new_certificate", i18n("New OpenPGP Key Pair..."), i18n("Create a new OpenPGP certificate"), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), }, { "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"), }, #ifdef MAILAKONADI_ENABLED { "file_export_certificate_to_provider", i18n("Publish at Mail Provider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"), "view-certificate-export", nullptr, nullptr, QString(), }, #endif // MAILAKONADI_ENABLED { "file_export_secret_keys", i18n("Backup Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), "folder-edit-sign-encrypt-symbolic", nullptr, nullptr, QString(), }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr /*"document-checksum-create"*/, nullptr, nullptr, QString(), }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr /*"document-checksum-verify"*/, nullptr, nullptr, QString(), }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, [this](bool) { cancelCommands(); }, QStringLiteral("Escape"), RegularQAction, Disabled, }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), }, // Certificate menu { "certificates_revoke", i18n("Revoke Certificate..."), i18n("Revoke the selected OpenPGP certificate"), "view-certificate-revoke", nullptr, nullptr, {}, }, { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), }, { "certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"), "view-certificate-revoke", nullptr, nullptr, QString(), }, { "certificates_change_expiry", i18n("Change End of Validity Period..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_change_owner_trust", i18nc("@action:inmenu", "Change Certification Power..."), i18nc("@info:tooltip", "Grant or revoke the certification power of the selected certificate"), nullptr, nullptr, nullptr, QString(), }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_add_userid", i18n("Add User ID..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_create_group", i18nc("@action:inmenu", "Create Group..."), i18nc("@info:tooltip", "Create a group from the selected certificates"), "resource-group-new", nullptr, nullptr, QString(), }, // Tools menu { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; static const action_data cms_create_csr_action_data = { "file_new_certificate_signing_request", i18n("New S/MIME Certification Request..."), i18n("Create a new S/MIME certificate signing request (CSR)"), "view-certificate-add", nullptr, nullptr, {}, }; static const std::vector cms_action_data = { // Certificate menu { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), }, }; std::vector action_data = common_and_openpgp_action_data; if (const Kleo::Settings settings{}; settings.cmsEnabled()) { if (settings.cmsCertificateCreationAllowed()) { action_data.push_back(cms_create_csr_action_data); } action_data.reserve(action_data.size() + cms_action_data.size()); std::copy(std::begin(cms_action_data), std::end(cms_action_data), std::back_inserter(action_data)); } 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_new_certificate_signing_request"))); //--- 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"))); #ifdef MAILAKONADI_ENABLED registerActionForCommand(coll->action(QStringLiteral("file_export_certificate_to_provider"))); #endif // MAILAKONADI_ENABLED //--- 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"))); if (RevokeCertificationCommand::isSupported()) { registerActionForCommand(coll->action(QStringLiteral("certificates_revoke_certification"))); } //--- 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_create_group"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_revoke"))); 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"))); 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, &QAction::triggered, this, [this, action]() { d->slotActionTriggered(action); }); 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, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(view, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) { slotDoubleClicked(index); }); connect(view, &QAbstractItemView::activated, q, [this](const QModelIndex &index) { slotActivated(index); }); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &oldSel, const QItemSelection &newSel) { slotSelectionChanged(oldSel, newSel); }); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) { slotContextMenu(pos); }); } void KeyListController::Private::connectCommand(Command *cmd) { if (!cmd) { return; } connect(cmd, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(cmd, &Command::finished, q, [this] { slotCommandFinished(); }); // connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) ); connect(cmd, &Command::progress, q, &KeyListController::progress); } 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; } if (const auto *const keyListModel = dynamic_cast(view->model())) { DetailsCommand *const c = new DetailsCommand{keyListModel->key(idx)}; c->setParentWidget(parentWidget ? parentWidget : view); 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); for (const Private::action_item &ai : std::as_const(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 GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2 // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret(); }; #else // older versions of GpgME did not always set the secret flag for card keys const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret() || k.subkey(0).isCardKey(); }; #endif if (std::all_of(keys.cbegin(), keys.cend(), primaryKeyCanBeUsedForSecretKeyOperations)) { result |= Command::NeedSecretKey; } if (std::all_of(std::begin(keys), std::end(keys), [](const auto &k) { return k.subkey(0).isSecret() && !k.subkey(0).isCardKey(); })) { result |= Command::NeedSecretKeyData; } 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 (Kleo::all_of(keys, [](const auto &key) { return !key.isBad(); })) { result |= Command::MustBeValid; } if (all_secret_are_not_owner_trust_ultimate(keys)) { result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate; } result |= find_root_restrictions(keys); if (const ReaderStatus *rs = ReaderStatus::instance()) { if (!rs->firstCardWithNullPin().empty()) { result |= Command::AnyCardHasNullPin; } - if (rs->anyCardCanLearnKeys()) { - result |= Command::AnyCardCanLearnKeys; - } } return result; } void KeyListController::Private::slotActionTriggered(QAction *sender) { const auto it = std::find_if(actions.cbegin(), actions.cend(), [sender](const action_item &item) { return item.action == 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(sender->objectName()) << "\""; else { qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(sender->objectName()); } } 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" diff --git a/src/view/netkeywidget.cpp b/src/view/netkeywidget.cpp index 0b0de2874..14404ab50 100644 --- a/src/view/netkeywidget.cpp +++ b/src/view/netkeywidget.cpp @@ -1,324 +1,317 @@ /* view/netkeywidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "netkeywidget.h" #include "keytreeview.h" #include "kleopatraapplication.h" #include "nullpinwidget.h" #include "systrayicon.h" #include "kleopatra_debug.h" #include "smartcard/netkeycard.h" #include "smartcard/readerstatus.h" #include "commands/changepincommand.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" #include "commands/detailscommand.h" #include "commands/learncardkeyscommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; using namespace Kleo::Commands; NetKeyWidget::NetKeyWidget(QWidget *parent) : QWidget(parent) , mSerialNumberLabel(new QLabel(this)) , mVersionLabel(new QLabel(this)) , mErrorLabel(new QLabel(this)) , mNullPinWidget(new NullPinWidget(this)) , mChangeNKSPINBtn(new QPushButton(this)) , mChangeSigGPINBtn(new QPushButton(this)) , mTreeView(new KeyTreeView(this)) , mArea(new QScrollArea) { auto vLay = new QVBoxLayout; // Set up the scroll are mArea->setFrameShape(QFrame::NoFrame); mArea->setWidgetResizable(true); auto mAreaWidget = new QWidget; mAreaWidget->setLayout(vLay); mArea->setWidget(mAreaWidget); auto scrollLay = new QVBoxLayout(this); scrollLay->setContentsMargins(0, 0, 0, 0); scrollLay->addWidget(mArea); // Add general widgets mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft); mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto hLay1 = new QHBoxLayout; hLay1->addWidget(new QLabel(i18n("Serial number:"))); hLay1->addWidget(mSerialNumberLabel); hLay1->addStretch(1); vLay->addLayout(hLay1); vLay->addWidget(mNullPinWidget); auto line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); vLay->addWidget(line1); vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Certificates:"))), 0, Qt::AlignLeft); mErrorLabel->setVisible(false); vLay->addWidget(mErrorLabel); // The certificate view mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView)); mTreeView->setHierarchicalView(true); connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &idx) { const auto klm = dynamic_cast(mTreeView->view()->model()); if (!klm) { qCDebug(KLEOPATRA_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className(); return; } auto cmd = new DetailsCommand(klm->key(idx)); cmd->setParentWidget(this); cmd->start(); }); vLay->addWidget(mTreeView); // The action area auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); vLay->addWidget(line2); vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), 0, Qt::AlignLeft); auto actionLayout = new QHBoxLayout(); if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) { mKeyForCardKeysButton = new QPushButton(this); mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key")); mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card.")); actionLayout->addWidget(mKeyForCardKeysButton); connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys); } if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184 mCreateCSRButton = new QPushButton(this); mCreateCSRButton->setText(i18nc("@action:button", "Create CSR")); mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card.")); mCreateCSRButton->setEnabled(false); actionLayout->addWidget(mCreateCSRButton); connect(mCreateCSRButton, &QPushButton::clicked, this, [this]() { createCSR(); }); } mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN")); mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this]() { doChangePin(NetKeyCard::nksPinKeyRef()); }); connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this]() { doChangePin(NetKeyCard::sigGPinKeyRef()); }); actionLayout->addWidget(mChangeNKSPINBtn); actionLayout->addWidget(mChangeSigGPINBtn); actionLayout->addStretch(1); vLay->addLayout(actionLayout); vLay->addStretch(1); const KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("NetKeyCardView")); mTreeView->restoreLayout(configGroup); } NetKeyWidget::~NetKeyWidget() = default; namespace { std::vector getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard) { if (netKeyCard->hasNKSNullPin()) { return {}; } std::vector keys; Kleo::copy_if(netKeyCard->keyInfos(), std::back_inserter(keys), [](const auto &keyInfo) { if (keyInfo.keyRef.substr(0, 9) == "NKS-SIGG.") { // SigG certificates for qualified signatures are issued with the physical cards; // it's not possible to request a certificate for them return false; } return keyInfo.canSign() // && (keyInfo.keyRef.substr(0, 9) == "NKS-NKS3.") // && DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm); }); return keys; } } void NetKeyWidget::setCard(const NetKeyCard *card) { mSerialNumber = card->serialNumber(); mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion())); mSerialNumberLabel->setText(card->displaySerialNumber()); mNullPinWidget->setSerialNumber(mSerialNumber); /* According to users of NetKey Cards it is fairly uncommon * to use SigG Certificates at all. So it should be optional to set the pins. */ mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/); mNullPinWidget->setSigGVisible(false /*card->hasSigGNullPin()*/); mNullPinWidget->setNKSVisible(card->hasNKSNullPin()); mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin()); if (card->hasSigGNullPin()) { mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN")); } else { mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); } const auto errMsg = card->errorMsg(); if (!errMsg.isEmpty()) { mErrorLabel->setText(QStringLiteral("%1: %2").arg(i18n("Error"), errMsg)); mErrorLabel->setVisible(true); } else { mErrorLabel->setVisible(false); } - mTreeView->setKeys(card->keys()); + const auto keys = card->keys(); + mTreeView->setKeys(keys); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin() && card->hasSigningKey() && card->hasEncryptionKey() && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm) && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm)); } if (mCreateCSRButton) { mCreateCSRButton->setEnabled(!getKeysSuitableForCSRCreation(card).empty()); } - if (card->canLearnKeys()) { + if (card->keyInfos().size() > keys.size()) { + // the card contains keys we don't know; try to learn them from the card learnCard(); } } void NetKeyWidget::learnCard() { auto cmd = new LearnCardKeysCommand(GpgME::CMS); cmd->setParentWidget(this); cmd->setShowsOutputWindow(false); cmd->start(); - - auto icon = KleopatraApplication::instance()->sysTrayIcon(); - if (icon) { - icon->setLearningInProgress(true); - } - - connect(cmd, &Command::finished, this, [icon]() { - icon->setLearningInProgress(false); - }); } void NetKeyWidget::doChangePin(const std::string &keyRef) { const auto netKeyCard = ReaderStatus::instance()->getCard(mSerialNumber); if (!netKeyCard) { KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber))); return; } auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin()) // || (keyRef == NetKeyCard::sigGPinKeyRef() && netKeyCard->hasSigGNullPin())) { cmd->setMode(ChangePinCommand::NullPinMode); } cmd->start(); } void NetKeyWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, NetKeyCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } namespace { std::string getKeyRef(const std::vector &keys, QWidget *parent) { QStringList options; for (const auto &key : keys) { options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip)); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Key"), i18n("Please select the key you want to create a certificate signing request for:"), options, /* current= */ 0, /* editable= */ false, &ok); return ok ? keys[options.indexOf(choice)].keyRef : std::string(); } } void NetKeyWidget::createCSR() { const auto netKeyCard = ReaderStatus::instance()->getCard(mSerialNumber); if (!netKeyCard) { KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber))); return; } const auto suitableKeys = getKeysSuitableForCSRCreation(netKeyCard.get()); if (suitableKeys.empty()) { KMessageBox::error(this, i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard.")); return; } const auto keyRef = getKeyRef(suitableKeys, this); if (keyRef.empty()) { return; } auto cmd = new CreateCSRForCardKeyCommand(keyRef, mSerialNumber, NetKeyCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "moc_netkeywidget.cpp"