openpgpkeycardwidget.cpp 6.97 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*  view/openpgpkeycardwidget.cpp

    This file is part of Kleopatra, the KDE keymanager
    SPDX-FileCopyrightText: 2021 g10 Code GmbH
    SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "openpgpkeycardwidget.h"

#include "smartcard/card.h"
#include "smartcard/keypairinfo.h"
#include "smartcard/openpgpcard.h"

#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>

#include <KLocalizedString>

#include <QGridLayout>
#include <QLabel>
#include <QPushButton>

#include <gpgme++/key.h>

using namespace Kleo;
using namespace SmartCard;

namespace
{
struct KeyWidgets {
33
    std::string cardKeyRef;
34
    std::string keyGrip;
35
    std::string keyFingerprint;
36
    QLabel *keyTitleLabel = nullptr;
37
    QLabel *keyInfoLabel = nullptr;
38
39
40
41
42
43
    QPushButton *createCSRButton = nullptr;
};

KeyWidgets createKeyWidgets(const KeyPairInfo &keyInfo, QWidget *parent)
{
    KeyWidgets keyWidgets;
44
    keyWidgets.keyTitleLabel = new QLabel{OpenPGPCard::keyDisplayName(keyInfo.keyRef), parent};
45
46
    keyWidgets.keyInfoLabel = new QLabel{parent};
    keyWidgets.keyInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    if (keyInfo.canCertify() || keyInfo.canSign() || keyInfo.canAuthenticate())
    {
        keyWidgets.createCSRButton = new QPushButton{i18nc("@action:button", "Create CSR"), parent};
        keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
        keyWidgets.createCSRButton->setEnabled(false);
    }

    return keyWidgets;
}
}

class OpenPGPKeyCardWidget::Private
{
public:
    explicit Private(OpenPGPKeyCardWidget *q);
    ~Private() = default;

    void update(const Card *card);

private:
67
68
    void updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card);
    void updateKeyWidgets(const std::string &openPGPKeyRef);
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

private:
    OpenPGPKeyCardWidget *const q;
    std::map<std::string, KeyWidgets> mKeyWidgets;
};

OpenPGPKeyCardWidget::Private::Private(OpenPGPKeyCardWidget *q)
    : q{q}
{
    auto grid = new QGridLayout{q};
    grid->setContentsMargins(0, 0, 0, 0);
    for (const auto &keyInfo : OpenPGPCard::supportedKeys()) {
        const KeyWidgets keyWidgets = createKeyWidgets(keyInfo, q);
        if (keyWidgets.createCSRButton) {
            const std::string keyRef = keyInfo.keyRef;
            connect(keyWidgets.createCSRButton, &QPushButton::clicked,
                    q, [q, keyRef] () { Q_EMIT q->createCSRRequested(keyRef); });
        }

        const int row = grid->rowCount();
89
        grid->addWidget(keyWidgets.keyTitleLabel, row, 0, Qt::AlignTop);
Ingo Klöcker's avatar
Ingo Klöcker committed
90
        grid->addWidget(keyWidgets.keyInfoLabel, row, 1, Qt::AlignTop);
91
        if (keyWidgets.createCSRButton) {
92
            grid->addWidget(keyWidgets.createCSRButton, row, 2, Qt::AlignTop);
93
94
95
96
97
98
99
100
101
        }

        mKeyWidgets.insert({keyInfo.keyRef, keyWidgets});
    }
    grid->setColumnStretch(grid->columnCount(), 1);
}

void OpenPGPKeyCardWidget::Private::update(const Card *card)
{
102
    if (card) {
103
104
105
        updateCachedValues(OpenPGPCard::pgpSigKeyRef(), card->signingKeyRef(), card);
        updateCachedValues(OpenPGPCard::pgpEncKeyRef(), card->encryptionKeyRef(), card);
        updateCachedValues(OpenPGPCard::pgpAuthKeyRef(), card->authenticationKeyRef(), card);
106
107
108
109
    }
    updateKeyWidgets(OpenPGPCard::pgpSigKeyRef());
    updateKeyWidgets(OpenPGPCard::pgpEncKeyRef());
    updateKeyWidgets(OpenPGPCard::pgpAuthKeyRef());
110
111
}

