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