1 /*  view/netkeywidget.cpp
2 
3     This file is part of Kleopatra, the KDE keymanager
4     SPDX-FileCopyrightText: 2017 Intevation GmbH
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 #include "netkeywidget.h"
9 #include "nullpinwidget.h"
10 #include "keytreeview.h"
11 #include "kleopatraapplication.h"
12 #include "systrayicon.h"
13 
14 #include "kleopatra_debug.h"
15 
16 #include "smartcard/netkeycard.h"
17 #include "smartcard/readerstatus.h"
18 
19 #include "commands/changepincommand.h"
20 #include "commands/createopenpgpkeyfromcardkeyscommand.h"
21 #include "commands/createcsrforcardkeycommand.h"
22 #include "commands/learncardkeyscommand.h"
23 #include "commands/detailscommand.h"
24 
25 #include <Libkleo/KeyListModel>
26 
27 #include <KConfigGroup>
28 #include <KSharedConfig>
29 
30 #include <QInputDialog>
31 #include <QLabel>
32 #include <QVBoxLayout>
33 #include <QHBoxLayout>
34 #include <QScrollArea>
35 #include <QPushButton>
36 #include <QTreeView>
37 
38 #include <KLocalizedString>
39 #include <KMessageBox>
40 
41 #include <gpgme++/context.h>
42 #include <gpgme++/engineinfo.h>
43 
44 using namespace Kleo;
45 using namespace Kleo::SmartCard;
46 using namespace Kleo::Commands;
47 
NetKeyWidget(QWidget * parent)48 NetKeyWidget::NetKeyWidget(QWidget *parent) :
49     QWidget(parent),
50     mSerialNumberLabel(new QLabel(this)),
51     mVersionLabel(new QLabel(this)),
52     mLearnKeysLabel(new QLabel(this)),
53     mErrorLabel(new QLabel(this)),
54     mNullPinWidget(new NullPinWidget(this)),
55     mLearnKeysBtn(new QPushButton(this)),
56     mChangeNKSPINBtn(new QPushButton(this)),
57     mChangeSigGPINBtn(new QPushButton(this)),
58     mTreeView(new KeyTreeView(this)),
59     mArea(new QScrollArea)
60 {
61     auto vLay = new QVBoxLayout;
62 
63     // Set up the scroll are
64     mArea->setFrameShape(QFrame::NoFrame);
65     mArea->setWidgetResizable(true);
66     auto mAreaWidget = new QWidget;
67     mAreaWidget->setLayout(vLay);
68     mArea->setWidget(mAreaWidget);
69     auto scrollLay = new QVBoxLayout(this);
70     scrollLay->setContentsMargins(0, 0, 0, 0);
71     scrollLay->addWidget(mArea);
72 
73     // Add general widgets
74     mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
75     vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft);
76 
77     mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
78 
79     auto hLay1 = new QHBoxLayout;
80     hLay1->addWidget(new QLabel(i18n("Serial number:")));
81     hLay1->addWidget(mSerialNumberLabel);
82     hLay1->addStretch(1);
83     vLay->addLayout(hLay1);
84 
85     vLay->addWidget(mNullPinWidget);
86 
87 
88     auto line1 = new QFrame();
89     line1->setFrameShape(QFrame::HLine);
90     vLay->addWidget(line1);
91     vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Certificates:"))), 0, Qt::AlignLeft);
92 
93     mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card."));
94     mLearnKeysBtn->setText(i18nc("@action", "Load Certificates"));
95     connect(mLearnKeysBtn, &QPushButton::clicked, this, [this] () {
96         mLearnKeysBtn->setEnabled(false);
97         auto cmd = new LearnCardKeysCommand(GpgME::CMS);
98         cmd->setParentWidget(this);
99         cmd->start();
100 
101         auto icon = KleopatraApplication::instance()->sysTrayIcon();
102         if (icon) {
103             icon->setLearningInProgress(true);
104         }
105 
106         connect(cmd, &Command::finished, this, [icon] () {
107             ReaderStatus::mutableInstance()->updateStatus();
108             icon->setLearningInProgress(false);
109         });
110     });
111 
112     auto hLay2 = new QHBoxLayout;
113     hLay2->addWidget(mLearnKeysLabel);
114     hLay2->addWidget(mLearnKeysBtn);
115     hLay2->addStretch(1);
116     vLay->addLayout(hLay2);
117 
118     mErrorLabel->setVisible(false);
119     vLay->addWidget(mErrorLabel);
120 
121     // The certificate view
122     mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView));
123     mTreeView->setHierarchicalView(true);
124 
125     connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this] (const QModelIndex &idx) {
126         const auto klm = dynamic_cast<KeyListModelInterface *> (mTreeView->view()->model());
127         if (!klm) {
128             qCDebug(KLEOPATRA_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className();
129             return;
130         }
131         auto cmd = new DetailsCommand(klm->key(idx), nullptr);
132         cmd->setParentWidget(this);
133         cmd->start();
134     });
135     vLay->addWidget(mTreeView);
136 
137 
138     // The action area
139     auto line2 = new QFrame();
140     line2->setFrameShape(QFrame::HLine);
141     vLay->addWidget(line2);
142     vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))), 0, Qt::AlignLeft);
143 
144     auto actionLayout = new QHBoxLayout();
145 
146     if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
147         mKeyForCardKeysButton = new QPushButton(this);
148         mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
149         mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
150         actionLayout->addWidget(mKeyForCardKeysButton);
151         connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys);
152     }
153 
154     if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184
155         mCreateCSRButton = new QPushButton(this);
156         mCreateCSRButton->setText(i18nc("@action:button", "Create CSR"));
157         mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card."));
158         mCreateCSRButton->setEnabled(false);
159         actionLayout->addWidget(mCreateCSRButton);
160         connect(mCreateCSRButton, &QPushButton::clicked, this, [this] () { createCSR(); });
161     }
162 
163     mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN"));
164     mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
165 
166     connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::nksPinKeyRef()); });
167     connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::sigGPinKeyRef()); });
168 
169     actionLayout->addWidget(mChangeNKSPINBtn);
170     actionLayout->addWidget(mChangeSigGPINBtn);
171     actionLayout->addStretch(1);
172 
173     vLay->addLayout(actionLayout);
174     vLay->addStretch(1);
175 
176     const KConfigGroup configGroup(KSharedConfig::openConfig(), "NetKeyCardView");
177     mTreeView->restoreLayout(configGroup);
178 }
179 
~NetKeyWidget()180 NetKeyWidget::~NetKeyWidget()
181 {
182     KConfigGroup configGroup(KSharedConfig::openConfig(), "NetKeyCardView");
183     mTreeView->saveLayout(configGroup);
184 }
185 
186 namespace
187 {
getKeysSuitableForCSRCreation(const NetKeyCard * netKeyCard)188 std::vector<KeyPairInfo> getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard)
189 {
190     std::vector<KeyPairInfo> keys;
191 
192     for (const auto &key : netKeyCard->keyInfos()) {
193         if (key.keyRef.substr(0, 9) == "NKS-SIGG.") {
194             // SigG certificates for qualified signatures are issued with the physical cards;
195             // it's not possible to request a certificate for them
196             continue;
197         }
198         if (key.canSign() && (key.keyRef.substr(0, 9) == "NKS-NKS3.") && !netKeyCard->hasNKSNullPin()) {
199             keys.push_back(key);
200         }
201     }
202 
203     return keys;
204 }
205 }
206 
setCard(const NetKeyCard * card)207 void NetKeyWidget::setCard(const NetKeyCard* card)
208 {
209     mSerialNumber = card->serialNumber();
210     mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion()));
211     mSerialNumberLabel->setText(card->displaySerialNumber());
212 
213     mNullPinWidget->setSerialNumber(mSerialNumber);
214     /* According to users of NetKey Cards it is fairly uncommon
215      * to use SigG Certificates at all. So it should be optional to set the pins. */
216     mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/);
217 
218     mNullPinWidget->setSigGVisible(false/*card->hasSigGNullPin()*/);
219     mNullPinWidget->setNKSVisible(card->hasNKSNullPin());
220     mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin());
221 
222     if (card->hasSigGNullPin()) {
223         mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card",
224                                    "Set SigG PIN"));
225     } else {
226         mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card",
227                                   "Change SigG PIN"));
228     }
229 
230     mLearnKeysBtn->setEnabled(true);
231     mLearnKeysBtn->setVisible(card->canLearnKeys());
232     mTreeView->setVisible(!card->canLearnKeys());
233     mLearnKeysLabel->setVisible(card->canLearnKeys());
234 
235     const auto errMsg = card->errorMsg();
236     if (!errMsg.isEmpty()) {
237         mErrorLabel->setText(QStringLiteral("<b>%1:</b> %2").arg(i18n("Error"), errMsg));
238         mErrorLabel->setVisible(true);
239     } else {
240         mErrorLabel->setVisible(false);
241     }
242 
243     const auto keys = card->keys();
244     mTreeView->setKeys(keys);
245 
246     if (mKeyForCardKeysButton) {
247         mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin() && card->hasSigningKey() && card->hasEncryptionKey());
248     }
249     if (mCreateCSRButton) {
250         mCreateCSRButton->setEnabled(!getKeysSuitableForCSRCreation(card).empty());
251     }
252 }
253 
doChangePin(const std::string & keyRef)254 void NetKeyWidget::doChangePin(const std::string &keyRef)
255 {
256     const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
257     if (!netKeyCard) {
258         KMessageBox::error(this,
259                            i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
260         return;
261     }
262 
263     auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this);
264     this->setEnabled(false);
265     connect(cmd, &ChangePinCommand::finished,
266             this, [this]() {
267                 this->setEnabled(true);
268             });
269     cmd->setKeyRef(keyRef);
270     if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin())
271         || (keyRef == NetKeyCard::sigGPinKeyRef() && netKeyCard->hasSigGNullPin())) {
272         cmd->setMode(ChangePinCommand::NullPinMode);
273     }
274     cmd->start();
275 }
276 
createKeyFromCardKeys()277 void NetKeyWidget::createKeyFromCardKeys()
278 {
279     auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, NetKeyCard::AppName, this);
280     this->setEnabled(false);
281     connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished,
282             this, [this]() {
283                 this->setEnabled(true);
284             });
285     cmd->start();
286 }
287 
288 namespace
289 {
getKeyRef(const std::vector<KeyPairInfo> & keys,QWidget * parent)290 std::string getKeyRef(const std::vector<KeyPairInfo> &keys, QWidget *parent)
291 {
292     QStringList options;
293     for (const auto &key : keys) {
294         options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip));
295     }
296 
297     bool ok;
298     const QString choice = QInputDialog::getItem(parent, i18n("Select Key"),
299         i18n("Please select the key you want to create a certificate signing request for:"), options, /* current= */ 0, /* editable= */ false, &ok);
300     return ok ? keys[options.indexOf(choice)].keyRef : std::string();
301 }
302 }
303 
createCSR()304 void NetKeyWidget::createCSR()
305 {
306     const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
307     if (!netKeyCard) {
308         KMessageBox::error(this,
309                            i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
310         return;
311     }
312     const auto suitableKeys = getKeysSuitableForCSRCreation(netKeyCard.get());
313     if (suitableKeys.empty()) {
314         KMessageBox::error(this,
315                            i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard."));
316         return;
317     }
318     const auto keyRef = getKeyRef(suitableKeys, this);
319     if (keyRef.empty()) {
320         return;
321     }
322     auto cmd = new CreateCSRForCardKeyCommand(keyRef, mSerialNumber, NetKeyCard::AppName, this);
323     this->setEnabled(false);
324     connect(cmd, &CreateCSRForCardKeyCommand::finished,
325             this, [this]() {
326                 this->setEnabled(true);
327             });
328     cmd->start();
329 }
330