112
void OpenPGPKeyCardWidget::Private::updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card)
113
{
114
115
116
117
    KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
    widgets.cardKeyRef = cardKeyRef;
    widgets.keyGrip = card->keyInfo(cardKeyRef).grip;
    widgets.keyFingerprint = card->keyFingerprint(openPGPKeyRef);
118
119
}

120
void OpenPGPKeyCardWidget::Private::updateKeyWidgets(const std::string &openPGPKeyRef)
121
{
122
    const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef);
123

124
125
126
127
128
129
130
131
132
133
    const auto cardSupportsKey = !widgets.cardKeyRef.empty();
    widgets.keyTitleLabel->setVisible(cardSupportsKey);
    widgets.keyInfoLabel->setVisible(cardSupportsKey);
    if (widgets.createCSRButton) {
        widgets.createCSRButton->setVisible(cardSupportsKey);
    }
    if (!cardSupportsKey) {
        return;
    }

Ingo Klöcker's avatar
Ingo Klöcker committed
134
135
136
    if (widgets.keyFingerprint.empty()) {
        widgets.keyInfoLabel->setTextFormat(Qt::RichText);
        widgets.keyInfoLabel->setText(i18nc("@info", "<em>No key</em>"));
137
138
139
140
        if (widgets.createCSRButton) {
            widgets.createCSRButton->setEnabled(false);
        }
    } else {
141
142
143
        QStringList lines = {Formatting::prettyID(widgets.keyFingerprint.c_str())};
        if (widgets.keyFingerprint.size() >= 16) {
            const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16);
144
145
            const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid});
            if (subkeys.empty() || subkeys[0].isNull()) {
Ingo Klöcker's avatar
Ingo Klöcker committed
146
147
                widgets.keyInfoLabel->setTextFormat(Qt::RichText);
                lines.push_back(i18nc("@info", "<em>Public key not found locally</em>"));
148
                widgets.keyInfoLabel->setToolTip({});
149
            } else {
Ingo Klöcker's avatar
Ingo Klöcker committed
150
151
                // force interpretation of text as plain text to avoid problems with HTML in user IDs
                widgets.keyInfoLabel->setTextFormat(Qt::PlainText);
152
153
154
155
                QStringList toolTips;
                toolTips.reserve(subkeys.size());
                for (const auto &sub: subkeys) {
                    // Yep you can have one subkey associated with multiple primary keys.
156
157
158
                    const GpgME::Key key = sub.parent();
                    toolTips << Formatting::toolTip(key,
                                                    Formatting::Validity |
159
160
161
                                                    Formatting::ExpiryDates |
                                                    Formatting::UserIDs |
                                                    Formatting::Fingerprint);
162
163
164
165
                    const auto uids = key.userIDs();
                    for (const auto &uid: uids) {
                        lines.push_back(Formatting::prettyUserID(uid));
                    }
166
                }
167
                widgets.keyInfoLabel->setToolTip(toolTips.join(QLatin1String("<br/>")));
168
            }
Ingo Klöcker's avatar
Ingo Klöcker committed
169
170
171
        } else {
            widgets.keyInfoLabel->setTextFormat(Qt::RichText);
            lines.push_back(i18nc("@info", "<em>Invalid fingerprint</em>"));
172
        }
173

Ingo Klöcker's avatar
Ingo Klöcker committed
174
175
        const auto lineSeparator = widgets.keyInfoLabel->textFormat() == Qt::PlainText ? QLatin1String("\n") : QLatin1String("<br>");
        widgets.keyInfoLabel->setText(lines.join(lineSeparator));
176

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
        if (widgets.createCSRButton) {
            widgets.createCSRButton->setEnabled(true);
        }
    }
}

OpenPGPKeyCardWidget::OpenPGPKeyCardWidget(QWidget *parent)
    : QWidget{parent}
    , d{std::make_unique<Private>(this)}
{
}

OpenPGPKeyCardWidget::~OpenPGPKeyCardWidget() = default;

void OpenPGPKeyCardWidget::update(const Card *card)
{
    d->update(card);
}