Libkleo

openpgpcertificatecreationdialog.cpp
1/* -*- mode: c++; c-basic-offset:4 -*-
2 This file is part of Libkleo.
3 SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
4 SPDX-FileCopyrightText: 2022 g10 Code GmbH
5 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "openpgpcertificatecreationdialog.h"
11
12#include "adjustingscrollarea.h"
13#include "animatedexpander_p.h"
14#include "nameandemailwidget.h"
15#include "openpgpcertificatecreationconfig.h"
16#include "utils/compat.h"
17#include "utils/compliance.h"
18#include "utils/expiration.h"
19#include "utils/gnupg.h"
20#include "utils/keyparameters.h"
21#include "utils/keyusage.h"
22
23#include <kdatecombobox.h>
24
25#include <KConfigGroup>
26#include <KDateComboBox>
27#include <KLocalizedString>
28#include <KMessageBox>
29#include <KSeparator>
30#include <KSharedConfig>
31
32#include <QCheckBox>
33#include <QDialogButtonBox>
34#include <QLabel>
35#include <QPushButton>
36#include <QVBoxLayout>
37
38#include <QGpgME/CryptoConfig>
39#include <QGpgME/Protocol>
40
41#include "libkleo_debug.h"
42
43using namespace Kleo;
44
45static bool unlimitedValidityIsAllowed()
46{
47 return !Kleo::Expiration::maximumExpirationDate().isValid();
48}
49
50class OpenPGPCertificateCreationDialog::Private
51{
52 friend class ::Kleo::OpenPGPCertificateCreationDialog;
54
55 struct UI {
56 QLabel *infoLabel;
57 AdjustingScrollArea *scrollArea;
58 NameAndEmailWidget *nameAndEmail;
59 QCheckBox *withPassCheckBox;
60 QDialogButtonBox *buttonBox;
61 QCheckBox *expiryCB;
62 KDateComboBox *expiryDE;
63 QComboBox *keyAlgoCB;
64 QLabel *keyAlgoLabel;
65 AnimatedExpander *expander;
66
67 UI(QWidget *dialog)
68 {
69 auto mainLayout = new QVBoxLayout{dialog};
70
71 infoLabel = new QLabel{dialog};
72 infoLabel->setWordWrap(true);
73 mainLayout->addWidget(infoLabel);
74
75 mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog});
76
77 scrollArea = new AdjustingScrollArea{dialog};
78 scrollArea->setFocusPolicy(Qt::NoFocus);
79 scrollArea->setFrameStyle(QFrame::NoFrame);
80 scrollArea->setBackgroundRole(dialog->backgroundRole());
83 auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout());
84 scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
85
86 nameAndEmail = new NameAndEmailWidget{dialog};
87 nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0);
88 scrollAreaLayout->addWidget(nameAndEmail);
89
90 withPassCheckBox = new QCheckBox{i18n("Protect the generated key with a passphrase."), dialog};
91 withPassCheckBox->setToolTip(
92 i18n("Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation."));
93 scrollAreaLayout->addWidget(withPassCheckBox);
94
95 expander = new AnimatedExpander(i18n("Advanced options"), {}, dialog);
96 scrollAreaLayout->addWidget(expander);
97
98 auto advancedLayout = new QVBoxLayout;
99 expander->setContentLayout(advancedLayout);
100
101 keyAlgoLabel = new QLabel(dialog);
102 keyAlgoLabel->setText(i18nc("The algorithm and strength of encryption key", "Key Material"));
103 auto font = keyAlgoLabel->font();
104 font.setBold(true);
105 keyAlgoLabel->setFont(font);
106 advancedLayout->addWidget(keyAlgoLabel);
107
108 keyAlgoCB = new QComboBox(dialog);
109 keyAlgoLabel->setBuddy(keyAlgoCB);
110 advancedLayout->addWidget(keyAlgoCB);
111
112 {
113 auto hbox = new QHBoxLayout;
114
115 expiryCB = new QCheckBox{i18nc("@option:check", "Valid until:"), dialog};
116 hbox->addWidget(expiryCB);
117
118 expiryDE = new KDateComboBox(dialog);
119 hbox->addWidget(expiryDE, 1);
120
121 advancedLayout->addLayout(hbox);
122 }
123
124 scrollAreaLayout->addStretch(1);
125
126 mainLayout->addWidget(scrollArea);
127
128 mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog});
129
131
132 mainLayout->addWidget(buttonBox);
133 }
134 } ui;
135
136public:
137 explicit Private(OpenPGPCertificateCreationDialog *qq)
138 : q{qq}
139 , ui{qq}
140 , technicalParameters{KeyParameters::OpenPGP}
141 {
142 q->setWindowTitle(i18nc("title:window", "Create OpenPGP Certificate"));
143
144 OpenPGPCertificateCreationConfig settings;
145 const auto requiredFields = settings.requiredFields();
146 const auto nameIsRequired = requiredFields.contains(QLatin1StringView{"NAME!"}, Qt::CaseInsensitive);
147 const auto emailIsRequired = requiredFields.contains(QLatin1StringView{"EMAIL!"}, Qt::CaseInsensitive);
148
149 ui.infoLabel->setText(nameIsRequired || emailIsRequired //
150 ? i18n("Enter a name and an email address to use for the certificate.")
151 : i18n("Enter a name and/or an email address to use for the certificate."));
152
153 ui.nameAndEmail->setNameIsRequired(nameIsRequired);
154 ui.nameAndEmail->setNameLabel(settings.nameLabel());
155 const auto nameHint = settings.nameHint();
156 ui.nameAndEmail->setNameHint(nameHint.isEmpty() ? settings.namePlaceholder() : nameHint);
157 ui.nameAndEmail->setNamePattern(settings.nameRegex());
158 ui.nameAndEmail->setEmailIsRequired(emailIsRequired);
159 ui.nameAndEmail->setEmailLabel(settings.emailLabel());
160 const auto emailHint = settings.emailHint();
161 ui.nameAndEmail->setEmailHint(emailHint.isEmpty() ? settings.emailPlaceholder() : emailHint);
162 ui.nameAndEmail->setEmailPattern(settings.emailRegex());
163
164 ui.expander->setVisible(!settings.hideAdvanced());
165
166 const auto conf = QGpgME::cryptoConfig();
167 const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints");
168 if (entry && entry->boolValue()) {
169 qCDebug(LIBKLEO_LOG) << "Disabling passphrase check box because of agent config.";
170 ui.withPassCheckBox->setEnabled(false);
171 ui.withPassCheckBox->setChecked(true);
172 } else {
173 ui.withPassCheckBox->setChecked(settings.withPassphrase());
174 ui.withPassCheckBox->setEnabled(!settings.isWithPassphraseImmutable());
175 }
176
177 connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() {
178 checkAccept();
179 });
181
182 for (const auto &algorithm : DeVSCompliance::isActive() ? DeVSCompliance::compliantAlgorithms() : availableAlgorithms()) {
183 ui.keyAlgoCB->addItem(QString::fromStdString(algorithm), QString::fromStdString(algorithm));
184 }
185 auto cryptoConfig = QGpgME::cryptoConfig();
186 if (cryptoConfig) {
187 auto pubkeyEntry = getCryptoConfigEntry(QGpgME::cryptoConfig(), "gpg", "default_pubkey_algo");
188 if (pubkeyEntry) {
189 auto algo = pubkeyEntry->stringValue().split(QLatin1Char('/'))[0];
190 if (algo == QStringLiteral("ed25519")) {
191 algo = QStringLiteral("curve25519");
192 } else if (algo == QStringLiteral("ed448")) {
193 algo = QStringLiteral("curve448");
194 }
195 auto index = ui.keyAlgoCB->findData(algo);
196 if (index != -1) {
197 ui.keyAlgoCB->setCurrentIndex(index);
198 } else {
199 ui.keyAlgoCB->setCurrentIndex(0);
200 }
201 } else {
202 ui.keyAlgoCB->setCurrentIndex(0);
203 }
204 } else {
205 ui.keyAlgoCB->setCurrentIndex(0);
206 }
207
208 Kleo::Expiration::setUpExpirationDateComboBox(ui.expiryDE);
209 ui.expiryCB->setEnabled(true);
210 setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
211 if (unlimitedValidityIsAllowed()) {
212 ui.expiryDE->setEnabled(ui.expiryCB->isChecked());
213 } else {
214 ui.expiryCB->setEnabled(false);
215 }
216 connect(ui.expiryCB, &QAbstractButton::toggled, q, [this](bool checked) {
217 ui.expiryDE->setEnabled(checked);
218 if (checked && !ui.expiryDE->isValid()) {
219 setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
220 }
221 updateTechnicalParameters();
222 });
223 connect(ui.expiryDE, &KDateComboBox::dateChanged, q, [this]() {
224 updateTechnicalParameters();
225 });
226 connect(ui.keyAlgoCB, &QComboBox::currentIndexChanged, q, [this]() {
227 updateTechnicalParameters();
228 });
229 updateTechnicalParameters(); // set key parameters to default values for OpenPGP
230 connect(ui.expander, &AnimatedExpander::startExpanding, q, [this]() {
231 q->resize(q->sizeHint().width() + 20, q->sizeHint().height() + ui.expander->contentHeight() + 20);
232 });
233 }
234
235private:
236 void updateTechnicalParameters()
237 {
238 technicalParameters = KeyParameters{KeyParameters::OpenPGP};
239 auto keyType = GpgME::Subkey::AlgoUnknown;
240 auto subkeyType = GpgME::Subkey::AlgoUnknown;
241 auto algoString = ui.keyAlgoCB->currentData().toString();
242 if (algoString.startsWith(QStringLiteral("rsa"))) {
243 keyType = GpgME::Subkey::AlgoRSA;
244 subkeyType = GpgME::Subkey::AlgoRSA;
245 const auto strength = algoString.mid(3).toInt();
246 technicalParameters.setKeyLength(strength);
247 technicalParameters.setSubkeyLength(strength);
248 } else if (algoString == QStringLiteral("curve25519") || algoString == QStringLiteral("curve448")) {
249 keyType = GpgME::Subkey::AlgoEDDSA;
250 subkeyType = GpgME::Subkey::AlgoECDH;
251 if (algoString.endsWith(QStringLiteral("25519"))) {
252 technicalParameters.setKeyCurve(QStringLiteral("ed25519"));
253 technicalParameters.setSubkeyCurve(QStringLiteral("cv25519"));
254 } else {
255 technicalParameters.setKeyCurve(QStringLiteral("ed448"));
256 technicalParameters.setSubkeyCurve(QStringLiteral("cv448"));
257 }
258 } else {
259 keyType = GpgME::Subkey::AlgoECDSA;
260 subkeyType = GpgME::Subkey::AlgoECDH;
261 technicalParameters.setKeyCurve(algoString);
262 technicalParameters.setSubkeyCurve(algoString);
263 }
264 technicalParameters.setKeyType(keyType);
265 technicalParameters.setSubkeyType(subkeyType);
266
267 technicalParameters.setKeyUsage(KeyUsage(KeyUsage::Certify | KeyUsage::Sign));
268 technicalParameters.setSubkeyUsage(KeyUsage(KeyUsage::Encrypt));
269
270 technicalParameters.setExpirationDate(expiryDate());
271 // name and email are set later
272 }
273
274 QDate expiryDate() const
275 {
276 return ui.expiryCB->isChecked() ? ui.expiryDE->date() : QDate{};
277 }
278
279 void setTechnicalParameters(const KeyParameters &parameters)
280 {
281 int index;
282 if (parameters.keyType() == GpgME::Subkey::AlgoRSA_S) {
283 index = ui.keyAlgoCB->findData(QStringLiteral("rsa%1").arg(parameters.keyLength()));
284 } else {
285 index = ui.keyAlgoCB->findData(parameters.keyCurve());
286 }
287 ui.keyAlgoCB->setCurrentIndex(index);
288 setExpiryDate(parameters.expirationDate());
289 }
290
291 void checkAccept()
292 {
293 QStringList errors;
294 if (ui.nameAndEmail->userID().isEmpty() && !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) {
295 errors.push_back(i18n("Enter a name or an email address."));
296 }
297 const auto nameError = ui.nameAndEmail->nameError();
298 if (!nameError.isEmpty()) {
299 errors.push_back(nameError);
300 }
301 const auto emailError = ui.nameAndEmail->emailError();
302 if (!emailError.isEmpty()) {
303 errors.push_back(emailError);
304 }
305 if (errors.size() > 1) {
306 KMessageBox::errorList(q, i18n("There is a problem."), errors);
307 } else if (!errors.empty()) {
308 KMessageBox::error(q, errors.first());
309 } else {
310 q->accept();
311 }
312 }
313
314 QDate forceDateIntoAllowedRange(QDate date) const
315 {
316 const auto minDate = ui.expiryDE->minimumDate();
317 if (minDate.isValid() && date < minDate) {
318 date = minDate;
319 }
320 const auto maxDate = ui.expiryDE->maximumDate();
321 if (maxDate.isValid() && date > maxDate) {
322 date = maxDate;
323 }
324 return date;
325 }
326
327 void setExpiryDate(QDate date)
328 {
329 if (date.isValid()) {
330 ui.expiryDE->setDate(forceDateIntoAllowedRange(date));
331 } else {
332 // check if unlimited validity is allowed
333 if (unlimitedValidityIsAllowed()) {
334 ui.expiryDE->setDate(date);
335 }
336 }
337 if (ui.expiryCB->isEnabled()) {
338 ui.expiryCB->setChecked(ui.expiryDE->isValid());
339 }
340 }
341
342private:
343 KeyParameters technicalParameters;
344};
345
346OpenPGPCertificateCreationDialog::OpenPGPCertificateCreationDialog(QWidget *parent, Qt::WindowFlags f)
347 : QDialog{parent, f}
348 , d(new Private{this})
349{
350 resize(sizeHint().width() + 20, sizeHint().height() + 20);
351}
352
353OpenPGPCertificateCreationDialog::~OpenPGPCertificateCreationDialog() = default;
354
355void OpenPGPCertificateCreationDialog::setName(const QString &name)
356{
357 d->ui.nameAndEmail->setName(name);
358}
359
360QString OpenPGPCertificateCreationDialog::name() const
361{
362 return d->ui.nameAndEmail->name();
363}
364
365void OpenPGPCertificateCreationDialog::setEmail(const QString &email)
366{
367 d->ui.nameAndEmail->setEmail(email);
368}
369
370QString OpenPGPCertificateCreationDialog::email() const
371{
372 return d->ui.nameAndEmail->email();
373}
374
375void Kleo::OpenPGPCertificateCreationDialog::setKeyParameters(const Kleo::KeyParameters &parameters)
376{
377 setName(parameters.name());
378 const auto emails = parameters.emails();
379 if (!emails.empty()) {
380 setEmail(emails.front());
381 }
382 d->setTechnicalParameters(parameters);
383}
384
385KeyParameters OpenPGPCertificateCreationDialog::keyParameters() const
386{
387 // set name and email on a copy of the technical parameters
388 auto parameters = d->technicalParameters;
389 if (!name().isEmpty()) {
390 parameters.setName(name());
391 }
392 if (!email().isEmpty()) {
393 parameters.setEmail(email());
394 }
395 return parameters;
396}
397
398void Kleo::OpenPGPCertificateCreationDialog::setProtectKeyWithPassword(bool protectKey)
399{
400 d->ui.withPassCheckBox->setChecked(protectKey);
401}
402
403bool OpenPGPCertificateCreationDialog::protectKeyWithPassword() const
404{
405 return d->ui.withPassCheckBox->isChecked();
406}
407
408#include "moc_openpgpcertificatecreationdialog.cpp"
void dateChanged(const QDate &date)
This class improves a few aspects of QScrollArea for usage by us, in particular, for vertically scrol...
A widget containing a name and an email field.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void errorList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void setChecked(bool)
void toggled(bool checked)
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)
void setSizeAdjustPolicy(SizeAdjustPolicy policy)
void addItem(const QIcon &icon, const QString &text, const QVariant &userData)
void setCurrentIndex(int index)
void currentIndexChanged(int index)
int findData(const QVariant &data, int role, Qt::MatchFlags flags) const const
bool isValid(int year, int month, int day)
bool setDate(int year, int month, int day)
virtual void accept()
virtual void reject()
void setFrameStyle(int style)
void setBuddy(QWidget *buddy)
void setText(const QString &)
void setWordWrap(bool on)
void setContentsMargins(const QMargins &margins)
bool empty() const const
T & first()
void push_back(parameter_type value)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QWidget * widget() const const
QString fromStdString(const std::string &str)
CaseInsensitive
Horizontal
ScrollBarAlwaysOff
typedef WindowFlags
QPalette::ColorRole backgroundRole() const const
void setEnabled(bool)
void setFocusPolicy(Qt::FocusPolicy policy)
QLayout * layout() const const
void setBackgroundRole(QPalette::ColorRole role)
void setToolTip(const QString &)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:50:31 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.