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

0001 /*  view/openpgpkeycardwidget.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
0005     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "openpgpkeycardwidget.h"
0011 
0012 #include "commands/detailscommand.h"
0013 
0014 #include "smartcard/card.h"
0015 #include "smartcard/keypairinfo.h"
0016 #include "smartcard/openpgpcard.h"
0017 
0018 #include <Libkleo/Compliance>
0019 #include <Libkleo/Formatting>
0020 #include <Libkleo/KeyCache>
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 
0025 #include <QGridLayout>
0026 #include <QLabel>
0027 #include <QPushButton>
0028 
0029 #include <gpgme++/key.h>
0030 
0031 using namespace Kleo;
0032 using namespace SmartCard;
0033 
0034 namespace
0035 {
0036 struct KeyWidgets {
0037     std::string cardKeyRef;
0038     std::string keyFingerprint;
0039     KeyPairInfo keyInfo;
0040     QLabel *keyTitleLabel = nullptr;
0041     QLabel *keyInfoLabel = nullptr;
0042     QPushButton *showCertificateDetailsButton = nullptr;
0043     QPushButton *generateButton = nullptr;
0044     QPushButton *createCSRButton = nullptr;
0045 };
0046 
0047 KeyWidgets createKeyWidgets(const KeyPairInfo &keyInfo, QWidget *parent)
0048 {
0049     KeyWidgets keyWidgets;
0050     keyWidgets.keyTitleLabel = new QLabel{OpenPGPCard::keyDisplayName(keyInfo.keyRef), parent};
0051     keyWidgets.keyInfoLabel = new QLabel{parent};
0052     keyWidgets.keyInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard);
0053     keyWidgets.showCertificateDetailsButton = new QPushButton{i18nc("@action:button", "Show Details"), parent};
0054     keyWidgets.showCertificateDetailsButton->setToolTip(i18nc("@action:tooltip", "Show detailed information about this key"));
0055     keyWidgets.showCertificateDetailsButton->setEnabled(false);
0056     keyWidgets.generateButton = new QPushButton{i18nc("@action:button", "Generate Key"), parent};
0057     keyWidgets.generateButton->setEnabled(false);
0058     if (keyInfo.canCertify() || keyInfo.canSign() || keyInfo.canAuthenticate()) {
0059         keyWidgets.createCSRButton = new QPushButton{i18nc("@action:button", "Create CSR"), parent};
0060         keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
0061         keyWidgets.createCSRButton->setEnabled(false);
0062     }
0063 
0064     return keyWidgets;
0065 }
0066 }
0067 
0068 class OpenPGPKeyCardWidget::Private
0069 {
0070 public:
0071     explicit Private(OpenPGPKeyCardWidget *q);
0072     ~Private() = default;
0073 
0074     void setAllowedActions(Actions actions);
0075     void update(const Card *card = nullptr);
0076 
0077 private:
0078     void updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card);
0079     void updateKeyWidgets(const std::string &openPGPKeyRef);
0080 
0081     void showCertificateDetails(const std::string &openPGPKeyRef);
0082 
0083 private:
0084     OpenPGPKeyCardWidget *const q;
0085     Actions mAllowedActions = AllActions;
0086     std::map<std::string, KeyWidgets> mKeyWidgets;
0087 };
0088 
0089 OpenPGPKeyCardWidget::Private::Private(OpenPGPKeyCardWidget *q)
0090     : q{q}
0091 {
0092     auto grid = new QGridLayout{q};
0093     grid->setContentsMargins(0, 0, 0, 0);
0094     for (const auto &keyInfo : OpenPGPCard::supportedKeys()) {
0095         const KeyWidgets keyWidgets = createKeyWidgets(keyInfo, q);
0096 
0097         const std::string keyRef = keyInfo.keyRef;
0098         connect(keyWidgets.showCertificateDetailsButton, &QPushButton::clicked, q, [this, keyRef]() {
0099             showCertificateDetails(keyRef);
0100         });
0101         connect(keyWidgets.generateButton, &QPushButton::clicked, q, [q, keyRef]() {
0102             Q_EMIT q->generateKeyRequested(keyRef);
0103         });
0104         if (keyWidgets.createCSRButton) {
0105             connect(keyWidgets.createCSRButton, &QPushButton::clicked, q, [q, keyRef]() {
0106                 Q_EMIT q->createCSRRequested(keyRef);
0107             });
0108         }
0109 
0110         const int row = grid->rowCount();
0111         grid->addWidget(keyWidgets.keyTitleLabel, row, 0, Qt::AlignTop);
0112         grid->addWidget(keyWidgets.keyInfoLabel, row, 1, Qt::AlignTop);
0113 
0114         auto buttons = new QHBoxLayout;
0115         buttons->addWidget(keyWidgets.showCertificateDetailsButton);
0116         buttons->addWidget(keyWidgets.generateButton);
0117         if (keyWidgets.createCSRButton) {
0118             buttons->addWidget(keyWidgets.createCSRButton);
0119         }
0120         buttons->addStretch(1);
0121         grid->addLayout(buttons, row, 2, Qt::AlignTop);
0122 
0123         mKeyWidgets.insert({keyInfo.keyRef, keyWidgets});
0124     }
0125     grid->setColumnStretch(grid->columnCount(), 1);
0126 }
0127 
0128 void OpenPGPKeyCardWidget::Private::setAllowedActions(Actions actions)
0129 {
0130     mAllowedActions = actions;
0131     update();
0132 }
0133 
0134 void OpenPGPKeyCardWidget::Private::update(const Card *card)
0135 {
0136     if (card) {
0137         updateCachedValues(OpenPGPCard::pgpSigKeyRef(), card->signingKeyRef(), card);
0138         updateCachedValues(OpenPGPCard::pgpEncKeyRef(), card->encryptionKeyRef(), card);
0139         updateCachedValues(OpenPGPCard::pgpAuthKeyRef(), card->authenticationKeyRef(), card);
0140     }
0141     updateKeyWidgets(OpenPGPCard::pgpSigKeyRef());
0142     updateKeyWidgets(OpenPGPCard::pgpEncKeyRef());
0143     updateKeyWidgets(OpenPGPCard::pgpAuthKeyRef());
0144 }
0145 
0146 void OpenPGPKeyCardWidget::Private::updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card)
0147 {
0148     KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
0149     widgets.cardKeyRef = cardKeyRef;
0150     widgets.keyFingerprint = card->keyFingerprint(openPGPKeyRef);
0151     widgets.keyInfo = card->keyInfo(cardKeyRef);
0152 }
0153 
0154 void OpenPGPKeyCardWidget::Private::updateKeyWidgets(const std::string &openPGPKeyRef)
0155 {
0156     const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
0157 
0158     const auto cardSupportsKey = !widgets.cardKeyRef.empty();
0159     widgets.keyTitleLabel->setVisible(cardSupportsKey);
0160     widgets.keyInfoLabel->setVisible(cardSupportsKey);
0161     widgets.showCertificateDetailsButton->setVisible(cardSupportsKey);
0162     widgets.generateButton->setVisible(cardSupportsKey && (mAllowedActions & Action::GenerateKey));
0163     if (widgets.createCSRButton) {
0164         widgets.createCSRButton->setVisible(cardSupportsKey && (mAllowedActions & Action::CreateCSR));
0165     }
0166     if (!cardSupportsKey) {
0167         return;
0168     }
0169 
0170     widgets.showCertificateDetailsButton->setEnabled(false);
0171 
0172     if (widgets.keyFingerprint.empty()) {
0173         widgets.keyInfoLabel->setTextFormat(Qt::RichText);
0174         widgets.keyInfoLabel->setText(i18nc("@info", "<em>No key</em>"));
0175         widgets.generateButton->setText(i18nc("@action:button", "Generate Key"));
0176         widgets.generateButton->setToolTip(i18nc("@info:tooltip", "Generate a key for this card slot"));
0177         if (widgets.createCSRButton) {
0178             widgets.createCSRButton->setEnabled(false);
0179         }
0180     } else {
0181         QStringList lines;
0182         if (widgets.keyFingerprint.size() >= 16) {
0183             const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16);
0184             const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid});
0185             if (subkeys.empty() || subkeys[0].isNull()) {
0186                 widgets.keyInfoLabel->setTextFormat(Qt::RichText);
0187                 lines.push_back(i18nc("@info", "<em>Public key not found locally</em>"));
0188                 widgets.keyInfoLabel->setToolTip({});
0189             } else {
0190                 // force interpretation of text as plain text to avoid problems with HTML in user IDs
0191                 widgets.keyInfoLabel->setTextFormat(Qt::PlainText);
0192                 QStringList toolTips;
0193                 toolTips.reserve(subkeys.size());
0194                 for (const auto &sub : subkeys) {
0195                     // Yep you can have one subkey associated with multiple primary keys.
0196                     const GpgME::Key key = sub.parent();
0197                     toolTips << Formatting::toolTip(key, Formatting::Validity | Formatting::ExpiryDates | Formatting::UserIDs | Formatting::Fingerprint);
0198                     const auto uids = key.userIDs();
0199                     for (const auto &uid : uids) {
0200                         lines.push_back(Formatting::prettyUserID(uid));
0201                     }
0202                 }
0203                 widgets.keyInfoLabel->setToolTip(toolTips.join(QLatin1StringView("<br/>")));
0204                 widgets.showCertificateDetailsButton->setEnabled(true);
0205             }
0206         } else {
0207             widgets.keyInfoLabel->setTextFormat(Qt::RichText);
0208             lines.push_back(i18nc("@info", "<em>Invalid fingerprint</em>"));
0209         }
0210 
0211         const QString fingerprint = widgets.keyInfoLabel->textFormat() == Qt::RichText
0212             ? Formatting::prettyID(widgets.keyFingerprint.c_str()).replace(QLatin1Char(' '), QLatin1StringView("&nbsp;"))
0213             : Formatting::prettyID(widgets.keyFingerprint.c_str());
0214         lines.insert(0, fingerprint);
0215         const auto lineSeparator = widgets.keyInfoLabel->textFormat() == Qt::PlainText ? QLatin1StringView("\n") : QLatin1String("<br>");
0216         widgets.keyInfoLabel->setText(lines.join(lineSeparator));
0217 
0218         widgets.generateButton->setText(i18nc("@action:button", "Regenerate Key"));
0219         widgets.generateButton->setToolTip(i18nc("@info:tooltip", "Generate a new key for this card slot replacing the existing key"));
0220         if (widgets.createCSRButton) {
0221             widgets.createCSRButton->setEnabled(DeVSCompliance::algorithmIsCompliant(widgets.keyInfo.algorithm));
0222         }
0223     }
0224 
0225     widgets.generateButton->setEnabled(!widgets.generateButton->isHidden());
0226 }
0227 
0228 void OpenPGPKeyCardWidget::Private::showCertificateDetails(const std::string &openPGPKeyRef)
0229 {
0230     const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
0231 
0232     if (widgets.keyFingerprint.size() >= 16) {
0233         const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16);
0234         const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid});
0235         if (!subkeys.empty() && !subkeys[0].isNull()) {
0236             auto cmd = new Commands::DetailsCommand(subkeys[0].parent());
0237             cmd->setParentWidget(q);
0238             cmd->start();
0239             return;
0240         }
0241     }
0242     KMessageBox::error(q, i18nc("@info", "Sorry, I cannot find the key with fingerprint %1.", Formatting::prettyID(widgets.keyFingerprint.c_str())));
0243 }
0244 
0245 OpenPGPKeyCardWidget::OpenPGPKeyCardWidget(QWidget *parent)
0246     : QWidget{parent}
0247     , d{std::make_unique<Private>(this)}
0248 {
0249     connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this]() {
0250         d->update();
0251     });
0252 }
0253 
0254 OpenPGPKeyCardWidget::~OpenPGPKeyCardWidget() = default;
0255 
0256 void OpenPGPKeyCardWidget::setAllowedActions(Actions actions)
0257 {
0258     d->setAllowedActions(actions);
0259 }
0260 
0261 void OpenPGPKeyCardWidget::update(const Card *card)
0262 {
0263     d->update(card);
0264 }
0265 
0266 #include "moc_openpgpkeycardwidget.cpp"