File indexing completed on 2024-06-16 04:56:16

0001 /*  view/pgpcardwiget.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
0005     SPDX-FileContributor: Intevation GmbH
0006     SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH
0007     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include <config-kleopatra.h>
0013 
0014 #include "pgpcardwidget.h"
0015 
0016 #include "openpgpkeycardwidget.h"
0017 
0018 #include "kleopatra_debug.h"
0019 
0020 #include "commands/createcsrforcardkeycommand.h"
0021 #include "commands/createopenpgpkeyfromcardkeyscommand.h"
0022 #include "commands/openpgpgeneratecardkeycommand.h"
0023 
0024 #include "smartcard/algorithminfo.h"
0025 #include "smartcard/openpgpcard.h"
0026 #include "smartcard/readerstatus.h"
0027 #include "smartcard/utils.h"
0028 
0029 #include "dialogs/gencardkeydialog.h"
0030 
0031 #include <Libkleo/Compliance>
0032 #include <Libkleo/GnuPG>
0033 
0034 #include <QFileDialog>
0035 #include <QFileInfo>
0036 #include <QGridLayout>
0037 #include <QHBoxLayout>
0038 #include <QInputDialog>
0039 #include <QLabel>
0040 #include <QProgressDialog>
0041 #include <QPushButton>
0042 #include <QScrollArea>
0043 #include <QThread>
0044 #include <QVBoxLayout>
0045 
0046 #include <KLocalizedString>
0047 #include <KMessageBox>
0048 #include <KSeparator>
0049 
0050 #include <Libkleo/Formatting>
0051 #include <Libkleo/KeyCache>
0052 
0053 #include <gpgme++/context.h>
0054 #include <gpgme++/data.h>
0055 
0056 #include <QGpgME/DataProvider>
0057 
0058 #include <gpgme++/gpggencardkeyinteractor.h>
0059 
0060 using namespace Kleo;
0061 using namespace Kleo::Commands;
0062 using namespace Kleo::SmartCard;
0063 
0064 namespace
0065 {
0066 class GenKeyThread : public QThread
0067 {
0068     Q_OBJECT
0069 
0070 public:
0071     explicit GenKeyThread(const GenCardKeyDialog::KeyParams &params, const std::string &serial)
0072         : mSerial(serial)
0073         , mParams(params)
0074     {
0075     }
0076 
0077     GpgME::Error error()
0078     {
0079         return mErr;
0080     }
0081 
0082     std::string bkpFile()
0083     {
0084         return mBkpFile;
0085     }
0086 
0087 protected:
0088     void run() override
0089     {
0090         // the index of the curves in this list has to match the enum values
0091         // minus 1 of GpgGenCardKeyInteractor::Curve
0092         static const std::vector<std::string> curves = {
0093             "curve25519",
0094             "curve448",
0095             "nistp256",
0096             "nistp384",
0097             "nistp521",
0098             "brainpoolP256r1",
0099             "brainpoolP384r1",
0100             "brainpoolP512r1",
0101             "secp256k1", // keep it, even if we don't support it in Kleopatra
0102         };
0103 
0104         auto ei = std::make_unique<GpgME::GpgGenCardKeyInteractor>(mSerial);
0105         if (mParams.algorithm.starts_with("rsa")) {
0106             ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA);
0107             ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt());
0108         } else {
0109             ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC);
0110             const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm);
0111             if (curveIt != curves.end()) {
0112                 ei->setCurve(static_cast<GpgME::GpgGenCardKeyInteractor::Curve>(curveIt - curves.cbegin() + 1));
0113             } else {
0114                 qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm;
0115                 mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE);
0116                 return;
0117             }
0118         }
0119         ei->setNameUtf8(mParams.name.toStdString());
0120         ei->setEmailUtf8(mParams.email.toStdString());
0121         ei->setDoBackup(mParams.backup);
0122 
0123         const auto ctx = std::shared_ptr<GpgME::Context>(GpgME::Context::createForProtocol(GpgME::OpenPGP));
0124         ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves
0125         QGpgME::QByteArrayDataProvider dp;
0126         GpgME::Data data(&dp);
0127 
0128         mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data);
0129         mBkpFile = static_cast<GpgME::GpgGenCardKeyInteractor *>(ctx->lastCardEditInteractor())->backupFileName();
0130     }
0131 
0132 private:
0133     GpgME::Error mErr;
0134     std::string mSerial;
0135     GenCardKeyDialog::KeyParams mParams;
0136 
0137     std::string mBkpFile;
0138 };
0139 
0140 } // Namespace
0141 
0142 PGPCardWidget::PGPCardWidget(QWidget *parent)
0143     : QWidget(parent)
0144     , mSerialNumber(new QLabel(this))
0145     , mCardHolderLabel(new QLabel(this))
0146     , mVersionLabel(new QLabel(this))
0147     , mUrlLabel(new QLabel(this))
0148     , mCardIsEmpty(false)
0149 {
0150     // Set up the scroll area
0151     auto myLayout = new QVBoxLayout(this);
0152     myLayout->setContentsMargins(0, 0, 0, 0);
0153 
0154     auto area = new QScrollArea;
0155     area->setFrameShape(QFrame::NoFrame);
0156     area->setWidgetResizable(true);
0157     myLayout->addWidget(area);
0158 
0159     auto areaWidget = new QWidget;
0160     area->setWidget(areaWidget);
0161 
0162     auto areaVLay = new QVBoxLayout(areaWidget);
0163 
0164     auto cardInfoGrid = new QGridLayout;
0165     {
0166         int row = 0;
0167 
0168         // Version and Serialnumber
0169         cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2);
0170         mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0171         row++;
0172 
0173         cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
0174         cardInfoGrid->addWidget(mSerialNumber, row, 1);
0175         mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction);
0176         row++;
0177 
0178         // Cardholder Row
0179         cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0);
0180         cardInfoGrid->addWidget(mCardHolderLabel, row, 1);
0181         mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0182         {
0183             auto button = new QPushButton;
0184             button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
0185             button->setAccessibleName(i18nc("@action:button", "Edit"));
0186             button->setToolTip(i18n("Change"));
0187             cardInfoGrid->addWidget(button, row, 2);
0188             connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested);
0189         }
0190         row++;
0191 
0192         // URL Row
0193         cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that "
0194                                                  "corresponds to a smartcard can be downloaded",
0195                                                  "Pubkey URL:")),
0196                                 row,
0197                                 0);
0198         cardInfoGrid->addWidget(mUrlLabel, row, 1);
0199         mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0200         {
0201             auto button = new QPushButton;
0202             button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
0203             button->setAccessibleName(i18nc("@action:button", "Edit"));
0204             button->setToolTip(i18n("Change"));
0205             cardInfoGrid->addWidget(button, row, 2);
0206             connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested);
0207         }
0208 
0209         cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1);
0210     }
0211     areaVLay->addLayout(cardInfoGrid);
0212 
0213     areaVLay->addWidget(new KSeparator(Qt::Horizontal));
0214 
0215     // The keys
0216     areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:"))));
0217 
0218     mKeysWidget = new OpenPGPKeyCardWidget{this};
0219     areaVLay->addWidget(mKeysWidget);
0220     connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR);
0221     connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey);
0222 
0223     areaVLay->addWidget(new KSeparator(Qt::Horizontal));
0224 
0225     areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))));
0226 
0227     auto actionLayout = new QHBoxLayout;
0228 
0229     {
0230         auto generateButton = new QPushButton(i18n("Generate New Keys"));
0231         generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card."));
0232         actionLayout->addWidget(generateButton);
0233         connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested);
0234     }
0235     {
0236         auto pinButton = new QPushButton(i18n("Change PIN"));
0237         pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard."));
0238         actionLayout->addWidget(pinButton);
0239         connect(pinButton, &QPushButton::clicked, this, [this]() {
0240             doChangePin(OpenPGPCard::pinKeyRef());
0241         });
0242     }
0243     {
0244         auto unblockButton = new QPushButton(i18n("Unblock Card"));
0245         unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN."));
0246         actionLayout->addWidget(unblockButton);
0247         connect(unblockButton, &QPushButton::clicked, this, [this]() {
0248             doChangePin(OpenPGPCard::resetCodeKeyRef());
0249         });
0250     }
0251     {
0252         auto pukButton = new QPushButton(i18n("Change Admin PIN"));
0253         pukButton->setToolTip(i18n("Change the PIN required for administrative operations."));
0254         actionLayout->addWidget(pukButton);
0255         connect(pukButton, &QPushButton::clicked, this, [this]() {
0256             doChangePin(OpenPGPCard::adminPinKeyRef());
0257         });
0258     }
0259     {
0260         auto resetCodeButton = new QPushButton(i18n("Change Reset Code"));
0261         resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN."));
0262         actionLayout->addWidget(resetCodeButton);
0263         connect(resetCodeButton, &QPushButton::clicked, this, [this]() {
0264             doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode);
0265         });
0266     }
0267 
0268     if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
0269         mKeyForCardKeysButton = new QPushButton(this);
0270         mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key"));
0271         mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card."));
0272         actionLayout->addWidget(mKeyForCardKeysButton);
0273         connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys);
0274     }
0275 
0276     actionLayout->addStretch(-1);
0277     areaVLay->addLayout(actionLayout);
0278 
0279     areaVLay->addStretch(1);
0280 }
0281 
0282 void PGPCardWidget::setCard(const OpenPGPCard *card)
0283 {
0284     const QString version = card->displayAppVersion();
0285 
0286     mIs21 = card->appVersion() >= 0x0201;
0287     const QString manufacturer = QString::fromStdString(card->manufacturer());
0288     const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1StringView("unknown");
0289     mVersionLabel->setText(
0290         manufacturerIsUnknown
0291             ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version)
0292             : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version));
0293     mSerialNumber->setText(card->displaySerialNumber());
0294     mRealSerial = card->serialNumber();
0295 
0296     const auto holder = card->cardHolder();
0297     const auto url = QString::fromStdString(card->pubkeyUrl());
0298     mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder);
0299     mUrl = url;
0300     mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("<a href=\"%1\">%1</a>").arg(url.toHtmlEscaped()));
0301     mUrlLabel->setOpenExternalLinks(true);
0302 
0303     mKeysWidget->update(card);
0304 
0305     mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty()
0306         && card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty();
0307 
0308     if (mKeyForCardKeysButton) {
0309         mKeyForCardKeysButton->setEnabled(card->hasSigningKey() //
0310                                           && card->hasEncryptionKey() //
0311                                           && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
0312                                           && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
0313     }
0314 }
0315 
0316 void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode)
0317 {
0318     auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this);
0319     this->setEnabled(false);
0320     connect(cmd, &ChangePinCommand::finished, this, [this]() {
0321         this->setEnabled(true);
0322     });
0323     cmd->setKeyRef(keyRef);
0324     cmd->setMode(mode);
0325     cmd->start();
0326 }
0327 
0328 void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg)
0329 {
0330     const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName);
0331     if (err) {
0332         return;
0333     }
0334 
0335     const auto params = dlg->getKeyParams();
0336 
0337     auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog);
0338     progress->setAutoClose(true);
0339     progress->setMinimumDuration(0);
0340     progress->setMaximum(0);
0341     progress->setMinimum(0);
0342     progress->setModal(true);
0343     progress->setCancelButton(nullptr);
0344     progress->setWindowTitle(i18nc("@title:window", "Generating Keys"));
0345     progress->setLabel(new QLabel(i18n("This may take several minutes...")));
0346     auto workerThread = new GenKeyThread(params, mRealSerial);
0347     connect(workerThread, &QThread::finished, this, [this, workerThread, progress] {
0348         progress->accept();
0349         progress->deleteLater();
0350         genKeyDone(workerThread->error(), workerThread->bkpFile());
0351         delete workerThread;
0352     });
0353     workerThread->start();
0354     progress->exec();
0355 }
0356 
0357 void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup)
0358 {
0359     if (err) {
0360         KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", Formatting::errorAsString(err)));
0361         return;
0362     }
0363     if (err.isCanceled()) {
0364         return;
0365     }
0366     if (!backup.empty()) {
0367         const auto bkpFile = QString::fromStdString(backup);
0368         QFileInfo fi(bkpFile);
0369         const auto target =
0370             QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key")));
0371         if (!target.isEmpty() && !QFile::copy(bkpFile, target)) {
0372             KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile));
0373         } else if (!target.isEmpty()) {
0374             QFile::remove(bkpFile);
0375         }
0376     }
0377 
0378     KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success"));
0379     ReaderStatus::mutableInstance()->updateStatus();
0380 }
0381 
0382 void PGPCardWidget::genkeyRequested()
0383 {
0384     const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
0385     if (!pgpCard) {
0386         KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
0387         return;
0388     }
0389 
0390     if (!mCardIsEmpty) {
0391         auto ret = KMessageBox::warningContinueCancel(this,
0392                                                       i18n("The existing keys on this card will be <b>deleted</b> "
0393                                                            "and replaced by new keys.")
0394                                                           + QStringLiteral("<br/><br/>")
0395                                                           + i18n("It will no longer be possible to decrypt past communication "
0396                                                                  "encrypted for the existing key."),
0397                                                       i18n("Secret Key Deletion"),
0398                                                       KStandardGuiItem::guiItem(KStandardGuiItem::Delete),
0399                                                       KStandardGuiItem::cancel(),
0400                                                       QString(),
0401                                                       KMessageBox::Notify | KMessageBox::Dangerous);
0402 
0403         if (ret != KMessageBox::Continue) {
0404             return;
0405         }
0406     }
0407 
0408     auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this);
0409     const auto allowedAlgos = getAllowedAlgorithms(pgpCard->supportedAlgorithms());
0410     if (allowedAlgos.empty()) {
0411         KMessageBox::error(this, i18nc("@info", "You cannot generate keys on this smart card because it doesn't support any of the compliant algorithms."));
0412         return;
0413     }
0414     dlg->setSupportedAlgorithms(allowedAlgos, getPreferredAlgorithm(allowedAlgos));
0415     connect(dlg, &QDialog::accepted, this, [this, dlg]() {
0416         doGenKey(dlg);
0417         dlg->deleteLater();
0418     });
0419     dlg->setModal(true);
0420     dlg->show();
0421 }
0422 
0423 void PGPCardWidget::changeNameRequested()
0424 {
0425     QString text = mCardHolderLabel->text();
0426     while (true) {
0427         bool ok = false;
0428         text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly);
0429         if (!ok) {
0430             return;
0431         }
0432         // Some additional restrictions imposed by gnupg
0433         if (text.contains(QLatin1Char('<'))) {
0434             KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used."));
0435             continue;
0436         }
0437         if (text.contains(QLatin1StringView("  "))) {
0438             KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed"));
0439             continue;
0440         }
0441         if (text.size() > 38) {
0442             KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters."));
0443         }
0444         break;
0445     }
0446     auto parts = text.split(QLatin1Char(' '));
0447     const auto lastName = parts.takeLast();
0448     const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<'));
0449 
0450     const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
0451     if (!pgpCard) {
0452         KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
0453         return;
0454     }
0455 
0456     const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8();
0457     ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
0458         changeNameResult(err);
0459     });
0460 }
0461 
0462 void PGPCardWidget::changeNameResult(const GpgME::Error &err)
0463 {
0464     if (err) {
0465         KMessageBox::error(this, i18nc("@info", "Name change failed: %1", Formatting::errorAsString(err)));
0466         return;
0467     }
0468     if (!err.isCanceled()) {
0469         KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success"));
0470         ReaderStatus::mutableInstance()->updateStatus();
0471     }
0472 }
0473 
0474 void PGPCardWidget::changeUrlRequested()
0475 {
0476     QString text = mUrl;
0477     while (true) {
0478         bool ok = false;
0479         text = QInputDialog::getText(this,
0480                                      i18n("Change the URL where the pubkey can be found"),
0481                                      i18n("New pubkey URL:"),
0482                                      QLineEdit::Normal,
0483                                      text,
0484                                      &ok,
0485                                      Qt::WindowFlags(),
0486                                      Qt::ImhLatinOnly);
0487         if (!ok) {
0488             return;
0489         }
0490         // Some additional restrictions imposed by gnupg
0491         if (text.size() > 254) {
0492             KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters."));
0493         }
0494         break;
0495     }
0496 
0497     const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
0498     if (!pgpCard) {
0499         KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
0500         return;
0501     }
0502 
0503     const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8();
0504     ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
0505         changeUrlResult(err);
0506     });
0507 }
0508 
0509 void PGPCardWidget::changeUrlResult(const GpgME::Error &err)
0510 {
0511     if (err) {
0512         KMessageBox::error(this, i18nc("@info", "URL change failed: %1", Formatting::errorAsString(err)));
0513         return;
0514     }
0515     if (!err.isCanceled()) {
0516         KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success"));
0517         ReaderStatus::mutableInstance()->updateStatus();
0518     }
0519 }
0520 
0521 void PGPCardWidget::createKeyFromCardKeys()
0522 {
0523     auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this);
0524     this->setEnabled(false);
0525     connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() {
0526         this->setEnabled(true);
0527     });
0528     cmd->start();
0529 }
0530 
0531 void PGPCardWidget::createCSR(const std::string &keyref)
0532 {
0533     auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this);
0534     this->setEnabled(false);
0535     connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() {
0536         this->setEnabled(true);
0537     });
0538     cmd->start();
0539 }
0540 
0541 void PGPCardWidget::generateKey(const std::string &keyref)
0542 {
0543     auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mRealSerial, this);
0544     this->setEnabled(false);
0545     connect(cmd, &OpenPGPGenerateCardKeyCommand::finished, this, [this]() {
0546         this->setEnabled(true);
0547     });
0548     cmd->start();
0549 }
0550 
0551 #include "pgpcardwidget.moc"
0552 
0553 #include "moc_pgpcardwidget.cpp"