10#include <config-libkleo.h>
12#include "openpgpcertificatecreationdialog.h"
14#include "animatedexpander_p.h"
15#include "nameandemailwidget.h"
16#include "openpgpcertificatecreationconfig.h"
17#include "utils/compat.h"
18#include "utils/compliance.h"
19#include "utils/expiration.h"
20#include "utils/gnupg.h"
21#include "utils/keyparameters.h"
22#include "utils/keyusage.h"
24#include <KAdjustingScrollArea>
25#include <KConfigGroup>
26#include <KDateComboBox>
27#include <KLocalizedString>
30#include <KSharedConfig>
33#include <QDialogButtonBox>
38#include <QGpgME/CryptoConfig>
39#include <QGpgME/Protocol>
41#include "libkleo_debug.h"
46static bool unlimitedValidityIsAllowed()
48 return !Kleo::Expiration::maximumExpirationDate().isValid();
51class OpenPGPCertificateCreationDialog::Private
53 friend class ::Kleo::OpenPGPCertificateCreationDialog;
54 OpenPGPCertificateCreationDialog *
const q;
58 KAdjustingScrollArea *scrollArea;
59 NameAndEmailWidget *nameAndEmail;
60 QCheckBox *withPassCheckBox;
61 QDialogButtonBox *buttonBox;
64 KDateComboBox *expiryDE;
67 AnimatedExpander *expander;
71 auto mainLayout =
new QVBoxLayout{dialog};
73 infoLabel =
new QLabel{dialog};
74 infoLabel->setWordWrap(
true);
75 mainLayout->addWidget(infoLabel);
79 scrollArea =
new KAdjustingScrollArea{dialog};
86 scrollArea->setWidget(widget);
87 auto scrollAreaLayout =
new QVBoxLayout(widget);
88 scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
90 nameAndEmail =
new NameAndEmailWidget{dialog};
91 nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0);
92 scrollAreaLayout->addWidget(nameAndEmail);
94 withPassCheckBox =
new QCheckBox{
i18n(
"Protect the generated key with a passphrase."), dialog};
95 withPassCheckBox->setToolTip(
96 i18n(
"Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation."));
97 scrollAreaLayout->addWidget(withPassCheckBox);
99 expander =
new AnimatedExpander(
i18n(
"Advanced options"), {}, dialog);
100 scrollAreaLayout->addWidget(expander);
102 auto advancedLayout =
new QVBoxLayout;
103 expander->setContentLayout(advancedLayout);
105 keyAlgoLabel =
new QLabel(dialog);
106 keyAlgoLabel->setText(
i18nc(
"The algorithm and strength of encryption key",
"Key Material"));
107 auto font = keyAlgoLabel->font();
109 keyAlgoLabel->setFont(
font);
110 advancedLayout->addWidget(keyAlgoLabel);
112 keyAlgoCB =
new QComboBox(dialog);
113 keyAlgoLabel->setBuddy(keyAlgoCB);
114 advancedLayout->addWidget(keyAlgoCB);
117 auto hbox =
new QHBoxLayout;
119 expiryCB =
new QCheckBox{dialog};
120 expiryCB->setAccessibleName(Expiration::validUntilLabel());
121 hbox->addWidget(expiryCB);
123 expiryLabel =
new QLabel{Expiration::validUntilLabel(), dialog};
124 hbox->addWidget(expiryLabel);
126 expiryDE =
new KDateComboBox(dialog);
127 hbox->addWidget(expiryDE, 1);
129 advancedLayout->addLayout(hbox);
132 scrollAreaLayout->addStretch(1);
134 mainLayout->addWidget(scrollArea);
140 mainLayout->addWidget(buttonBox);
145 explicit Private(OpenPGPCertificateCreationDialog *qq)
148 , technicalParameters{KeyParameters::OpenPGP}
150 q->setWindowTitle(
i18nc(
"title:window",
"Create OpenPGP Certificate"));
152 OpenPGPCertificateCreationConfig settings;
153 const auto requiredFields = settings.requiredFields();
154 const auto nameIsRequired = requiredFields.contains(QLatin1StringView{
"NAME!"},
Qt::CaseInsensitive);
155 const auto emailIsRequired = requiredFields.contains(QLatin1StringView{
"EMAIL!"},
Qt::CaseInsensitive);
157 ui.infoLabel->setText(nameIsRequired || emailIsRequired
158 ?
i18n(
"Enter a name and an email address to use for the certificate.")
159 :
i18n(
"Enter a name and/or an email address to use for the certificate."));
161 ui.nameAndEmail->setNameIsRequired(nameIsRequired);
162 ui.nameAndEmail->setNameLabel(settings.nameLabel());
163 const auto nameHint = settings.nameHint();
164 ui.nameAndEmail->setNameHint(nameHint.isEmpty() ? settings.namePlaceholder() : nameHint);
165 ui.nameAndEmail->setNamePattern(settings.nameRegex());
166 ui.nameAndEmail->setEmailIsRequired(emailIsRequired);
167 ui.nameAndEmail->setEmailLabel(settings.emailLabel());
168 const auto emailHint = settings.emailHint();
169 ui.nameAndEmail->setEmailHint(emailHint.isEmpty() ? settings.emailPlaceholder() : emailHint);
170 ui.nameAndEmail->setEmailPattern(settings.emailRegex());
172 ui.expander->setVisible(!settings.hideAdvanced());
174 const auto conf = QGpgME::cryptoConfig();
175 const auto entry = getCryptoConfigEntry(conf,
"gpg-agent",
"enforce-passphrase-constraints");
176 if (entry && entry->boolValue()) {
177 qCDebug(LIBKLEO_LOG) <<
"Disabling passphrase check box because of agent config.";
178 ui.withPassCheckBox->setEnabled(
false);
179 ui.withPassCheckBox->setChecked(
true);
181 ui.withPassCheckBox->setChecked(settings.withPassphrase());
182 ui.withPassCheckBox->setEnabled(!settings.isWithPassphraseImmutable());
190 for (
const auto &algorithm : DeVSCompliance::isActive() ? DeVSCompliance::compliantAlgorithms() : availableAlgorithms()) {
193 auto cryptoConfig = QGpgME::cryptoConfig();
195 auto pubkeyEntry = getCryptoConfigEntry(QGpgME::cryptoConfig(),
"gpg",
"default_pubkey_algo");
197 auto algo = pubkeyEntry->stringValue().split(QLatin1Char(
'/'))[0];
198 if (algo == QLatin1StringView(
"ed25519")) {
199 algo = QStringLiteral(
"curve25519");
200 }
else if (algo == QLatin1StringView(
"ed448")) {
201 algo = QStringLiteral(
"curve448");
203 auto index = ui.keyAlgoCB->findData(algo);
205 ui.keyAlgoCB->setCurrentIndex(index);
207 ui.keyAlgoCB->setCurrentIndex(0);
210 ui.keyAlgoCB->setCurrentIndex(0);
213 ui.keyAlgoCB->setCurrentIndex(0);
216 Kleo::Expiration::setUpExpirationDateComboBox(ui.expiryDE);
217 ui.expiryCB->setEnabled(
true);
218 setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
219 if (unlimitedValidityIsAllowed()) {
220 ui.expiryLabel->setEnabled(ui.expiryCB->isChecked());
221 ui.expiryDE->setEnabled(ui.expiryCB->isChecked());
223 ui.expiryCB->setEnabled(
false);
224 ui.expiryCB->setVisible(
false);
227 ui.expiryLabel->setEnabled(checked);
228 ui.expiryDE->setEnabled(checked);
229 if (checked && !ui.expiryDE->isValid()) {
230 setExpiryDate(defaultExpirationDate(Kleo::Expiration::ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
232 updateTechnicalParameters();
235 updateTechnicalParameters();
238 updateTechnicalParameters();
240 updateTechnicalParameters();
241 connect(ui.expander, &AnimatedExpander::startExpanding, q, [
this]() {
242 q->resize(std::max(q->sizeHint().width(), ui.expander->contentWidth()) + 20, q->sizeHint().height() + ui.expander->contentHeight() + 20);
247 void updateTechnicalParameters()
249 technicalParameters = KeyParameters{KeyParameters::OpenPGP};
250 auto keyType = GpgME::Subkey::AlgoUnknown;
251 auto subkeyType = GpgME::Subkey::AlgoUnknown;
252 auto algoString = ui.keyAlgoCB->currentData().toString();
253 if (algoString.startsWith(QStringLiteral(
"rsa"))) {
254 keyType = GpgME::Subkey::AlgoRSA;
255 subkeyType = GpgME::Subkey::AlgoRSA;
256 const auto strength = algoString.mid(3).toInt();
257 technicalParameters.setKeyLength(strength);
258 technicalParameters.setSubkeyLength(strength);
260 keyType = GpgME::Subkey::AlgoEDDSA;
261 subkeyType = GpgME::Subkey::AlgoECDH;
262 if (algoString.endsWith(QStringLiteral(
"25519"))) {
263 technicalParameters.setKeyCurve(QStringLiteral(
"ed25519"));
264 technicalParameters.setSubkeyCurve(QStringLiteral(
"cv25519"));
266 technicalParameters.setKeyCurve(QStringLiteral(
"ed448"));
267 technicalParameters.setSubkeyCurve(QStringLiteral(
"cv448"));
269#if GPGMEPP_SUPPORTS_KYBER
270 }
else if (algoString ==
"ky768_bp256"_L1) {
271 keyType = GpgME::Subkey::AlgoECDSA;
272 subkeyType = GpgME::Subkey::AlgoKyber;
273 technicalParameters.setKeyCurve(u
"brainpoolP256r1"_s);
274 technicalParameters.setSubkeyCurve(u
"brainpoolP256r1"_s);
275 technicalParameters.setSubkeyLength(768);
276 }
else if (algoString ==
"ky1024_bp384"_L1) {
277 keyType = GpgME::Subkey::AlgoECDSA;
278 subkeyType = GpgME::Subkey::AlgoKyber;
279 technicalParameters.setKeyCurve(u
"brainpoolP384r1"_s);
280 technicalParameters.setSubkeyCurve(u
"brainpoolP384r1"_s);
281 technicalParameters.setSubkeyLength(1024);
284 keyType = GpgME::Subkey::AlgoECDSA;
285 subkeyType = GpgME::Subkey::AlgoECDH;
286 technicalParameters.setKeyCurve(algoString);
287 technicalParameters.setSubkeyCurve(algoString);
289 technicalParameters.setKeyType(keyType);
290 technicalParameters.setSubkeyType(subkeyType);
292 technicalParameters.setKeyUsage(KeyUsage(KeyUsage::Certify | KeyUsage::Sign));
293 technicalParameters.setSubkeyUsage(KeyUsage(KeyUsage::Encrypt));
295 technicalParameters.setExpirationDate(expiryDate());
299 QDate expiryDate()
const
301 return ui.expiryCB->isChecked() ? ui.expiryDE->date() :
QDate{};
304 void setTechnicalParameters(
const KeyParameters ¶meters)
307 if (parameters.keyType() == GpgME::Subkey::AlgoRSA) {
308 index = ui.keyAlgoCB->findData(QStringLiteral(
"rsa%1").arg(parameters.keyLength()));
310 index = ui.keyAlgoCB->findData(QStringLiteral(
"curve25519"));
312 index = ui.keyAlgoCB->findData(QStringLiteral(
"curve448"));
313#if GPGMEPP_SUPPORTS_KYBER
314 }
else if (parameters.subkeyType() == GpgME::Subkey::AlgoKyber) {
315 if (parameters.subkeyLength() == 768 && parameters.keyCurve() ==
"brainpoolP256r1"_L1) {
316 index = ui.keyAlgoCB->findData(
"ky768_bp256"_L1);
317 }
else if (parameters.subkeyLength() == 1024 && parameters.keyCurve() ==
"brainpoolP384r1"_L1) {
318 index = ui.keyAlgoCB->findData(
"ky1024_bp384"_L1);
320 qCDebug(LIBKLEO_LOG) << __func__ <<
"Unsupported Kyber parameters" << parameters.subkeyLength() << parameters.keyCurve();
324 index = ui.keyAlgoCB->findData(parameters.keyCurve());
327 ui.keyAlgoCB->setCurrentIndex(index);
329 setExpiryDate(parameters.expirationDate());
335 if (ui.nameAndEmail->userID().isEmpty() && !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) {
338 const auto nameError = ui.nameAndEmail->nameError();
339 if (!nameError.isEmpty()) {
342 const auto emailError = ui.nameAndEmail->emailError();
343 if (!emailError.isEmpty()) {
346 if (!Expiration::isValidExpirationDate(expiryDate())) {
347 errors.
push_back(Expiration::validityPeriodHint());
349 if (errors.
size() > 1) {
351 }
else if (!errors.
empty()) {
358 QDate forceDateIntoAllowedRange(
QDate date)
const
360 const auto minDate = ui.expiryDE->minimumDate();
361 if (minDate.isValid() && date < minDate) {
364 const auto maxDate = ui.expiryDE->maximumDate();
365 if (maxDate.isValid() && date > maxDate) {
371 void setExpiryDate(
QDate date)
374 ui.expiryDE->setDate(forceDateIntoAllowedRange(date));
377 if (unlimitedValidityIsAllowed()) {
378 ui.expiryDE->setDate(date);
381 if (ui.expiryCB->isEnabled()) {
382 ui.expiryCB->setChecked(ui.expiryDE->isValid());
387 KeyParameters technicalParameters;
392 , d(new Private{this})
394 resize(std::max(sizeHint().width(), d->ui.expander->contentWidth()) + 20, sizeHint().height() + 20);
397OpenPGPCertificateCreationDialog::~OpenPGPCertificateCreationDialog() =
default;
399void OpenPGPCertificateCreationDialog::setName(
const QString &name)
401 d->ui.nameAndEmail->setName(name);
404QString OpenPGPCertificateCreationDialog::name()
const
406 return d->ui.nameAndEmail->name();
409void OpenPGPCertificateCreationDialog::setEmail(
const QString &email)
411 d->ui.nameAndEmail->setEmail(email);
414QString OpenPGPCertificateCreationDialog::email()
const
416 return d->ui.nameAndEmail->email();
419void Kleo::OpenPGPCertificateCreationDialog::setKeyParameters(
const Kleo::KeyParameters ¶meters)
421 setName(parameters.name());
422 const auto emails = parameters.emails();
423 if (!emails.empty()) {
424 setEmail(emails.front());
426 d->setTechnicalParameters(parameters);
429KeyParameters OpenPGPCertificateCreationDialog::keyParameters()
const
432 auto parameters = d->technicalParameters;
433 if (!name().isEmpty()) {
434 parameters.setName(name());
436 if (!email().isEmpty()) {
437 parameters.setEmail(email());
442void Kleo::OpenPGPCertificateCreationDialog::setProtectKeyWithPassword(
bool protectKey)
444 d->ui.withPassCheckBox->setChecked(protectKey);
447bool OpenPGPCertificateCreationDialog::protectKeyWithPassword()
const
449 return d->ui.withPassCheckBox->isChecked();
452#include "moc_openpgpcertificatecreationdialog.cpp"
void dateChanged(const QDate &date)
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 currentIndexChanged(int index)
bool isValid(int year, int month, int day)
void push_back(parameter_type value)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString fromStdString(const std::string &str)