diff --git a/src/view/netkeywidget.cpp b/src/view/netkeywidget.cpp index 6389b6ecd..84c77de0b 100644 --- a/src/view/netkeywidget.cpp +++ b/src/view/netkeywidget.cpp @@ -1,282 +1,248 @@ /* 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 "cardkeysview.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 <Libkleo/Algorithm> #include <Libkleo/Compliance> #include <Libkleo/Debug> #include <Libkleo/KeyCache> #include <Libkleo/KeyHelpers> #include <Libkleo/KeyListModel> #include <KLocalizedString> #include <KMessageBox> #include <KSeparator> #include <QHBoxLayout> #include <QInputDialog> #include <QLabel> #include <QPushButton> -#include <QScrollArea> #include <QVBoxLayout> #include <gpgme++/engineinfo.h> using namespace Kleo; using namespace Kleo::SmartCard; using namespace Kleo::Commands; NetKeyWidget::NetKeyWidget(QWidget *parent) : SmartCardWidget(parent) { - auto vLay = new QVBoxLayout; - - // Set up the scroll are - mArea = new QScrollArea{this}; - mArea->setFocusPolicy(Qt::NoFocus); - 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 = new QLabel{this}; - mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft); - - { - auto hLay1 = new QHBoxLayout; - auto label = new QLabel(i18nc("@label", "Serial number:")); - mSerialNumberLabel = new QLabel{this}; - mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - label->setBuddy(mSerialNumberLabel); - hLay1->addWidget(label); - hLay1->addWidget(mSerialNumberLabel); - hLay1->addStretch(1); - vLay->addLayout(hLay1); - } - mNullPinWidget = new NullPinWidget{this}; - vLay->addWidget(mNullPinWidget); + mContentLayout->addWidget(mNullPinWidget); mErrorLabel = new QLabel{this}; mErrorLabel->setVisible(false); - vLay->addWidget(mErrorLabel); + mContentLayout->addWidget(mErrorLabel); - vLay->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); mCardKeysView = new CardKeysView{this}; - vLay->addWidget(mCardKeysView); + mContentLayout->addWidget(mCardKeysView); // The action area - vLay->addWidget(new KSeparator(Qt::Horizontal)); - vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))), 0, Qt::AlignLeft); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new QLabel(QStringLiteral("<b>%1</b>").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 = new QPushButton{this}; mChangeNKSPINBtn->setText(i18nc("@action:button NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN")); mChangeSigGPINBtn = new QPushButton{this}; mChangeSigGPINBtn->setText(i18nc("@action:button 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); + mContentLayout->addLayout(actionLayout); + mContentLayout->addStretch(1); } NetKeyWidget::~NetKeyWidget() = default; namespace { std::vector<KeyPairInfo> getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard) { if (netKeyCard->hasNKSNullPin()) { return {}; } std::vector<KeyPairInfo> 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()); + SmartCardWidget::setCard(card); 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("<b>%1:</b> %2").arg(i18n("Error"), errMsg)); mErrorLabel->setVisible(true); } else { mErrorLabel->setVisible(false); } 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()); } mCardKeysView->setCard(card); } void NetKeyWidget::doChangePin(const std::string &keyRef) { const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(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<KeyPairInfo> &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<NetKeyCard>(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" diff --git a/src/view/netkeywidget.h b/src/view/netkeywidget.h index b98149cd8..41216bcd2 100644 --- a/src/view/netkeywidget.h +++ b/src/view/netkeywidget.h @@ -1,60 +1,56 @@ /* view/netkeywidget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "smartcardwidget.h" #include <Libkleo/Predicates> #include <gpgme++/error.h> #include <set> #include <string> #include <vector> class QLabel; class QPushButton; -class QScrollArea; namespace Kleo { class NullPinWidget; class CardKeysView; namespace SmartCard { class NetKeyCard; } class NetKeyWidget : public SmartCardWidget { Q_OBJECT public: explicit NetKeyWidget(QWidget *parent = nullptr); ~NetKeyWidget() override; void setCard(const SmartCard::NetKeyCard *card); private: void doChangePin(const std::string &keyRef); void createKeyFromCardKeys(); void createCSR(); private: - QLabel *mSerialNumberLabel = nullptr; - QLabel *mVersionLabel = nullptr; QLabel *mErrorLabel = nullptr; NullPinWidget *mNullPinWidget = nullptr; QPushButton *mKeyForCardKeysButton = nullptr; QPushButton *mCreateCSRButton = nullptr; QPushButton *mChangeNKSPINBtn = nullptr; QPushButton *mChangeSigGPINBtn = nullptr; CardKeysView *mCardKeysView = nullptr; - QScrollArea *mArea = nullptr; }; } // namespace Kleo diff --git a/src/view/p15cardwidget.cpp b/src/view/p15cardwidget.cpp index 254f32d54..ee48478c6 100644 --- a/src/view/p15cardwidget.cpp +++ b/src/view/p15cardwidget.cpp @@ -1,187 +1,150 @@ /* view/p15cardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com> SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include "p15cardwidget.h" #include "cardkeysview.h" #include "openpgpkeycardwidget.h" #include "settings.h" #include "smartcard/openpgpcard.h" #include "smartcard/p15card.h" #include "smartcard/readerstatus.h" -#include <QGridLayout> #include <QLabel> #include <QPushButton> -#include <QScrollArea> #include <QStringList> #include <QVBoxLayout> #include <KLocalizedString> #include <KSeparator> #include <Libkleo/Compat> #include <Libkleo/Formatting> #include <Libkleo/GnuPG> #include <Libkleo/KeyCache> #include <QGpgME/CryptoConfig> #include <QGpgME/ImportFromKeyserverJob> #include <QGpgME/KeyListJob> #include <QGpgME/Protocol> #include <gpgme++/importresult.h> #include <gpgme++/keylistresult.h> #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; P15CardWidget::P15CardWidget(QWidget *parent) : SmartCardWidget{parent} { - // Set up the scroll area - auto myLayout = new QVBoxLayout(this); - myLayout->setContentsMargins(0, 0, 0, 0); - - auto area = new QScrollArea; - area->setFocusPolicy(Qt::NoFocus); - area->setFrameShape(QFrame::NoFrame); - area->setWidgetResizable(true); - myLayout->addWidget(area); - - auto areaWidget = new QWidget; - area->setWidget(areaWidget); - - auto areaVLay = new QVBoxLayout(areaWidget); - - auto cardInfoGrid = new QGridLayout; - { - int row = 0; - - // Version and Serialnumber - mVersionLabel = new QLabel{this}; - mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); - cardInfoGrid->addWidget(mVersionLabel, row++, 0, 1, 2); - - cardInfoGrid->addWidget(new QLabel(i18nc("@label:textbox", "Serial number:")), row, 0); - mSerialNumberLabel = new QLabel{this}; - mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); - cardInfoGrid->addWidget(mSerialNumberLabel, row++, 1); - - cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); - } - areaVLay->addLayout(cardInfoGrid); mStatusLabel = new QLabel{this}; mStatusLabel->setVisible(false); - areaVLay->addWidget(mStatusLabel); + mContentLayout->addWidget(mStatusLabel); - areaVLay->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); mOpenPGPKeysSection = new QWidget{this}; { auto l = new QVBoxLayout{mOpenPGPKeysSection}; l->setContentsMargins(0, 0, 0, 0); l->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("OpenPGP keys:")))); mOpenPGPKeysWidget = new OpenPGPKeyCardWidget{this}; mOpenPGPKeysWidget->setAllowedActions(OpenPGPKeyCardWidget::NoAction); l->addWidget(mOpenPGPKeysWidget); l->addWidget(new KSeparator(Qt::Horizontal)); } mOpenPGPKeysSection->setVisible(false); - areaVLay->addWidget(mOpenPGPKeysSection); + mContentLayout->addWidget(mOpenPGPKeysSection); mCardKeysView = new CardKeysView{this}; mCardKeysView->setVisible(false); - areaVLay->addWidget(mCardKeysView); + mContentLayout->addWidget(mCardKeysView); - areaVLay->addStretch(1); + mContentLayout->addStretch(1); } P15CardWidget::~P15CardWidget() = default; void P15CardWidget::searchPGPFpr(const std::string &fpr) { /* Only do auto import from LDAP */ auto conf = QGpgME::cryptoConfig(); Q_ASSERT(conf); if (!Settings().alwaysSearchCardOnKeyserver() && !Kleo::keyserver().startsWith(QLatin1StringView{"ldap"})) { return; } mStatusLabel->setText(i18n("Searching in directory service...")); mStatusLabel->setVisible(true); qCDebug(KLEOPATRA_LOG) << "Looking for:" << fpr.c_str() << "on ldap server"; QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(true); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this, fpr]() { qCDebug(KLEOPATRA_LOG) << "Updating key info after changes"; ReaderStatus::mutableInstance()->updateStatus(); mOpenPGPKeysWidget->update(nullptr); }); connect(job, &QGpgME::KeyListJob::result, job, [this](GpgME::KeyListResult, std::vector<GpgME::Key> keys, QString, GpgME::Error) { if (keys.size() == 1) { auto importJob = QGpgME::openpgp()->importFromKeyserverJob(); qCDebug(KLEOPATRA_LOG) << "Importing: " << keys[0].primaryFingerprint(); connect(importJob, &QGpgME::ImportFromKeyserverJob::result, importJob, [this](GpgME::ImportResult, QString, GpgME::Error) { qCDebug(KLEOPATRA_LOG) << "import job done"; mStatusLabel->setText(i18n("Automatic import finished.")); }); importJob->start(keys); } else if (keys.size() > 1) { qCDebug(KLEOPATRA_LOG) << "Multiple keys found on server"; mStatusLabel->setText(i18n("Error multiple keys found on server.")); } else { qCDebug(KLEOPATRA_LOG) << "No key found"; mStatusLabel->setText(i18n("Key not found in directory service.")); } }); job->start(QStringList() << QString::fromStdString(fpr)); } void P15CardWidget::setCard(const P15Card *card) { - mSerialNumber = card->serialNumber(); - mVersionLabel->setText(i18nc("%1 is a smartcard manufacturer", "%1 PKCS#15 card", QString::fromStdString(card->manufacturer()))); - mSerialNumberLabel->setText(card->displaySerialNumber()); - mSerialNumberLabel->setToolTip(QString::fromStdString(card->serialNumber())); + SmartCardWidget::setCard(card); const auto sigInfo = card->keyInfo(card->signingKeyRef()); if (!sigInfo.grip.empty()) { const auto key = KeyCache::instance()->findSubkeyByKeyGrip(sigInfo.grip, GpgME::OpenPGP).parent(); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Failed to find key for grip:" << sigInfo.grip.c_str(); const auto pgpSigFpr = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()); if (!pgpSigFpr.empty()) { qCDebug(KLEOPATRA_LOG) << "Should be pgp key:" << pgpSigFpr.c_str(); searchPGPFpr(pgpSigFpr); } } else { mStatusLabel->setVisible(false); } } const bool cardHasOpenPGPKeys = (!card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() // || !card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty()); mOpenPGPKeysSection->setVisible(cardHasOpenPGPKeys); if (cardHasOpenPGPKeys) { mOpenPGPKeysWidget->update(card); } /* Check if additional keys could be available */ if (!Settings().autoLoadP15Certs()) { return; } mCardKeysView->setVisible(true); mCardKeysView->setCard(card); } #include "moc_p15cardwidget.cpp" diff --git a/src/view/p15cardwidget.h b/src/view/p15cardwidget.h index 26a494025..716fdffc9 100644 --- a/src/view/p15cardwidget.h +++ b/src/view/p15cardwidget.h @@ -1,47 +1,45 @@ /* view/p15cardwiget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com> SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "smartcardwidget.h" class QLabel; namespace Kleo { class CardKeysView; class OpenPGPKeyCardWidget; namespace SmartCard { class P15Card; } class P15CardWidget : public SmartCardWidget { Q_OBJECT public: explicit P15CardWidget(QWidget *parent = nullptr); ~P15CardWidget() override; void setCard(const SmartCard::P15Card *card); private: void searchPGPFpr(const std::string &fpr); private: - QLabel *mVersionLabel = nullptr; - QLabel *mSerialNumberLabel = nullptr; QLabel *mStatusLabel = nullptr; QWidget *mOpenPGPKeysSection = nullptr; OpenPGPKeyCardWidget *mOpenPGPKeysWidget = nullptr; CardKeysView *mCardKeysView = nullptr; }; } diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index f955e528b..cf467bbe4 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,573 +1,536 @@ /* view/pgpcardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "pgpcardwidget.h" #include "openpgpkeycardwidget.h" #include "kleopatra_debug.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/openpgpgeneratecardkeycommand.h" #include "smartcard/algorithminfo.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "smartcard/utils.h" #include "dialogs/gencardkeydialog.h" #include <Libkleo/Compliance> #include <Libkleo/GnuPG> #include <QFileDialog> #include <QFileInfo> #include <QGridLayout> #include <QHBoxLayout> #include <QInputDialog> #include <QLabel> #include <QProgressDialog> #include <QPushButton> -#include <QScrollArea> #include <QThread> #include <QVBoxLayout> #include <KLocalizedString> #include <KMessageBox> #include <KSeparator> #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <gpgme++/context.h> #include <gpgme++/data.h> #include <QGpgME/DataProvider> #include <gpgme++/gpggencardkeyinteractor.h> using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { class GenKeyThread : public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial) : mSerial(serial) , mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { // the index of the curves in this list has to match the enum values // minus 1 of GpgGenCardKeyInteractor::Curve static const std::vector<std::string> curves = { "curve25519", "curve448", "nistp256", "nistp384", "nistp521", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1", // keep it, even if we don't support it in Kleopatra }; auto ei = std::make_unique<GpgME::GpgGenCardKeyInteractor>(mSerial); if (mParams.algorithm.starts_with("rsa")) { ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt()); } else { ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC); const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm); if (curveIt != curves.end()) { ei->setCurve(static_cast<GpgME::GpgGenCardKeyInteractor::Curve>(curveIt - curves.cbegin() + 1)); } else { qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm; mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE); return; } } ei->setNameUtf8(mParams.name.toStdString()); ei->setEmailUtf8(mParams.email.toStdString()); ei->setDoBackup(mParams.backup); const auto ctx = std::shared_ptr<GpgME::Context>(GpgME::Context::createForProtocol(GpgME::OpenPGP)); ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves QGpgME::QByteArrayDataProvider dp; GpgME::Data data(&dp); mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data); mBkpFile = static_cast<GpgME::GpgGenCardKeyInteractor *>(ctx->lastCardEditInteractor())->backupFileName(); } private: GpgME::Error mErr; std::string mSerial; GenCardKeyDialog::KeyParams mParams; std::string mBkpFile; }; } // Namespace PGPCardWidget::PGPCardWidget(QWidget *parent) : SmartCardWidget(parent) { - // Set up the scroll area - auto myLayout = new QVBoxLayout(this); - myLayout->setContentsMargins(0, 0, 0, 0); - - auto area = new QScrollArea; - area->setFocusPolicy(Qt::NoFocus); - area->setFrameShape(QFrame::NoFrame); - area->setWidgetResizable(true); - myLayout->addWidget(area); - - auto areaWidget = new QWidget; - area->setWidget(areaWidget); - - auto areaVLay = new QVBoxLayout(areaWidget); - - auto cardInfoGrid = new QGridLayout; { - int row = 0; - - // Version and Serialnumber - mVersionLabel = new QLabel{this}; - mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2); - row++; - - cardInfoGrid->addWidget(new QLabel(i18nc("@label:textbox", "Serial number:")), row, 0); - mSerialNumberLabel = new QLabel{this}; - mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mSerialNumberLabel, row, 1); - row++; + mInfoGridLayout->setColumnStretch(mInfoGridLayout->columnCount() - 1, 0); // undo stretch set by base widget + int row = mInfoGridLayout->rowCount(); // Cardholder Row - cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); + mInfoGridLayout->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); mCardHolderLabel = new QLabel{this}; mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mCardHolderLabel, row, 1); + mInfoGridLayout->addWidget(mCardHolderLabel, row, 1); { auto button = new QPushButton{this}; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18nc("@info:tooltip", "Change")); - cardInfoGrid->addWidget(button, row, 2); + mInfoGridLayout->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); } row++; // URL Row - cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that " - "corresponds to a smartcard can be downloaded", - "Pubkey URL:")), - row, - 0); + mInfoGridLayout->addWidget(new QLabel(i18nc("The URL under which a public key that " + "corresponds to a smartcard can be downloaded", + "Pubkey URL:")), + row, + 0); mUrlLabel = new QLabel{this}; mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mUrlLabel, row, 1); + mInfoGridLayout->addWidget(mUrlLabel, row, 1); { auto button = new QPushButton{this}; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18nc("@info:tooltip", "Change")); - cardInfoGrid->addWidget(button, row, 2); + mInfoGridLayout->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); } row++; // PIN counters row { - cardInfoGrid->addWidget(new QLabel(i18nc("@label The number of remaining attempts to enter a PIN or PUK, as in " - "Remaining attempts: PIN: 2, PUK: 3, Admin PIN: 3", - "Remaining attempts:")), - row, - 0); + mInfoGridLayout->addWidget(new QLabel(i18nc("@label The number of remaining attempts to enter a PIN or PUK, as in " + "Remaining attempts: PIN: 2, PUK: 3, Admin PIN: 3", + "Remaining attempts:")), + row, + 0); mPinCounterLabel = new QLabel{this}; mPinCounterLabel->setToolTip(xi18nc("@info:tooltip", "Shows the number of remaining attempts for entering the correct PIN or PUK.")); mPinCounterLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mPinCounterLabel, row, 1); + mInfoGridLayout->addWidget(mPinCounterLabel, row, 1); } - cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); + mInfoGridLayout->setColumnStretch(mInfoGridLayout->columnCount(), 1); } - areaVLay->addLayout(cardInfoGrid); - areaVLay->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); // The keys - areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:")))); + mContentLayout->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:")))); mKeysWidget = new OpenPGPKeyCardWidget{this}; - areaVLay->addWidget(mKeysWidget); + mContentLayout->addWidget(mKeysWidget); connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR); connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey); - areaVLay->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); - areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:")))); + mContentLayout->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:")))); auto actionLayout = new QHBoxLayout; { auto generateButton = new QPushButton(i18nc("@action:button", "Generate New Keys"), this); generateButton->setToolTip(xi18nc("@info:tooltip", "<para>Generate three new keys on the smart card and create a new OpenPGP " "certificate with those keys. Optionally, the encryption key is generated " "off-card and a backup is created so that you can still access data encrypted " "with this key in case the card is lost or damaged.</para>" "<para><emphasis strong='true'>" "Existing keys on the smart card will be overwritten." "</emphasis></para>")); actionLayout->addWidget(generateButton); connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); } { auto pinButton = new QPushButton(i18nc("@action:button", "Change PIN"), this); pinButton->setToolTip(i18nc("@info:tooltip", "Change the PIN required for using the keys on the smart card. " "The PIN must contain at least six characters.")); actionLayout->addWidget(pinButton); connect(pinButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::pinKeyRef()); }); } { auto unblockButton = new QPushButton(i18nc("@action:button", "Unblock Card"), this); unblockButton->setToolTip(i18nc("@info:tooltip", "Unblock the smart card with the PUK (if available) or the Admin PIN.")); actionLayout->addWidget(unblockButton); connect(unblockButton, &QPushButton::clicked, this, [this]() { if (mPUKIsAvailable) { // unblock card with the PUK doChangePin(OpenPGPCard::resetCodeKeyRef()); } else { // unblock card with the Admin PIN doChangePin(OpenPGPCard::pinKeyRef(), ChangePinCommand::ResetMode); } }); } { auto pukButton = new QPushButton(i18nc("@action:button", "Change Admin PIN"), this); pukButton->setToolTip(i18nc("@info:tooltip", "Change the PIN required for administrative operations.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::adminPinKeyRef()); }); } { mSetOrChangePUKButton = new QPushButton(i18nc("@action:button", "Set PUK"), this); mSetOrChangePUKButton->setToolTip(i18nc("@info:tooltip", "Set or change the PUK that can be used to unblock the smart card. " "The PUK must contain at least eight characters.")); actionLayout->addWidget(mSetOrChangePUKButton); connect(mSetOrChangePUKButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); }); } actionLayout->addStretch(-1); - areaVLay->addLayout(actionLayout); + mContentLayout->addLayout(actionLayout); - areaVLay->addStretch(1); + mContentLayout->addStretch(1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { - const QString version = card->displayAppVersion(); + SmartCardWidget::setCard(card); mIs21 = card->appVersion() >= 0x0201; - const QString manufacturer = QString::fromStdString(card->manufacturer()); - const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1StringView("unknown"); - mVersionLabel->setText( - manufacturerIsUnknown - ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) - : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version)); - mSerialNumberLabel->setText(card->displaySerialNumber()); - mSerialNumber = card->serialNumber(); const auto holder = card->cardHolder(); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("<a href=\"%1\">%1</a>").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); const auto pinLabels = card->pinLabels(); const auto pinCounters = card->pinCounters(); QStringList countersWithLabels; countersWithLabels.reserve(pinCounters.size()); for (const auto &pinCounter : pinCounters) { // sanity check if (countersWithLabels.size() == pinLabels.size()) { break; } countersWithLabels.push_back(i18nc("label: value", "%1: %2", pinLabels[countersWithLabels.size()], pinCounter)); } mPinCounterLabel->setText(countersWithLabels.join(QLatin1String(", "))); mPUKIsAvailable = (pinCounters.size() == 3) && (pinCounters[1] > 0); mSetOrChangePUKButton->setText(mPUKIsAvailable ? i18nc("@action:button", "Change PUK") : i18nc("@action:button", "Set PUK")); mKeysWidget->update(card); mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty(); } void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode) { auto cmd = new ChangePinCommand(mSerialNumber, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->setMode(mode); cmd->start(); } void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const GpgME::Error err = ReaderStatus::switchCardAndApp(mSerialNumber, OpenPGPCard::AppName); if (err) { return; } const auto params = dlg->getKeyParams(); auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setMaximum(0); progress->setMinimum(0); progress->setModal(true); progress->setCancelButton(nullptr); progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); progress->setLabel(new QLabel(i18nc("@label:textbox", "This may take several minutes..."))); auto workerThread = new GenKeyThread(params, mSerialNumber); connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { progress->accept(); progress->deleteLater(); genKeyDone(workerThread->error(), workerThread->bkpFile()); delete workerThread; }); workerThread->start(); progress->exec(); } void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) { if (err) { KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", Formatting::errorAsString(err))); return; } if (err.isCanceled()) { return; } if (!backup.empty()) { const auto bkpFile = QString::fromStdString(backup); QFileInfo fi(bkpFile); const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile)); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } void PGPCardWidget::genkeyRequested() { const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mSerialNumber); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mSerialNumber))); return; } if (!mCardIsEmpty) { auto ret = KMessageBox::warningContinueCancel(this, i18n("The existing keys on this card will be <b>deleted</b> " "and replaced by new keys.") + QStringLiteral("<br/><br/>") + i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."), i18n("Secret Key Deletion"), KStandardGuiItem::guiItem(KStandardGuiItem::Delete), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return; } } auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this); const auto allowedAlgos = getAllowedAlgorithms(pgpCard->supportedAlgorithms()); if (allowedAlgos.empty()) { KMessageBox::error(this, i18nc("@info", "You cannot generate keys on this smart card because it doesn't support any of the compliant algorithms.")); return; } dlg->setSupportedAlgorithms(allowedAlgos, getPreferredAlgorithm(allowedAlgos)); connect(dlg, &QDialog::accepted, this, [this, dlg]() { doGenKey(dlg); dlg->deleteLater(); }); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changeNameRequested() { QString text = mCardHolderLabel->text(); while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.contains(QLatin1Char('<'))) { KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used.")); continue; } if (text.contains(QLatin1StringView(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters.")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mSerialNumber); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mSerialNumber))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeNameResult(err); }); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", Formatting::errorAsString(err))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = mUrl; while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), i18n("New pubkey URL:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.size() > 254) { KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters.")); } break; } const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mSerialNumber); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mSerialNumber))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeUrlResult(err); }); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", Formatting::errorAsString(err))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::createCSR(const std::string &keyref) { auto cmd = new CreateCSRForCardKeyCommand(keyref, mSerialNumber, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::generateKey(const std::string &keyref) { auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mSerialNumber, this); this->setEnabled(false); connect(cmd, &OpenPGPGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "pgpcardwidget.moc" #include "moc_pgpcardwidget.cpp" diff --git a/src/view/pgpcardwidget.h b/src/view/pgpcardwidget.h index 66db13e08..c2fb12a2c 100644 --- a/src/view/pgpcardwidget.h +++ b/src/view/pgpcardwidget.h @@ -1,70 +1,68 @@ /* view/pgpcardwiget.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-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "smartcardwidget.h" #include "commands/changepincommand.h" #include <gpgme++/error.h> #include <string> class QLabel; class QPushButton; namespace Kleo { class GenCardKeyDialog; class OpenPGPKeyCardWidget; namespace SmartCard { struct KeyPairInfo; class OpenPGPCard; } // namespace SmartCard class PGPCardWidget : public SmartCardWidget { Q_OBJECT public: explicit PGPCardWidget(QWidget *parent = nullptr); void setCard(const SmartCard::OpenPGPCard *card); void doGenKey(GenCardKeyDialog *dlg); void genKeyDone(const GpgME::Error &err, const std::string &backup); public Q_SLOTS: void genkeyRequested(); void changeNameRequested(); void changeNameResult(const GpgME::Error &err); void changeUrlRequested(); void changeUrlResult(const GpgME::Error &err); void createCSR(const std::string &keyref); void generateKey(const std::string &keyref); private: void doChangePin(const std::string &keyRef, Commands::ChangePinCommand::ChangePinMode mode = Commands::ChangePinCommand::NormalMode); private: - QLabel *mSerialNumberLabel = nullptr; QLabel *mCardHolderLabel = nullptr; - QLabel *mVersionLabel = nullptr; QLabel *mUrlLabel = nullptr; QLabel *mPinCounterLabel = nullptr; QPushButton *mSetOrChangePUKButton = nullptr; OpenPGPKeyCardWidget *mKeysWidget = nullptr; QString mUrl; bool mCardIsEmpty = false; bool mIs21 = false; bool mPUKIsAvailable = false; }; } // namespace Kleo diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp index 3e4333360..1508d1e4b 100644 --- a/src/view/pivcardwidget.cpp +++ b/src/view/pivcardwidget.cpp @@ -1,397 +1,358 @@ /* view/pivcardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include "pivcardwidget.h" #include "tooltippreferences.h" #include "commands/certificatetopivcardcommand.h" #include "commands/changepincommand.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" #include "commands/importcertificatefrompivcardcommand.h" #include "commands/keytocardcommand.h" #include "commands/pivgeneratecardkeycommand.h" #include "commands/setpivcardapplicationadministrationkeycommand.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include <Libkleo/Compliance> #include <Libkleo/Dn> #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <KLocalizedString> #include <KSeparator> -#include <QFrame> #include <QGridLayout> #include <QLabel> #include <QPushButton> -#include <QScrollArea> #include <QVBoxLayout> using namespace GpgME; using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { static void layoutKeyWidgets(QGridLayout *grid, const QString &keyName, const PIVCardWidget::KeyWidgets &keyWidgets) { int row = grid->rowCount(); grid->addWidget(new QLabel(keyName), row, 0); grid->addWidget(keyWidgets.keyGrip, row, 1); grid->addWidget(keyWidgets.keyAlgorithm, row, 2); grid->addWidget(keyWidgets.generateButton, row, 3); if (keyWidgets.writeKeyButton) { grid->addWidget(keyWidgets.writeKeyButton, row, 4); } row++; grid->addWidget(keyWidgets.certificateInfo, row, 1, 1, 2); grid->addWidget(keyWidgets.writeCertificateButton, row, 3); grid->addWidget(keyWidgets.importCertificateButton, row, 4); if (keyWidgets.createCSRButton) { grid->addWidget(keyWidgets.createCSRButton, row, 5); } } static int toolTipOptions() { 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; } } PIVCardWidget::PIVCardWidget(QWidget *parent) : SmartCardWidget(parent) { - // Set up the scroll area - auto myLayout = new QVBoxLayout(this); - myLayout->setContentsMargins(0, 0, 0, 0); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); - auto area = new QScrollArea; - area->setFocusPolicy(Qt::NoFocus); - area->setFrameShape(QFrame::NoFrame); - area->setWidgetResizable(true); - myLayout->addWidget(area); - - auto areaWidget = new QWidget; - area->setWidget(areaWidget); - - auto areaVLay = new QVBoxLayout(areaWidget); - - auto cardInfoGrid = new QGridLayout; - { - int row = 0; - - // Version and Serialnumber - mVersionLabel = new QLabel{this}; - mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mVersionLabel, row++, 0, 1, 2); - - cardInfoGrid->addWidget(new QLabel(i18nc("@label:textbox", "Serial number:")), row, 0); - mSerialNumberLabel = new QLabel{this}; - mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - cardInfoGrid->addWidget(mSerialNumberLabel, row++, 1); - - cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); - } - areaVLay->addLayout(cardInfoGrid); - - areaVLay->addWidget(new KSeparator(Qt::Horizontal)); - - areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:")), this)); + mContentLayout->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:")), this)); auto keysGrid = new QGridLayout; for (const auto &keyInfo : PIVCard::supportedKeys()) { KeyWidgets keyWidgets = createKeyWidgets(keyInfo); layoutKeyWidgets(keysGrid, PIVCard::keyDisplayName(keyInfo.keyRef), keyWidgets); } - areaVLay->addLayout(keysGrid); + mContentLayout->addLayout(keysGrid); - areaVLay->addWidget(new KSeparator(Qt::Horizontal)); + mContentLayout->addWidget(new KSeparator(Qt::Horizontal)); 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, &PIVCardWidget::createKeyFromCardKeys); } { auto button = new QPushButton(i18nc("@action:button", "Change PIN"), this); button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application PIN that activates the PIV Card " "and enables private key operations using the stored keys.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this]() { changePin(PIVCard::pinKeyRef()); }); } { auto button = new QPushButton(i18nc("@action:button", "Change PUK"), this); button->setToolTip(i18nc("@info:tooltip", "Change the PIN Unblocking Key that enables a reset of the PIN.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this]() { changePin(PIVCard::pukKeyRef()); }); } { auto button = new QPushButton(i18nc("@action:button", "Change Admin Key"), this); button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application Administration Key that is used by the " "PIV Card Application to authenticate the PIV Card Application Administrator and by the " "administrator (resp. Kleopatra) to authenticate the PIV Card Application.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this]() { setAdminKey(); }); } actionLayout->addStretch(-1); - areaVLay->addLayout(actionLayout); + mContentLayout->addLayout(actionLayout); - areaVLay->addStretch(1); + mContentLayout->addStretch(1); } PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const KeyPairInfo &keyInfo) { const std::string keyRef = keyInfo.keyRef; KeyWidgets keyWidgets; keyWidgets.keyGrip = new QLabel(this); keyWidgets.keyGrip->setTextInteractionFlags(Qt::TextBrowserInteraction); keyWidgets.keyAlgorithm = new QLabel(this); keyWidgets.keyAlgorithm->setTextInteractionFlags(Qt::TextSelectableByMouse); keyWidgets.generateButton = new QPushButton(i18nc("@action:button", "Generate"), this); keyWidgets.generateButton->setEnabled(false); connect(keyWidgets.generateButton, &QPushButton::clicked, this, [this, keyRef]() { generateKey(keyRef); }); if (keyRef == PIVCard::cardAuthenticationKeyRef() || keyRef == PIVCard::keyManagementKeyRef()) { keyWidgets.writeKeyButton = new QPushButton(i18nc("@action:button", "Write Key"), this); keyWidgets.writeKeyButton->setToolTip(i18nc("@info:tooltip", "Write the key pair of a certificate to the card")); keyWidgets.writeKeyButton->setEnabled(true); connect(keyWidgets.writeKeyButton, &QPushButton::clicked, this, [this, keyRef]() { writeKeyToCard(keyRef); }); } keyWidgets.certificateInfo = new QLabel(this); keyWidgets.certificateInfo->setTextInteractionFlags(Qt::TextBrowserInteraction); keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate"), this); keyWidgets.writeCertificateButton->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card")); keyWidgets.writeCertificateButton->setEnabled(false); connect(keyWidgets.writeCertificateButton, &QPushButton::clicked, this, [this, keyRef]() { writeCertificateToCard(keyRef); }); keyWidgets.importCertificateButton = new QPushButton(i18nc("@action:button", "Import Certificate"), this); keyWidgets.importCertificateButton->setToolTip(i18nc("@info:tooltip", "Import the certificate stored on the card")); keyWidgets.importCertificateButton->setEnabled(false); connect(keyWidgets.importCertificateButton, &QPushButton::clicked, this, [this, keyRef]() { importCertificateFromCard(keyRef); }); if (keyInfo.canSign() || keyInfo.canEncrypt()) { keyWidgets.createCSRButton = new QPushButton(i18nc("@action:button", "Create CSR"), this); keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key")); keyWidgets.createCSRButton->setEnabled(false); connect(keyWidgets.createCSRButton, &QPushButton::clicked, this, [this, keyRef]() { createCSR(keyRef); }); } mKeyWidgets.insert({keyRef, keyWidgets}); return keyWidgets; } PIVCardWidget::~PIVCardWidget() { } void PIVCardWidget::setCard(const PIVCard *card) { - mSerialNumber = card->serialNumber(); - mVersionLabel->setText(i18nc("%1 version number", "PIV v%1 card", card->displayAppVersion())); - - mSerialNumberLabel->setText(card->displaySerialNumber()); - mSerialNumberLabel->setToolTip(QString::fromStdString(card->serialNumber())); + SmartCardWidget::setCard(card); if (card) { updateCachedValues(PIVCard::pivAuthenticationKeyRef(), card); updateCachedValues(PIVCard::cardAuthenticationKeyRef(), card); updateCachedValues(PIVCard::digitalSignatureKeyRef(), card); updateCachedValues(PIVCard::keyManagementKeyRef(), card); } updateKeyWidgets(PIVCard::pivAuthenticationKeyRef()); updateKeyWidgets(PIVCard::cardAuthenticationKeyRef()); updateKeyWidgets(PIVCard::digitalSignatureKeyRef()); updateKeyWidgets(PIVCard::keyManagementKeyRef()); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(card->hasSigningKey() // && card->hasEncryptionKey() // && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm) && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm)); } } void PIVCardWidget::updateCachedValues(const std::string &keyRef, const SmartCard::PIVCard *card) { KeyWidgets &widgets = mKeyWidgets.at(keyRef); widgets.keyInfo = card->keyInfo(keyRef); widgets.certificateData = card->certificateData(keyRef); } void PIVCardWidget::updateKeyWidgets(const std::string &keyRef) { const KeyWidgets &widgets = mKeyWidgets.at(keyRef); const std::string grip = widgets.keyInfo.grip; if (grip.empty()) { widgets.certificateInfo->setText(i18nc("@info", "<em>slot empty</em>")); widgets.certificateInfo->setToolTip(QString()); widgets.keyGrip->setText(QString()); widgets.keyAlgorithm->setText(QString()); widgets.generateButton->setText(i18nc("@action:button", "Generate")); widgets.generateButton->setToolTip(i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef))); if (widgets.createCSRButton) { widgets.createCSRButton->setEnabled(false); } widgets.writeCertificateButton->setEnabled(false); widgets.importCertificateButton->setEnabled(false); } else { const Key certificate = KeyCache::instance()->findSubkeyByKeyGrip(grip, GpgME::CMS).parent(); if (!certificate.isNull()) { widgets.certificateInfo->setText(i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)", DN(certificate.userID(0).id()).prettyDN(), Formatting::complianceStringShort(certificate), Formatting::creationDateString(certificate))); widgets.certificateInfo->setToolTip(Formatting::toolTip(certificate, toolTipOptions())); widgets.writeCertificateButton->setEnabled(true); } else { widgets.certificateInfo->setText(i18nc("@info", "<em>no matching certificate</em>")); widgets.certificateInfo->setToolTip(QString()); widgets.writeCertificateButton->setEnabled(false); } widgets.keyGrip->setText(QString::fromStdString(grip)); const std::string algo = widgets.keyInfo.algorithm; widgets.keyAlgorithm->setText(algo.empty() ? i18nc("@info unknown key algorithm", "unknown") : QString::fromStdString(algo)); widgets.importCertificateButton->setEnabled(!widgets.certificateData.empty()); widgets.generateButton->setText(i18nc("@action:button", "Replace")); widgets.generateButton->setToolTip(i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef))); if (widgets.createCSRButton) { widgets.createCSRButton->setEnabled(DeVSCompliance::algorithmIsCompliant(algo)); } } widgets.generateButton->setEnabled(true); } void PIVCardWidget::generateKey(const std::string &keyref) { auto cmd = new PIVGenerateCardKeyCommand(mSerialNumber, this); this->setEnabled(false); connect(cmd, &PIVGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyref); cmd->start(); } void PIVCardWidget::createCSR(const std::string &keyref) { auto cmd = new CreateCSRForCardKeyCommand(keyref, mSerialNumber, PIVCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PIVCardWidget::writeCertificateToCard(const std::string &keyref) { auto cmd = new CertificateToPIVCardCommand(keyref, mSerialNumber); this->setEnabled(false); connect(cmd, &CertificateToPIVCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::importCertificateFromCard(const std::string &keyref) { auto cmd = new ImportCertificateFromPIVCardCommand(keyref, mSerialNumber); this->setEnabled(false); connect(cmd, &ImportCertificateFromPIVCardCommand::finished, this, [this, keyref]() { this->updateKeyWidgets(keyref); this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::writeKeyToCard(const std::string &keyref) { auto cmd = new KeyToCardCommand(keyref, mSerialNumber, PIVCard::AppName); this->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setParentWidget(this); cmd->start(); } void PIVCardWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, PIVCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PIVCardWidget::changePin(const std::string &keyRef) { auto cmd = new ChangePinCommand(mSerialNumber, PIVCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->start(); } void PIVCardWidget::setAdminKey() { auto cmd = new SetPIVCardApplicationAdministrationKeyCommand(mSerialNumber, this); this->setEnabled(false); connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "moc_pivcardwidget.cpp" diff --git a/src/view/pivcardwidget.h b/src/view/pivcardwidget.h index 18333a928..3e5c3ea87 100644 --- a/src/view/pivcardwidget.h +++ b/src/view/pivcardwidget.h @@ -1,71 +1,69 @@ /* view/pivcardwiget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "smartcardwidget.h" #include <smartcard/keypairinfo.h> #include <QMap> #include <gpgme++/error.h> class QLabel; class QPushButton; namespace Kleo { namespace SmartCard { class PIVCard; } // namespace SmartCard class PIVCardWidget : public SmartCardWidget { Q_OBJECT public: explicit PIVCardWidget(QWidget *parent = nullptr); ~PIVCardWidget() override; void setCard(const SmartCard::PIVCard *card); struct KeyWidgets { SmartCard::KeyPairInfo keyInfo; std::string certificateData; QLabel *keyGrip = nullptr; QLabel *keyAlgorithm = nullptr; QLabel *certificateInfo = nullptr; QPushButton *generateButton = nullptr; QPushButton *createCSRButton = nullptr; QPushButton *writeCertificateButton = nullptr; QPushButton *importCertificateButton = nullptr; QPushButton *writeKeyButton = nullptr; }; private: KeyWidgets createKeyWidgets(const SmartCard::KeyPairInfo &keyInfo); void updateCachedValues(const std::string &keyRef, const SmartCard::PIVCard *card); void updateKeyWidgets(const std::string &keyRef); void generateKey(const std::string &keyref); void createCSR(const std::string &keyref); void writeCertificateToCard(const std::string &keyref); void importCertificateFromCard(const std::string &keyref); void writeKeyToCard(const std::string &keyref); void createKeyFromCardKeys(); void changePin(const std::string &keyRef); void setAdminKey(); private: - QLabel *mSerialNumberLabel = nullptr; - QLabel *mVersionLabel = nullptr; QPushButton *mKeyForCardKeysButton = nullptr; std::map<std::string, KeyWidgets> mKeyWidgets; }; } // namespace Kleo diff --git a/src/view/smartcardwidget.cpp b/src/view/smartcardwidget.cpp index 151217b5a..eb2aa27fc 100644 --- a/src/view/smartcardwidget.cpp +++ b/src/view/smartcardwidget.cpp @@ -1,17 +1,101 @@ /* view/smartcardwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2024 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include "smartcardwidget.h" +#include "infofield.h" + +#include <smartcard/card.h> + +#include <KLocalizedString> + +#include <QGridLayout> +#include <QLabel> +#include <QScrollArea> +#include <QVBoxLayout> + +using namespace Kleo; +using namespace Kleo::SmartCard; + +QString cardType(const Card *card) +{ + switch (card->appType()) { + case AppType::NetKeyApp: + return i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion()); + case AppType::OpenPGPApp: { + const std::string manufacturer = card->manufacturer(); + const bool manufacturerIsUnknown = manufacturer.empty() || manufacturer == "unknown"; + return (manufacturerIsUnknown // + ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", card->displayAppVersion()) + : i18nc("First placeholder is manufacturer, second placeholder is a version number", + "%1 OpenPGP v%2 card", + QString::fromStdString(manufacturer), + card->displayAppVersion())); + } + case AppType::P15App: + return i18nc("%1 is a smartcard manufacturer", "%1 PKCS#15 card", QString::fromStdString(card->manufacturer())); + case AppType::PIVApp: + return i18nc("%1 version number", "PIV v%1 card", card->displayAppVersion()); + default: + return {}; + }; +} + SmartCardWidget::SmartCardWidget(QWidget *parent) : QWidget{parent} { + auto mainLayout = new QVBoxLayout{this}; + mainLayout->setContentsMargins({}); + + auto area = new QScrollArea{this}; + area->setFocusPolicy(Qt::NoFocus); + area->setFrameShape(QFrame::NoFrame); + area->setWidgetResizable(true); + mainLayout->addWidget(area); + + auto areaWidget = new QWidget{this}; + area->setWidget(areaWidget); + mContentLayout = new QVBoxLayout{areaWidget}; + auto contentLayout = mContentLayout; + + { + // auto gridLayout = new QGridLayout; + mInfoGridLayout = new QGridLayout; + auto gridLayout = mInfoGridLayout; + // gridLayout->setColumnStretch(1, 1); + + int row = -1; + + row++; + mCardTypeField = std::make_unique<InfoField>(i18nc("@label", "Card type:"), parent); + gridLayout->addWidget(mCardTypeField->label(), row, 0); + gridLayout->addLayout(mCardTypeField->layout(), row, 1); + + row++; + mSerialNumberField = std::make_unique<InfoField>(i18nc("@label", "Serial number:"), parent); + gridLayout->addWidget(mSerialNumberField->label(), row, 0); + gridLayout->addLayout(mSerialNumberField->layout(), row, 1); + + gridLayout->setColumnStretch(gridLayout->columnCount(), 1); + + contentLayout->addLayout(gridLayout); + } } SmartCardWidget::~SmartCardWidget() = default; + +void SmartCardWidget::setCard(const Card *card) +{ + mSerialNumber = card->serialNumber(); + mAppName = card->appName(); + mAppType = card->appType(); + + mCardTypeField->setValue(cardType(card)); + mSerialNumberField->setValue(card->displaySerialNumber()); +} diff --git a/src/view/smartcardwidget.h b/src/view/smartcardwidget.h index 457f61bfa..063d1b3fd 100644 --- a/src/view/smartcardwidget.h +++ b/src/view/smartcardwidget.h @@ -1,23 +1,49 @@ /* view/smartcardwidget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2024 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include <QWidget> +#include <memory> #include <string> +class QGridLayout; +class QVBoxLayout; + +namespace Kleo +{ +class InfoField; +} +namespace Kleo::SmartCard +{ +enum class AppType; +class Card; +} + class SmartCardWidget : public QWidget { public: SmartCardWidget(QWidget *parent = nullptr); ~SmartCardWidget() override; + void setCard(const Kleo::SmartCard::Card *card); + protected: std::string mSerialNumber; + + QVBoxLayout *mContentLayout = nullptr; + QGridLayout *mInfoGridLayout = nullptr; + +private: + std::string mAppName; + Kleo::SmartCard::AppType mAppType; + + std::unique_ptr<Kleo::InfoField> mCardTypeField; + std::unique_ptr<Kleo::InfoField> mSerialNumberField; };