Changeset View
Changeset View
Standalone View
Standalone View
src/dialogs/certificatedetailswidget.cpp
- This file was added.
1 | /* Copyright (c) 2016 Klarälvdalens Datakonsult AB | ||||
---|---|---|---|---|---|
2 | | ||||
3 | Kleopatra is free software; you can redistribute it and/or modify | ||||
4 | it under the terms of the GNU General Public License as published by | ||||
5 | the Free Software Foundation; either version 2 of the License, or | ||||
6 | (at your option) any later version. | ||||
7 | | ||||
8 | Kleopatra is distributed in the hope that it will be useful, | ||||
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
11 | General Public License for more details. | ||||
12 | | ||||
13 | You should have received a copy of the GNU General Public License | ||||
14 | along with this program; if not, write to the Free Software | ||||
15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||||
16 | */ | ||||
17 | | ||||
18 | #include "certificatedetailswidget.h" | ||||
19 | #include "ui_certificatedetailswidget.h" | ||||
20 | #include "kleopatra_debug.h" | ||||
21 | #include "trustchainwidget.h" | ||||
22 | #include "subkeyswidget.h" | ||||
23 | | ||||
24 | #include "commands/changepassphrasecommand.h" | ||||
25 | #include "commands/changeexpirycommand.h" | ||||
26 | #include "commands/adduseridcommand.h" | ||||
27 | #include "commands/dumpcertificatecommand.h" | ||||
28 | | ||||
29 | #include <libkleo/formatting.h> | ||||
30 | #include <libkleo/dn.h> | ||||
31 | #include <libkleo/keycache.h> | ||||
32 | | ||||
33 | #include <gpgme++/key.h> | ||||
34 | #include <gpgme++/tofuinfo.h> | ||||
35 | | ||||
36 | #include <QStandardItemModel> | ||||
37 | #include <QToolTip> | ||||
38 | #include <QDateTime> | ||||
39 | #include <QDialogButtonBox> | ||||
40 | #include <KConfigGroup> | ||||
41 | #include <KSharedConfig> | ||||
42 | | ||||
43 | #define HIDE_ROW(row) \ | ||||
44 | ui.row->setVisible(false); \ | ||||
45 | ui.row##Lbl->setVisible(false); | ||||
46 | | ||||
47 | class CertificateDetailsWidget::Private | ||||
48 | { | ||||
49 | public: | ||||
50 | Private(CertificateDetailsWidget *parent) | ||||
51 | : q(parent) | ||||
52 | {} | ||||
53 | | ||||
54 | void setupCommonProperties(); | ||||
55 | void setupPGPProperties(); | ||||
56 | void setupSMIMEProperties(); | ||||
57 | | ||||
58 | void revokeUID(const GpgME::UserID &uid); | ||||
59 | void addUserID(); | ||||
60 | void changePassphrase(); | ||||
61 | void changeExpiration(); | ||||
62 | void keysMayHaveChanged(); | ||||
63 | void showTrustChainDialog(); | ||||
64 | void showMoreDetails(); | ||||
65 | void publishCertificate(); | ||||
66 | | ||||
67 | QString tofuTooltipString(const GpgME::UserID &uid) const; | ||||
68 | | ||||
69 | void smimeLinkActivated(const QString &link); | ||||
70 | | ||||
71 | Ui::CertificateDetailsWidget ui; | ||||
72 | GpgME::Key key; | ||||
73 | private: | ||||
74 | CertificateDetailsWidget *q; | ||||
75 | }; | ||||
76 | | ||||
77 | void CertificateDetailsWidget::Private::setupCommonProperties() | ||||
78 | { | ||||
79 | const bool hasSecret = key.hasSecret(); | ||||
80 | const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; | ||||
81 | // TODO: Enable once implemented | ||||
82 | const bool canRevokeUID = false; // isOpenPGP && hasSecret | ||||
83 | | ||||
84 | ui.changePassphraseBtn->setVisible(hasSecret); | ||||
85 | ui.changeExpirationBtn->setVisible(isOpenPGP && hasSecret); | ||||
86 | ui.addUserIDBtn->setVisible(hasSecret); | ||||
87 | | ||||
88 | ui.validFrom->setText(Kleo::Formatting::creationDateString(key)); | ||||
89 | const QString expiry = Kleo::Formatting::expirationDateString(key); | ||||
90 | ui.expires->setText(expiry.isEmpty() ? i18nc("Expires", "never") : expiry); | ||||
91 | ui.type->setText(Kleo::Formatting::type(key)); | ||||
92 | ui.fingerprint->setText(QString::fromLatin1(key.primaryFingerprint())); | ||||
93 | | ||||
94 | ui.userIDTable->clear(); | ||||
95 | | ||||
96 | QStringList headers = { i18n("Email"), i18n("Name") }; | ||||
97 | if (isOpenPGP) { | ||||
98 | headers << i18n("Trust Level"); | ||||
99 | if (canRevokeUID) { | ||||
100 | headers << QString(); | ||||
101 | } | ||||
102 | } | ||||
103 | ui.userIDTable->setColumnCount(headers.count()); | ||||
104 | ui.userIDTable->setColumnWidth(0, 200); | ||||
105 | ui.userIDTable->setColumnWidth(1, 200); | ||||
106 | ui.userIDTable->setHeaderLabels(headers); | ||||
107 | | ||||
108 | const auto uids = key.userIDs(); | ||||
109 | for (const auto &uid : uids) { | ||||
110 | auto item = new QTreeWidgetItem; | ||||
111 | const QString toolTip = tofuTooltipString(uid); | ||||
112 | item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyEMail(uid)); | ||||
113 | item->setData(0, Qt::ToolTipRole, toolTip); | ||||
114 | item->setData(1, Qt::DisplayRole, Kleo::Formatting::prettyName(uid)); | ||||
115 | item->setData(1, Qt::ToolTipRole, toolTip); | ||||
116 | if (isOpenPGP) { | ||||
117 | QIcon trustIcon; | ||||
118 | switch (uid.validity()) { | ||||
119 | case GpgME::UserID::Unknown: | ||||
120 | case GpgME::UserID::Undefined: | ||||
121 | trustIcon = QIcon::fromTheme(QStringLiteral("emblem-question")); | ||||
122 | break; | ||||
123 | case GpgME::UserID::Never: | ||||
124 | trustIcon = QIcon::fromTheme(QStringLiteral("emblem-error")); | ||||
125 | break; | ||||
126 | case GpgME::UserID::Marginal: | ||||
127 | trustIcon = QIcon::fromTheme(QStringLiteral("emblem-warning")); | ||||
128 | break; | ||||
129 | case GpgME::UserID::Full: | ||||
130 | case GpgME::UserID::Ultimate: | ||||
131 | trustIcon = QIcon::fromTheme(QStringLiteral("emblem-success")); | ||||
132 | break; | ||||
133 | } | ||||
134 | item->setData(2, Qt::DecorationRole, trustIcon); | ||||
135 | item->setData(2, Qt::DisplayRole, Kleo::Formatting::validityShort(uid)); | ||||
136 | item->setData(2, Qt::ToolTipRole, toolTip); | ||||
137 | | ||||
138 | ui.userIDTable->addTopLevelItem(item); | ||||
139 | | ||||
140 | if (canRevokeUID) { | ||||
141 | auto button = new QPushButton; | ||||
142 | button->setIcon(QIcon::fromTheme(QStringLiteral("entry-delete"))); | ||||
143 | button->setToolTip(i18n("Revoke this User ID")); | ||||
144 | button->setMaximumWidth(32); | ||||
145 | QObject::connect(button, &QPushButton::clicked, | ||||
146 | q, [this, uid]() { revokeUID(uid); }); | ||||
147 | ui.userIDTable->setItemWidget(item, 4, button); | ||||
148 | } | ||||
149 | } else { | ||||
150 | ui.userIDTable->addTopLevelItem(item); | ||||
151 | } | ||||
152 | } | ||||
153 | } | ||||
154 | | ||||
155 | void CertificateDetailsWidget::Private::revokeUID(const GpgME::UserID &uid) | ||||
156 | { | ||||
157 | Q_UNUSED(uid); | ||||
158 | qCWarning(KLEOPATRA_LOG) << "Revoking UserID is not implemented. How did you even get here?!?!"; | ||||
159 | } | ||||
160 | | ||||
161 | void CertificateDetailsWidget::Private::changeExpiration() | ||||
162 | { | ||||
163 | auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); | ||||
164 | QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, | ||||
165 | q, [this]() { | ||||
166 | ui.changeExpirationBtn->setEnabled(true); | ||||
167 | }); | ||||
168 | ui.changeExpirationBtn->setEnabled(false); | ||||
169 | cmd->start(); | ||||
170 | } | ||||
171 | | ||||
172 | void CertificateDetailsWidget::Private::changePassphrase() | ||||
173 | { | ||||
174 | auto cmd = new Kleo::Commands::ChangePassphraseCommand(key); | ||||
175 | QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished, | ||||
176 | q, [this, cmd]() { | ||||
177 | ui.changePassphraseBtn->setEnabled(true); | ||||
178 | }); | ||||
179 | ui.changePassphraseBtn->setEnabled(false); | ||||
180 | cmd->start(); | ||||
181 | } | ||||
182 | | ||||
183 | void CertificateDetailsWidget::Private::addUserID() | ||||
184 | { | ||||
185 | auto cmd = new Kleo::Commands::AddUserIDCommand(key); | ||||
186 | QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, | ||||
187 | q, [this, cmd]() { | ||||
188 | ui.addUserIDBtn->setEnabled(true); | ||||
189 | }); | ||||
190 | ui.addUserIDBtn->setEnabled(false); | ||||
191 | cmd->start(); | ||||
192 | } | ||||
193 | | ||||
194 | void CertificateDetailsWidget::Private::keysMayHaveChanged() | ||||
195 | { | ||||
196 | auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); | ||||
197 | if (!newKey.isNull()) { | ||||
198 | q->setKey(newKey); | ||||
199 | } | ||||
200 | } | ||||
201 | | ||||
202 | void CertificateDetailsWidget::Private::showTrustChainDialog() | ||||
203 | { | ||||
204 | QScopedPointer<TrustChainDialog> dlg(new TrustChainDialog(q)); | ||||
205 | dlg->setKey(key); | ||||
206 | dlg->exec(); | ||||
207 | } | ||||
208 | | ||||
209 | void CertificateDetailsWidget::Private::publishCertificate() | ||||
210 | { | ||||
211 | qCWarning(KLEOPATRA_LOG) << "publishCertificateis not implemented."; | ||||
212 | //TODO | ||||
213 | } | ||||
214 | | ||||
215 | void CertificateDetailsWidget::Private::showMoreDetails() | ||||
216 | { | ||||
217 | ui.moreDetailsBtn->setEnabled(false); | ||||
218 | if (key.protocol() == GpgME::CMS) { | ||||
219 | auto cmd = new Kleo::Commands::DumpCertificateCommand(key); | ||||
220 | connect(cmd, &Kleo::Commands::DumpCertificateCommand::finished, | ||||
221 | q, [this]() { | ||||
222 | ui.moreDetailsBtn->setEnabled(true); | ||||
223 | }); | ||||
224 | cmd->setUseDialog(true); | ||||
225 | cmd->start(); | ||||
226 | } else { | ||||
227 | QScopedPointer<SubKeysDialog> dlg(new SubKeysDialog(q)); | ||||
228 | dlg->setKey(key); | ||||
229 | dlg->exec(); | ||||
230 | ui.moreDetailsBtn->setEnabled(true); | ||||
231 | } | ||||
232 | } | ||||
233 | | ||||
234 | QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const | ||||
235 | { | ||||
236 | const auto tofu = uid.tofuInfo(); | ||||
237 | if (tofu.isNull()) { | ||||
238 | return QString(); | ||||
239 | } | ||||
240 | | ||||
241 | QString html = QStringLiteral("<table border=\"0\" cell-padding=\"5\">"); | ||||
242 | const auto appendRow = [&html](const QString &lbl, const QString &val) { | ||||
243 | html += QStringLiteral("<tr>" | ||||
244 | "<th style=\"text-align: right; padding-right: 5px; white-space: nowrap;\">%1:</th>" | ||||
245 | "<td style=\"white-space: nowrap;\">%2</td>" | ||||
246 | "</tr>") | ||||
247 | .arg(lbl, val); | ||||
248 | }; | ||||
249 | const auto appendHeader = [this, &html](const QString &hdr) { | ||||
250 | html += QStringLiteral("<tr><th colspan=\"2\" style=\"background-color: %1; color: %2\">%3</th></tr>") | ||||
251 | .arg(q->palette().highlight().color().name(), | ||||
252 | q->palette().highlightedText().color().name(), | ||||
253 | hdr); | ||||
254 | }; | ||||
255 | const auto dateTime = [](long ts) { | ||||
256 | return ts == 0 ? i18n("never") : QDateTime::fromTime_t(ts).toString(Qt::SystemLocaleShortDate); | ||||
257 | }; | ||||
258 | appendHeader(i18n("Signing")); | ||||
259 | appendRow(i18n("First message"), dateTime(tofu.signFirst())); | ||||
260 | appendRow(i18n("Last message"), dateTime(tofu.signLast())); | ||||
261 | appendRow(i18n("Message count"), QString::number(tofu.signCount())); | ||||
262 | appendHeader(i18n("Encryption")); | ||||
263 | appendRow(i18n("First message"), dateTime(tofu.encrFirst())); | ||||
264 | appendRow(i18n("Last message"), dateTime(tofu.encrLast())); | ||||
265 | appendRow(i18n("Message count"), QString::number(tofu.encrCount())); | ||||
266 | | ||||
267 | html += QStringLiteral("</table>"); | ||||
268 | return html; | ||||
269 | } | ||||
270 | | ||||
271 | | ||||
272 | void CertificateDetailsWidget::Private::setupPGPProperties() | ||||
273 | { | ||||
274 | HIDE_ROW(smimeOwner) | ||||
275 | HIDE_ROW(smimeIssuer) | ||||
276 | ui.smimeRelatedAddresses->setVisible(false); | ||||
277 | ui.trustChainDetailsBtn->setVisible(false); | ||||
278 | } | ||||
279 | | ||||
280 | void CertificateDetailsWidget::Private::setupSMIMEProperties() | ||||
281 | { | ||||
282 | HIDE_ROW(publishing) | ||||
283 | | ||||
284 | const auto ownerId = key.userID(0); | ||||
285 | const Kleo::DN dn(ownerId.id()); | ||||
286 | const QString cn = dn[QStringLiteral("CN")]; | ||||
287 | const QString o = dn[QStringLiteral("O")]; | ||||
288 | const QString dnEmail = dn[QStringLiteral("EMAIL")]; | ||||
289 | const QString name = cn.isEmpty() ? dnEmail : cn; | ||||
290 | | ||||
291 | QString owner; | ||||
292 | if (name.isEmpty()) { | ||||
293 | owner = dn.dn(); | ||||
294 | } else if (o.isEmpty()) { | ||||
295 | owner = name; | ||||
296 | } else { | ||||
297 | owner = i18nc("<name> of <company>", "%1 of %2", name, o); | ||||
298 | } | ||||
299 | ui.smimeOwner->setText(QStringLiteral("<a href=\"#ownerDetails\">%1</a>").arg(owner)); | ||||
300 | | ||||
301 | const Kleo::DN issuerDN(key.issuerName()); | ||||
302 | const QString issuerCN = issuerDN[QStringLiteral("CN")]; | ||||
303 | const QString issuer = issuerCN.isEmpty() ? key.issuerName() : issuerCN; | ||||
304 | ui.smimeIssuer->setText(QStringLiteral("<a href=\"#issuerDetails\">%1</a>").arg(issuer)); | ||||
305 | } | ||||
306 | | ||||
307 | void CertificateDetailsWidget::Private::smimeLinkActivated(const QString &link) | ||||
308 | { | ||||
309 | Kleo::DN dn; | ||||
310 | QWidget *w; | ||||
311 | if (link == QLatin1String("#ownerDetails")) { | ||||
312 | dn = Kleo::DN(key.userID(0).id()); | ||||
313 | w = ui.smimeOwner; | ||||
314 | } else if (link == QLatin1String("#issuerDetails")) { | ||||
315 | dn = Kleo::DN(key.issuerName()); | ||||
316 | w = ui.smimeIssuer; | ||||
317 | } else { | ||||
318 | Q_UNREACHABLE(); | ||||
319 | return; | ||||
320 | } | ||||
321 | | ||||
322 | QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>"); | ||||
323 | const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) { | ||||
324 | const QString val = dn[attr]; | ||||
325 | if (!val.isEmpty()) { | ||||
326 | html += QStringLiteral( | ||||
327 | "<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>" | ||||
328 | "<td style=\"white-space: nowrap\">%2</td>" | ||||
329 | "</tr>").arg(lbl, val); | ||||
330 | } | ||||
331 | }; | ||||
332 | appendRow(i18n("Common Name"), QStringLiteral("CN")); | ||||
333 | appendRow(i18n("Organization"), QStringLiteral("O")); | ||||
334 | appendRow(i18n("Street"), QStringLiteral("STREET")); | ||||
335 | appendRow(i18n("City"), QStringLiteral("L")); | ||||
336 | appendRow(i18n("State"), QStringLiteral("ST")); | ||||
337 | appendRow(i18n("Country"), QStringLiteral("C")); | ||||
338 | html += QStringLiteral("</table>"); | ||||
339 | | ||||
340 | QToolTip::showText(QCursor::pos(), html, w); | ||||
341 | } | ||||
342 | | ||||
343 | | ||||
344 | | ||||
345 | CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent) | ||||
346 | : QWidget(parent) | ||||
347 | , d(new Private(this)) | ||||
348 | { | ||||
349 | d->ui.setupUi(this); | ||||
350 | connect(d->ui.addUserIDBtn, &QPushButton::clicked, | ||||
351 | this, [this]() { d->addUserID(); }); | ||||
352 | connect(d->ui.changePassphraseBtn, &QPushButton::clicked, | ||||
353 | this, [this]() { d->changePassphrase(); }); | ||||
354 | connect(d->ui.changeExpirationBtn, &QPushButton::clicked, | ||||
355 | this, [this]() { d->changeExpiration(); }); | ||||
356 | connect(d->ui.smimeOwner, &QLabel::linkActivated, | ||||
357 | this, [this](const QString &link) { d->smimeLinkActivated(link); }); | ||||
358 | connect(d->ui.smimeIssuer, &QLabel::linkActivated, | ||||
359 | this, [this](const QString &link) { d->smimeLinkActivated(link); }); | ||||
360 | connect(d->ui.trustChainDetailsBtn, &QPushButton::pressed, | ||||
361 | this, [this]() { d->showTrustChainDialog(); }); | ||||
362 | connect(d->ui.moreDetailsBtn, &QPushButton::pressed, | ||||
363 | this, [this]() { d->showMoreDetails(); }); | ||||
364 | connect(d->ui.publishing, &QPushButton::pressed, | ||||
365 | this, [this]() { d->publishCertificate(); }); | ||||
366 | | ||||
367 | connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, | ||||
368 | this, [this]() { d->keysMayHaveChanged(); }); | ||||
369 | } | ||||
370 | | ||||
371 | CertificateDetailsWidget::~CertificateDetailsWidget() | ||||
372 | { | ||||
373 | } | ||||
374 | | ||||
375 | void CertificateDetailsWidget::setKey(const GpgME::Key &key) | ||||
376 | { | ||||
377 | d->key = key; | ||||
378 | d->key.update(); // Fetch TOFU info (TODO: could be blocking, use async?) | ||||
379 | | ||||
380 | d->setupCommonProperties(); | ||||
381 | if (key.protocol() == GpgME::OpenPGP) { | ||||
382 | d->setupPGPProperties(); | ||||
383 | } else { | ||||
384 | d->setupSMIMEProperties(); | ||||
385 | } | ||||
386 | } | ||||
387 | | ||||
388 | GpgME::Key CertificateDetailsWidget::key() const | ||||
389 | { | ||||
390 | return d->key; | ||||
391 | } | ||||
392 | | ||||
393 | CertificateDetailsDialog::CertificateDetailsDialog(QWidget *parent) | ||||
394 | : QDialog(parent) | ||||
395 | { | ||||
396 | setWindowTitle(i18n("Certificate Details")); | ||||
397 | auto l = new QVBoxLayout(this); | ||||
398 | l->addWidget(new CertificateDetailsWidget(this)); | ||||
399 | | ||||
400 | auto bbox = new QDialogButtonBox(this); | ||||
401 | auto btn = bbox->addButton(QDialogButtonBox::Close); | ||||
402 | connect(btn, &QPushButton::pressed, this, &QDialog::accept); | ||||
403 | l->addWidget(bbox); | ||||
404 | readConfig(); | ||||
405 | } | ||||
406 | | ||||
407 | CertificateDetailsDialog::~CertificateDetailsDialog() | ||||
408 | { | ||||
409 | writeConfig(); | ||||
410 | } | ||||
411 | | ||||
412 | void CertificateDetailsDialog::readConfig() | ||||
413 | { | ||||
414 | KConfigGroup dialog(KSharedConfig::openConfig(), "CertificateDetailsDialog"); | ||||
415 | const QSize size = dialog.readEntry("Size", QSize(730, 280)); | ||||
416 | if (size.isValid()) { | ||||
417 | resize(size); | ||||
418 | } | ||||
419 | } | ||||
420 | | ||||
421 | void CertificateDetailsDialog::writeConfig() | ||||
422 | { | ||||
423 | KConfigGroup dialog(KSharedConfig::openConfig(), "CertificateDetailsDialog"); | ||||
424 | dialog.writeEntry("Size", size()); | ||||
425 | dialog.sync(); | ||||
426 | } | ||||
427 | | ||||
428 | void CertificateDetailsDialog::setKey(const GpgME::Key &key) | ||||
429 | { | ||||
430 | auto w = findChild<CertificateDetailsWidget*>(); | ||||
431 | Q_ASSERT(w); | ||||
432 | w->setKey(key); | ||||
433 | } | ||||
434 | | ||||
435 | GpgME::Key CertificateDetailsDialog::key() const | ||||
436 | { | ||||
437 | auto w = findChild<CertificateDetailsWidget*>(); | ||||
438 | Q_ASSERT(w); | ||||
439 | return w->key(); | ||||
440 | } |