diff --git a/src/newcertificatewizard/advancedsettingsdialog.cpp b/src/newcertificatewizard/advancedsettingsdialog.cpp index 9b6322c53..fcfd950f0 100644 --- a/src/newcertificatewizard/advancedsettingsdialog.cpp +++ b/src/newcertificatewizard/advancedsettingsdialog.cpp @@ -1,1101 +1,1105 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/advancedsettingsdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "advancedsettingsdialog_p.h" #include "keyalgo_p.h" #include "listwidget.h" #include "utils/scrollarea.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames { { QStringLiteral("brainpoolP256r1") }, { QStringLiteral("brainpoolP384r1") }, { QStringLiteral("brainpoolP512r1") }, { QStringLiteral("NIST P-256") }, { QStringLiteral("NIST P-384") }, { QStringLiteral("NIST P-521") }, }; namespace { static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast(strength)); cb->setCurrentIndex(idx); } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findText(curve, Qt::MatchFixedString); if (idx < 0) { // Can't happen as we don't have them configurable. qCWarning(KLEOPATRA_LOG) << "curve " << curve << " not allowed"; } cb->setCurrentIndex(idx); } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } // Extract the algo information from default_pubkey_algo format // // and put it into the return values size, algo and curve. // // Values look like: // RSA-2048 // rsa2048/cert,sign+rsa2048/enc // brainpoolP256r1+brainpoolP256r1 static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve) { const auto split = algoString.split(QLatin1Char('/')); bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1String("enc")); // Normalize const auto lowered = split[0].toLower().remove(QLatin1Char('-')); if (!algo || !size) { return; } *algo = Subkey::AlgoUnknown; if (lowered.startsWith(QLatin1String("rsa"))) { *algo = Subkey::AlgoRSA; } else if (lowered.startsWith(QLatin1String("dsa"))) { *algo = Subkey::AlgoDSA; } else if (lowered.startsWith(QLatin1String("elg"))) { *algo = Subkey::AlgoELG; } if (*algo != Subkey::AlgoUnknown) { bool ok; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) *size = lowered.rightRef(lowered.size() - 3).toInt(&ok); +#else + *size = QStringView(lowered).right(lowered.size() - 3).toInt(&ok); +#endif if (!ok) { qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered; *size = 3072; } return; } // Now the ECC Algorithms if (lowered.startsWith(QLatin1String("ed25519"))) { // Special handling for this as technically // this is a cv25519 curve used for EDDSA if (isEncrypt) { curve = QLatin1String("cv25519"); *algo = Subkey::AlgoECDH; } else { curve = split[0]; *algo = Subkey::AlgoEDDSA; } return; } if (lowered.startsWith(QLatin1String("cv25519")) || lowered.startsWith(QLatin1String("nist")) || lowered.startsWith(QLatin1String("brainpool")) || lowered.startsWith(QLatin1String("secp"))) { curve = split[0]; *algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA; return; } qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString; } enum class OnUnlimitedValidity { ReturnInvalidDate, ReturnInternalDefault }; QDate defaultExpirationDate(OnUnlimitedValidity onUnlimitedValidity) { QDate expirationDate{}; const auto settings = Kleo::Settings{}; const auto defaultExpirationInDays = settings.validityPeriodInDays(); if (defaultExpirationInDays > 0) { expirationDate = QDate::currentDate().addDays(defaultExpirationInDays); } else if (defaultExpirationInDays < 0 || onUnlimitedValidity == OnUnlimitedValidity::ReturnInternalDefault) { expirationDate = QDate::currentDate().addYears(2); } return expirationDate; } } struct AdvancedSettingsDialog::UI { QTabWidget *tabWidget = nullptr; QRadioButton *rsaRB = nullptr; QComboBox *rsaKeyStrengthCB = nullptr; QCheckBox *rsaSubCB = nullptr; QComboBox *rsaKeyStrengthSubCB = nullptr; QRadioButton *dsaRB = nullptr; QComboBox *dsaKeyStrengthCB = nullptr; QCheckBox *elgCB = nullptr; QComboBox *elgKeyStrengthCB = nullptr; QRadioButton *ecdsaRB = nullptr; QComboBox *ecdsaKeyCurvesCB = nullptr; QCheckBox *ecdhCB = nullptr; QComboBox *ecdhKeyCurvesCB = nullptr; QCheckBox *certificationCB = nullptr; QCheckBox *signingCB = nullptr; QCheckBox *encryptionCB = nullptr; QCheckBox *authenticationCB = nullptr; QCheckBox *expiryCB = nullptr; KDateComboBox *expiryDE = nullptr; ScrollArea *personalTab = nullptr; QGroupBox *uidGB = nullptr; Kleo::NewCertificateUi::ListWidget *uidLW = nullptr; QGroupBox *emailGB = nullptr; Kleo::NewCertificateUi::ListWidget *emailLW = nullptr; QGroupBox *dnsGB = nullptr; Kleo::NewCertificateUi::ListWidget *dnsLW = nullptr; QGroupBox *uriGB = nullptr; Kleo::NewCertificateUi::ListWidget *uriLW = nullptr; QDialogButtonBox *buttonBox = nullptr; UI(QDialog *parent) { parent->setWindowTitle(i18nc("@title:window", "Advanced Settings")); auto mainLayout = new QVBoxLayout{parent}; tabWidget = new QTabWidget{parent}; { auto technicalTab = new ScrollArea{tabWidget}; technicalTab->setFocusPolicy(Qt::NoFocus); technicalTab->setFrameStyle(QFrame::NoFrame); technicalTab->setBackgroundRole(parent->backgroundRole()); technicalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); technicalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto tabLayout = qobject_cast(technicalTab->widget()->layout()); { auto groupBox = new QGroupBox{i18nc("@title:group", "Key Material"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; rsaRB = new QRadioButton{i18nc("@option:radio", "RSA"), groupBox}; rsaRB->setChecked(false); groupBoxGrid->addWidget(rsaRB, row, 0, 1, 2); rsaKeyStrengthCB = new QComboBox{groupBox}; rsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthCB, row, 2, 1, 1); row++; auto subKeyIndentation = new QSpacerItem(13, 13, QSizePolicy::Fixed, QSizePolicy::Minimum); groupBoxGrid->addItem(subKeyIndentation, row, 0, 1, 1); rsaSubCB = new QCheckBox{i18nc("@option:check", "+ RSA"), groupBox}; rsaSubCB->setEnabled(true); groupBoxGrid->addWidget(rsaSubCB, row, 1, 1, 1); rsaKeyStrengthSubCB = new QComboBox{groupBox}; rsaKeyStrengthSubCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthSubCB, row, 2, 1, 1); row++; dsaRB = new QRadioButton{i18nc("@option:radio", "DSA"), groupBox}; groupBoxGrid->addWidget(dsaRB, row, 0, 1, 2); dsaKeyStrengthCB = new QComboBox{groupBox}; dsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(dsaKeyStrengthCB, row, 2, 1, 1); row++; elgCB = new QCheckBox{i18nc("@option:check", "+ Elgamal"), groupBox}; elgCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); elgCB->setEnabled(true); groupBoxGrid->addWidget(elgCB, row, 1, 1, 1); elgKeyStrengthCB = new QComboBox{groupBox}; elgKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(elgKeyStrengthCB, row, 2, 1, 1); row++; ecdsaRB = new QRadioButton{i18nc("@option:radio", "ECDSA"), groupBox}; groupBoxGrid->addWidget(ecdsaRB, row, 0, 1, 2); ecdsaKeyCurvesCB = new QComboBox{groupBox}; ecdsaKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdsaKeyCurvesCB, row, 2, 1, 1); row++; ecdhCB = new QCheckBox{i18nc("@option:check", "+ ECDH"), groupBox}; ecdhCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); ecdhCB->setEnabled(true); groupBoxGrid->addWidget(ecdhCB, row, 1, 1, 1); ecdhKeyCurvesCB = new QComboBox{groupBox}; ecdhKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdhKeyCurvesCB, row, 2, 1, 1); groupBoxGrid->setColumnStretch(3, 1); tabLayout->addWidget(groupBox); } { auto groupBox = new QGroupBox{i18nc("@title:group", "Certificate Usage"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; signingCB = new QCheckBox{i18nc("@option:check", "Signing"), groupBox}; signingCB->setChecked(true); groupBoxGrid->addWidget(signingCB, row, 0, 1, 1); certificationCB = new QCheckBox{i18nc("@option:check", "Certification"), groupBox}; groupBoxGrid->addWidget(certificationCB, row, 1, 1, 1); row++; encryptionCB = new QCheckBox{i18nc("@option:check", "Encryption"), groupBox}; encryptionCB->setChecked(true); groupBoxGrid->addWidget(encryptionCB, row, 0, 1, 1); authenticationCB = new QCheckBox{i18nc("@option:check", "Authentication"), groupBox}; groupBoxGrid->addWidget(authenticationCB, row, 1, 1, 1); row++; { auto hbox = new QHBoxLayout; expiryCB = new QCheckBox{i18nc("@option:check", "Valid until:"), groupBox}; hbox->addWidget(expiryCB); expiryDE = new KDateComboBox(groupBox); hbox->addWidget(expiryDE, 1); groupBoxGrid->addLayout(hbox, row, 0, 1, 2); } tabLayout->addWidget(groupBox); } tabLayout->addStretch(1); tabWidget->addTab(technicalTab, i18nc("@title:tab", "Technical Details")); } { personalTab = new ScrollArea{tabWidget}; personalTab->setFocusPolicy(Qt::NoFocus); personalTab->setFrameStyle(QFrame::NoFrame); personalTab->setBackgroundRole(parent->backgroundRole()); personalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); personalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(personalTab->widget()->layout()); auto tabGrid = new QGridLayout; uidGB = new QGroupBox{i18nc("@title:group", "Additional User IDs"), personalTab}; { auto layout = new QVBoxLayout{uidGB}; uidLW = new Kleo::NewCertificateUi::ListWidget{uidGB}; layout->addWidget(uidLW); } tabGrid->addWidget(uidGB, 0, 0, 1, 2); emailGB = new QGroupBox{i18nc("@title:group", "EMail Addresses"), personalTab}; { auto layout = new QVBoxLayout{emailGB}; emailLW = new Kleo::NewCertificateUi::ListWidget{emailGB}; layout->addWidget(emailLW); } tabGrid->addWidget(emailGB, 2, 0, 2, 1); dnsGB = new QGroupBox{i18nc("@title:group", "DNS Names"), personalTab}; { auto layout = new QVBoxLayout{dnsGB}; dnsLW = new Kleo::NewCertificateUi::ListWidget{dnsGB}; layout->addWidget(dnsLW); } tabGrid->addWidget(dnsGB, 2, 1, 1, 1); uriGB = new QGroupBox{i18nc("@title:group", "URIs"), personalTab}; { auto layout = new QVBoxLayout{uriGB}; uriLW = new Kleo::NewCertificateUi::ListWidget{uriGB}; layout->addWidget(uriLW); } tabGrid->addWidget(uriGB, 3, 1, 1, 1); scrollAreaLayout->addLayout(tabGrid); tabWidget->addTab(personalTab, i18nc("@title:tab", "Personal Details")); } mainLayout->addWidget(tabWidget); buttonBox = new QDialogButtonBox{parent}; buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); mainLayout->addWidget(buttonBox); } }; AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent) : QDialog{parent} , ui{new UI{this}} , mECCSupported{engineIsVersion(2, 1, 0)} , mEdDSASupported{engineIsVersion(2, 1, 15)} { qRegisterMetaType("Subkey::PubkeyAlgo"); const auto settings = Kleo::Settings{}; { const auto minimumExpiry = std::max(0, settings.validityPeriodInDaysMin()); ui->expiryDE->setMinimumDate(QDate::currentDate().addDays(minimumExpiry)); } { const auto maximumExpiry = settings.validityPeriodInDaysMax(); if (maximumExpiry >= 0) { ui->expiryDE->setMaximumDate(std::max(ui->expiryDE->minimumDate(), QDate::currentDate().addDays(maximumExpiry))); } } if (unlimitedValidityIsAllowed()) { ui->expiryDE->setEnabled(ui->expiryCB->isChecked()); } else { ui->expiryCB->setEnabled(false); ui->expiryCB->setChecked(true); if (ui->expiryDE->maximumDate() == ui->expiryDE->minimumDate()) { // validity period is a fixed number of days ui->expiryDE->setEnabled(false); } } ui->expiryDE->setToolTip(validityPeriodHint(ui->expiryDE->minimumDate(), ui->expiryDE->maximumDate())); ui->emailLW->setDefaultValue(i18n("new email")); ui->dnsLW->setDefaultValue(i18n("new dns name")); ui->uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); connect(ui->rsaRB, &QAbstractButton::toggled, ui->rsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->rsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->rsaSubCB, &QAbstractButton::toggled, ui->rsaKeyStrengthSubCB, &QWidget::setEnabled); connect(ui->rsaSubCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->dsaRB, &QAbstractButton::toggled, ui->dsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->dsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->elgCB, &QAbstractButton::toggled, ui->elgKeyStrengthCB, &QWidget::setEnabled); connect(ui->elgCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdsaRB, &QAbstractButton::toggled, ui->ecdsaKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdhCB, &QAbstractButton::toggled, ui->ecdhKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdhCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->signingCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotSigningAllowedToggled); connect(ui->encryptionCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotEncryptionAllowedToggled); connect(ui->expiryCB, &QAbstractButton::toggled, this, [this](bool checked) { ui->expiryDE->setEnabled(checked); if (checked && !ui->expiryDE->isValid()) { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInternalDefault)); } }); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } AdvancedSettingsDialog::~AdvancedSettingsDialog() = default; QString AdvancedSettingsDialog::dateToString(const QDate &date) const { // workaround for QLocale using "yy" way too often for years // stolen from KDateComboBox const auto dateFormat = (locale().dateFormat(QLocale::ShortFormat) // .replace(QLatin1String{"yy"}, QLatin1String{"yyyy"}) .replace(QLatin1String{"yyyyyyyy"}, QLatin1String{"yyyy"})); return locale().toString(date, dateFormat); } QString AdvancedSettingsDialog::validityPeriodHint(const QDate &minDate, const QDate &maxDate) const { // Note: minDate is always valid const auto today = QDate::currentDate(); if (maxDate.isValid()) { if (maxDate == minDate) { return i18n("The validity period cannot be changed."); } else if (minDate == today) { return i18nc("... between today and .", "The validity period must end between today and %1.", dateToString(maxDate)); } else { return i18nc("... between and .", "The validity period must end between %1 and %2.", dateToString(minDate), dateToString(maxDate)); } } else { if (minDate == today) { return i18n("The validity period must end after today."); } else { return i18nc("... after .", "The validity period must end after %1.", dateToString(minDate)); } } } bool AdvancedSettingsDialog::unlimitedValidityIsAllowed() const { return !ui->expiryDE->maximumDate().isValid(); } void AdvancedSettingsDialog::setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaults(); } void AdvancedSettingsDialog::setAdditionalUserIDs(const QStringList &items) { ui->uidLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalUserIDs() const { return ui->uidLW->items(); } void AdvancedSettingsDialog::setAdditionalEMailAddresses(const QStringList &items) { ui->emailLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalEMailAddresses() const { return ui->emailLW->items(); } void AdvancedSettingsDialog::setDnsNames(const QStringList &items) { ui->dnsLW->setItems(items); } QStringList AdvancedSettingsDialog::dnsNames() const { return ui->dnsLW->items(); } void AdvancedSettingsDialog::setUris(const QStringList &items) { ui->uriLW->setItems(items); } QStringList AdvancedSettingsDialog::uris() const { return ui->uriLW->items(); } void AdvancedSettingsDialog::setKeyStrength(unsigned int strength) { set_keysize(ui->rsaKeyStrengthCB, strength); set_keysize(ui->dsaKeyStrengthCB, strength); } unsigned int AdvancedSettingsDialog::keyStrength() const { return ui->dsaRB->isChecked() ? get_keysize(ui->dsaKeyStrengthCB) : ui->rsaRB->isChecked() ? get_keysize(ui->rsaKeyStrengthCB) : 0; } void AdvancedSettingsDialog::setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui->rsaRB : is_dsa(algo) ? ui->dsaRB : is_ecdsa(algo) || is_eddsa(algo) ? ui->ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo AdvancedSettingsDialog::keyType() const { return ui->dsaRB->isChecked() ? Subkey::AlgoDSA : ui->rsaRB->isChecked() ? Subkey::AlgoRSA : ui->ecdsaRB->isChecked() ? ui->ecdsaKeyCurvesCB->currentText() == QLatin1String("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setKeyCurve(const QString &curve) { set_curve(ui->ecdsaKeyCurvesCB, curve); } QString AdvancedSettingsDialog::keyCurve() const { return get_curve(ui->ecdsaKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyType(Subkey::PubkeyAlgo algo) { ui->elgCB->setChecked(is_elg(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); ui->ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo AdvancedSettingsDialog::subkeyType() const { if (ui->elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui->rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui->ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setSubkeyCurve(const QString &curve) { set_curve(ui->ecdhKeyCurvesCB, curve); } QString AdvancedSettingsDialog::subkeyCurve() const { return get_curve(ui->ecdhKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui->rsaKeyStrengthSubCB, strength); } else { set_keysize(ui->elgKeyStrengthCB, strength); } } unsigned int AdvancedSettingsDialog::subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui->rsaKeyStrengthSubCB); } return get_keysize(ui->elgKeyStrengthCB); } void AdvancedSettingsDialog::setSigningAllowed(bool on) { ui->signingCB->setChecked(on); } bool AdvancedSettingsDialog::signingAllowed() const { return ui->signingCB->isChecked(); } void AdvancedSettingsDialog::setEncryptionAllowed(bool on) { ui->encryptionCB->setChecked(on); } bool AdvancedSettingsDialog::encryptionAllowed() const { return ui->encryptionCB->isChecked(); } void AdvancedSettingsDialog::setCertificationAllowed(bool on) { ui->certificationCB->setChecked(on); } bool AdvancedSettingsDialog::certificationAllowed() const { return ui->certificationCB->isChecked(); } void AdvancedSettingsDialog::setAuthenticationAllowed(bool on) { ui->authenticationCB->setChecked(on); } bool AdvancedSettingsDialog::authenticationAllowed() const { return ui->authenticationCB->isChecked(); } QDate AdvancedSettingsDialog::forceDateIntoAllowedRange(QDate date) const { const auto minDate = ui->expiryDE->minimumDate(); if (minDate.isValid() && date < minDate) { date = minDate; } const auto maxDate = ui->expiryDE->maximumDate(); if (maxDate.isValid() && date > maxDate) { date = maxDate; } return date; } void AdvancedSettingsDialog::setExpiryDate(QDate date) { if (date.isValid()) { ui->expiryDE->setDate(forceDateIntoAllowedRange(date)); } else { // check if unlimited validity is allowed if (unlimitedValidityIsAllowed()) { ui->expiryDE->setDate(date); } } if (ui->expiryCB->isEnabled()) { ui->expiryCB->setChecked(ui->expiryDE->isValid()); } } QDate AdvancedSettingsDialog::expiryDate() const { return ui->expiryCB->isChecked() ? forceDateIntoAllowedRange(ui->expiryDE->date()) : QDate(); } void AdvancedSettingsDialog::slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { // first update the enabled state, but only if key type is not forced if (!keyTypeImmutable) { ui->elgCB->setEnabled(is_dsa(algo)); ui->rsaSubCB->setEnabled(is_rsa(algo)); ui->ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo)); if (is_rsa(algo)) { ui->encryptionCB->setEnabled(true); ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setEnabled(false); } else { ui->encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui->encryptionCB->setEnabled(false); } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); ui->encryptionCB->setEnabled(false); } } // then update the checked state if (sender() == ui->dsaRB || sender() == ui->rsaRB || sender() == ui->ecdsaRB) { ui->elgCB->setChecked(is_dsa(algo)); ui->ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui->encryptionCB->setChecked(true); ui->signingCB->setChecked(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setChecked(true); } } else if (is_dsa(algo)) { if (is_elg(sk_algo)) { ui->encryptionCB->setChecked(true); } else { ui->encryptionCB->setChecked(false); } } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setChecked(true); ui->encryptionCB->setChecked(is_ecdh(sk_algo)); } } else { //assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void AdvancedSettingsDialog::slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void AdvancedSettingsDialog::slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } static void fill_combobox(QComboBox &cb, const QList &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { const int size = std::abs(sizes[i]); /* As we respect the defaults configurable in GnuPG, and we also have configurable * defaults in Kleopatra its difficult to print out "default" here. To avoid confusion * about that its better not to show any default indication. */ cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", size, labels[i].trimmed()) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", size), size); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QList rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << 2048 << -3072 << 4096); if (Kleo::gnupgUsesDeVsCompliance()) { rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << -3072 << 4096); } const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui->rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui->elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); if (mEdDSASupported) { // If supported we recommend cv25519 ui->ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519")); ui->ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519")); } ui->ecdhKeyCurvesCB->addItems(curveNames); ui->ecdsaKeyCurvesCB->addItems(curveNames); } // Try to load the default key type from GnuPG void AdvancedSettingsDialog::loadDefaultGnuPGKeyType() { const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = getCryptoConfigEntry(conf, protocol == CMS ? "gpgsm" : "gpg", "default_pubkey_algo"); if (!entry) { qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA"; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); return; } qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue(); // Format is [/usage]+[/usage] const auto split = entry->stringValue().split(QLatin1Char('+')); int size = 0; Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown; QString curve; parseAlgoString(split[0], &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setKeyType(algo); if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) { setKeyStrength(size); } else { setKeyCurve(curve); } { auto algoString = (split.size() == 2) ? split[1] : split[0]; // If it has no usage we assume encrypt subkey if (!algoString.contains(QLatin1Char('/'))) { algoString += QStringLiteral("/enc"); } parseAlgoString(algoString, &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setSubkeyType(algo); if (is_rsa(algo) || is_elg(algo)) { setSubkeyStrength(size); } else { setSubkeyCurve(curve); } } } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); } else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) { loadDefaultGnuPGKeyType(); } else { if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); } void AdvancedSettingsDialog::loadDefaultExpiration() { if (protocol != OpenPGP) { return; } if (unlimitedValidityIsAllowed()) { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInvalidDate)); } else { setExpiryDate(defaultExpirationDate(OnUnlimitedValidity::ReturnInternalDefault)); } } void AdvancedSettingsDialog::loadDefaults() { loadDefaultKeyType(); loadDefaultExpiration(); updateWidgetVisibility(); setInitialFocus(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui->tabWidget->indexOf(ui->personalTab) != -1) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->personalTab)); } } else { if (ui->tabWidget->indexOf(ui->personalTab) == -1) { ui->tabWidget->addTab(ui->personalTab, i18nc("@title:tab", "Personal Details")); } } ui->uidGB->setVisible(protocol == OpenPGP); ui->uidGB->setEnabled(false); ui->uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one user ID is not yet implemented.")); ui->emailGB->setVisible(protocol == CMS); ui->dnsGB->setVisible(protocol == CMS); ui->uriGB->setVisible(protocol == CMS); // Technical Details Page ui->ecdhCB->setVisible(mECCSupported); ui->ecdhKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaRB->setVisible(mECCSupported); if (mEdDSASupported) { // We use the same radio button for EdDSA as we use for // ECDSA GnuPG does the same and this is really super technical // land. ui->ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA")); } const bool deVsHack = Kleo::gnupgUsesDeVsCompliance(); if (deVsHack) { // GnuPG Provides no API to query which keys are compliant for // a mode. If we request a different one it will error out so // we have to remove the options. // // Does anyone want to use NIST anyway? int i; while ((i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdsaKeyCurvesCB->removeItem(i); } while ((i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdhKeyCurvesCB->removeItem(i); } } ui->certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui->authenticationCB->setVisible(protocol == OpenPGP); if (keyTypeImmutable) { ui->rsaRB->setEnabled(false); ui->rsaSubCB->setEnabled(false); ui->dsaRB->setEnabled(false); ui->elgCB->setEnabled(false); ui->ecdsaRB->setEnabled(false); ui->ecdhCB->setEnabled(false); // force usage if key type is forced ui->certificationCB->setEnabled(false); ui->signingCB->setEnabled(false); ui->encryptionCB->setEnabled(false); ui->authenticationCB->setEnabled(false); } else { ui->rsaRB->setEnabled(true); ui->rsaSubCB->setEnabled(protocol == OpenPGP); ui->dsaRB->setEnabled(protocol == OpenPGP && !deVsHack); ui->elgCB->setEnabled(protocol == OpenPGP && !deVsHack); ui->ecdsaRB->setEnabled(protocol == OpenPGP); ui->ecdhCB->setEnabled(protocol == OpenPGP); if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setEnabled(false); } if (protocol == CMS) { ui->encryptionCB->setEnabled(true); ui->rsaKeyStrengthSubCB->setEnabled(false); } } if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setChecked(true); } if (protocol == CMS) { ui->rsaSubCB->setChecked(false); } ui->expiryDE->setVisible(protocol == OpenPGP); ui->expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } template bool focusFirstButtonIf(const std::vector &buttons, UnaryPredicate p) { auto it = std::find_if(std::begin(buttons), std::end(buttons), p); if (it != std::end(buttons)) { (*it)->setFocus(); return true; } return false; } static bool focusFirstCheckedButton(const std::vector &buttons) { return focusFirstButtonIf(buttons, [](auto btn) { return btn && btn->isEnabled() && btn->isChecked(); }); } static bool focusFirstEnabledButton(const std::vector &buttons) { return focusFirstButtonIf(buttons, [](auto btn) { return btn && btn->isEnabled(); }); } void AdvancedSettingsDialog::setInitialFocus() { // first try the key type radio buttons if (focusFirstCheckedButton({ui->rsaRB, ui->dsaRB, ui->ecdsaRB})) { return; } // then try the usage check boxes and the expiration check box if (focusFirstEnabledButton({ui->signingCB, ui->certificationCB, ui->encryptionCB, ui->authenticationCB, ui->expiryCB})) { return; } // finally, focus the OK button ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); }