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

0001 /*  view/netkeywidget.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2017 Intevation GmbH
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 #include "netkeywidget.h"
0009 
0010 #include "keytreeview.h"
0011 #include "kleopatraapplication.h"
0012 #include "nullpinwidget.h"
0013 #include "systrayicon.h"
0014 
0015 #include "kleopatra_debug.h"
0016 
0017 #include "smartcard/netkeycard.h"
0018 #include "smartcard/readerstatus.h"
0019 
0020 #include "commands/changepincommand.h"
0021 #include "commands/createcsrforcardkeycommand.h"
0022 #include "commands/createopenpgpkeyfromcardkeyscommand.h"
0023 #include "commands/detailscommand.h"
0024 #include "commands/learncardkeyscommand.h"
0025 
0026 #include <Libkleo/Algorithm>
0027 #include <Libkleo/Compliance>
0028 #include <Libkleo/KeyListModel>
0029 
0030 #include <KConfigGroup>
0031 #include <KSharedConfig>
0032 
0033 #include <QHBoxLayout>
0034 #include <QInputDialog>
0035 #include <QLabel>
0036 #include <QPushButton>
0037 #include <QScrollArea>
0038 #include <QTreeView>
0039 #include <QVBoxLayout>
0040 
0041 #include <KLocalizedString>
0042 #include <KMessageBox>
0043 
0044 #include <gpgme++/context.h>
0045 #include <gpgme++/engineinfo.h>
0046 
0047 using namespace Kleo;
0048 using namespace Kleo::SmartCard;
0049 using namespace Kleo::Commands;
0050 
0051 NetKeyWidget::NetKeyWidget(QWidget *parent)
0052     : QWidget(parent)
0053     , mSerialNumberLabel(new QLabel(this))
0054     , mVersionLabel(new QLabel(this))
0055     , mLearnKeysLabel(new QLabel(this))
0056     , mErrorLabel(new QLabel(this))
0057     , mNullPinWidget(new NullPinWidget(this))
0058     , mLearnKeysBtn(new QPushButton(this))
0059     , mChangeNKSPINBtn(new QPushButton(this))
0060     , mChangeSigGPINBtn(new QPushButton(this))
0061     , mTreeView(new KeyTreeView(this))
0062     , mArea(new QScrollArea)
0063 {
0064     auto vLay = new QVBoxLayout;
0065 
0066     // Set up the scroll are
0067     mArea->setFrameShape(QFrame::NoFrame);
0068     mArea->setWidgetResizable(true);
0069     auto mAreaWidget = new QWidget;
0070     mAreaWidget->setLayout(vLay);
0071     mArea->setWidget(mAreaWidget);
0072     auto scrollLay = new QVBoxLayout(this);
0073     scrollLay->setContentsMargins(0, 0, 0, 0);
0074     scrollLay->addWidget(mArea);
0075 
0076     // Add general widgets
0077     mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0078     vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft);
0079 
0080     mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0081 
0082     auto hLay1 = new QHBoxLayout;
0083     hLay1->addWidget(new QLabel(i18n("Serial number:")));
0084     hLay1->addWidget(mSerialNumberLabel);
0085     hLay1->addStretch(1);
0086     vLay->addLayout(hLay1);
0087 
0088     vLay->addWidget(mNullPinWidget);
0089 
0090     auto line1 = new QFrame();
0091     line1->setFrameShape(QFrame::HLine);
0092     vLay->addWidget(line1);
0093     vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Certificates:"))), 0, Qt::AlignLeft);
0094 
0095     mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card."));
0096     mLearnKeysBtn->setText(i18nc("@action", "Load Certificates"));
0097     connect(mLearnKeysBtn, &QPushButton::clicked, this, [this]() {
0098         mLearnKeysBtn->setEnabled(false);
0099         auto cmd = new LearnCardKeysCommand(GpgME::CMS);
0100         cmd->setParentWidget(this);
0101         cmd->start();
0102 
0103         auto icon = KleopatraApplication::instance()->sysTrayIcon();
0104         if (icon) {
0105             icon->setLearningInProgress(true);
0106         }
0107 
0108         connect(cmd, &Command::finished, this, [icon]() {
0109             ReaderStatus::mutableInstance()->updateStatus();
0110             icon->setLearningInProgress(false);
0111         });
0112     });
0113 
0114     auto hLay2 = new QHBoxLayout;
0115     hLay2->addWidget(mLearnKeysLabel);
0116     hLay2->addWidget(mLearnKeysBtn);
0117     hLay2->addStretch(1);
0118     vLay->addLayout(hLay2);
0119 
0120     mErrorLabel->setVisible(false);
0121     vLay->addWidget(mErrorLabel);
0122 
0123     // The certificate view
0124     mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView));
0125     mTreeView->setHierarchicalView(true);
0126 
0127     connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &idx) {
0128         const auto klm = dynamic_cast<KeyListModelInterface *>(mTreeView->view()->model());
0129         if (!klm) {
0130             qCDebug(KLEOPATRA_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className();
0131             return;
0132         }
0133         auto cmd = new DetailsCommand(klm->key(idx));
0134         cmd->setParentWidget(this);
0135         cmd->start();
0136     });
0137     vLay->addWidget(mTreeView);
0138 
0139     // The action area
0140     auto line2 = new QFrame();
0141     line2->setFrameShape(QFrame::HLine);
0142     vLay->addWidget(line2);
0143     vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))), 0, Qt::AlignLeft);
0144 
0145     auto actionLayout = new QHBoxLayout();
0146 
0147     if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
0148         mKeyForCardKeysButton = new QPushButton(this);
0149         mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
0150         mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
0151         actionLayout->addWidget(mKeyForCardKeysButton);
0152         connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys);
0153     }
0154 
0155     if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184
0156         mCreateCSRButton = new QPushButton(this);
0157         mCreateCSRButton->setText(i18nc("@action:button", "Create CSR"));
0158         mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card."));
0159         mCreateCSRButton->setEnabled(false);
0160         actionLayout->addWidget(mCreateCSRButton);
0161         connect(mCreateCSRButton, &QPushButton::clicked, this, [this]() {
0162             createCSR();
0163         });
0164     }
0165 
0166     mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN"));
0167     mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
0168 
0169     connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this]() {
0170         doChangePin(NetKeyCard::nksPinKeyRef());
0171     });
0172     connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this]() {
0173         doChangePin(NetKeyCard::sigGPinKeyRef());
0174     });
0175 
0176     actionLayout->addWidget(mChangeNKSPINBtn);
0177     actionLayout->addWidget(mChangeSigGPINBtn);
0178     actionLayout->addStretch(1);
0179 
0180     vLay->addLayout(actionLayout);
0181     vLay->addStretch(1);
0182 
0183     const KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("NetKeyCardView"));
0184     mTreeView->restoreLayout(configGroup);
0185 }
0186 
0187 NetKeyWidget::~NetKeyWidget() = default;
0188 
0189 namespace
0190 {
0191 std::vector<KeyPairInfo> getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard)
0192 {
0193     if (netKeyCard->hasNKSNullPin()) {
0194         return {};
0195     }
0196 
0197     std::vector<KeyPairInfo> keys;
0198     Kleo::copy_if(netKeyCard->keyInfos(), std::back_inserter(keys), [](const auto &keyInfo) {
0199         if (keyInfo.keyRef.substr(0, 9) == "NKS-SIGG.") {
0200             // SigG certificates for qualified signatures are issued with the physical cards;
0201             // it's not possible to request a certificate for them
0202             return false;
0203         }
0204         return keyInfo.canSign() //
0205             && (keyInfo.keyRef.substr(0, 9) == "NKS-NKS3.") //
0206             && DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm);
0207     });
0208 
0209     return keys;
0210 }
0211 }
0212 
0213 void NetKeyWidget::setCard(const NetKeyCard *card)
0214 {
0215     mSerialNumber = card->serialNumber();
0216     mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion()));
0217     mSerialNumberLabel->setText(card->displaySerialNumber());
0218 
0219     mNullPinWidget->setSerialNumber(mSerialNumber);
0220     /* According to users of NetKey Cards it is fairly uncommon
0221      * to use SigG Certificates at all. So it should be optional to set the pins. */
0222     mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/);
0223 
0224     mNullPinWidget->setSigGVisible(false /*card->hasSigGNullPin()*/);
0225     mNullPinWidget->setNKSVisible(card->hasNKSNullPin());
0226     mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin());
0227 
0228     if (card->hasSigGNullPin()) {
0229         mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN"));
0230     } else {
0231         mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
0232     }
0233 
0234     const auto keys = card->keys();
0235     mLearnKeysBtn->setEnabled(true);
0236     mLearnKeysBtn->setVisible(card->canLearnKeys());
0237     mTreeView->setVisible(!keys.empty());
0238     mLearnKeysLabel->setVisible(keys.empty());
0239 
0240     const auto errMsg = card->errorMsg();
0241     if (!errMsg.isEmpty()) {
0242         mErrorLabel->setText(QStringLiteral("<b>%1:</b> %2").arg(i18n("Error"), errMsg));
0243         mErrorLabel->setVisible(true);
0244     } else {
0245         mErrorLabel->setVisible(false);
0246     }
0247 
0248     mTreeView->setKeys(keys);
0249 
0250     if (mKeyForCardKeysButton) {
0251         mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin() && card->hasSigningKey() && card->hasEncryptionKey()
0252                                           && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
0253                                           && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
0254     }
0255     if (mCreateCSRButton) {
0256         mCreateCSRButton->setEnabled(!getKeysSuitableForCSRCreation(card).empty());
0257     }
0258 }
0259 
0260 void NetKeyWidget::doChangePin(const std::string &keyRef)
0261 {
0262     const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
0263     if (!netKeyCard) {
0264         KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
0265         return;
0266     }
0267 
0268     auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this);
0269     this->setEnabled(false);
0270     connect(cmd, &ChangePinCommand::finished, this, [this]() {
0271         this->setEnabled(true);
0272     });
0273     cmd->setKeyRef(keyRef);
0274     if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin()) //
0275         || (keyRef == NetKeyCard::sigGPinKeyRef() && netKeyCard->hasSigGNullPin())) {
0276         cmd->setMode(ChangePinCommand::NullPinMode);
0277     }
0278     cmd->start();
0279 }
0280 
0281 void NetKeyWidget::createKeyFromCardKeys()
0282 {
0283     auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, NetKeyCard::AppName, this);
0284     this->setEnabled(false);
0285     connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() {
0286         this->setEnabled(true);
0287     });
0288     cmd->start();
0289 }
0290 
0291 namespace
0292 {
0293 std::string getKeyRef(const std::vector<KeyPairInfo> &keys, QWidget *parent)
0294 {
0295     QStringList options;
0296     for (const auto &key : keys) {
0297         options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip));
0298     }
0299 
0300     bool ok;
0301     const QString choice = QInputDialog::getItem(parent,
0302                                                  i18n("Select Key"),
0303                                                  i18n("Please select the key you want to create a certificate signing request for:"),
0304                                                  options,
0305                                                  /* current= */ 0,
0306                                                  /* editable= */ false,
0307                                                  &ok);
0308     return ok ? keys[options.indexOf(choice)].keyRef : std::string();
0309 }
0310 }
0311 
0312 void NetKeyWidget::createCSR()
0313 {
0314     const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
0315     if (!netKeyCard) {
0316         KMessageBox::error(this, i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
0317         return;
0318     }
0319     const auto suitableKeys = getKeysSuitableForCSRCreation(netKeyCard.get());
0320     if (suitableKeys.empty()) {
0321         KMessageBox::error(this, i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard."));
0322         return;
0323     }
0324     const auto keyRef = getKeyRef(suitableKeys, this);
0325     if (keyRef.empty()) {
0326         return;
0327     }
0328     auto cmd = new CreateCSRForCardKeyCommand(keyRef, mSerialNumber, NetKeyCard::AppName, this);
0329     this->setEnabled(false);
0330     connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() {
0331         this->setEnabled(true);
0332     });
0333     cmd->start();
0334 }
0335 
0336 #include "moc_netkeywidget.cpp"