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

KDE's Doxygen guidelines are available online.