diff --git a/server/identity/identitydialog.cpp b/server/identity/identitydialog.cpp index c40e436..2306d0c 100644 --- a/server/identity/identitydialog.cpp +++ b/server/identity/identitydialog.cpp @@ -1,627 +1,628 @@ /* identitydialog.cpp This file is part of KMail, the KDE mail client. SPDX-FileCopyrightText: 2002 Marc Mutz SPDX-FileCopyrightText: 2014-2023 Laurent Montel SPDX-License-Identifier: GPL-2.0-only */ #include "identitydialog.h" #include "identitymanager.h" #include "addressvalidationjob.h" #include "kleo_util.h" #include #include // other KMail headers: #include #include // other kdepim headers: #include #include #include // libkleopatra: #include #include #include #include // gpgme++ #include // other KDE headers: #include #include #include #include #include // Qt headers: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // other headers: #include #include #include namespace KMail { class KeySelectionCombo : public Kleo::KeySelectionCombo { Q_OBJECT public: enum KeyType { SigningKey, EncryptionKey, }; explicit KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent); ~KeySelectionCombo() override; void setIdentity(const QString &name, const QString &email); void init() override; private: void onCustomItemSelected(const QVariant &type); QString mEmail; QString mName; const KeyType mKeyType; const GpgME::Protocol mProtocol; }; class KeyGenerationJob : public QGpgME::Job { Q_OBJECT public: explicit KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent); ~KeyGenerationJob() override; void slotCancel() override; void start(); private: void keyGenerated(const GpgME::KeyGenerationResult &result); const QString mName; const QString mEmail; QGpgME::Job *mJob = nullptr; }; KeyGenerationJob::KeyGenerationJob(const QString &name, const QString &email, KeySelectionCombo *parent) : QGpgME::Job(parent) , mName(name) , mEmail(email) { } KeyGenerationJob::~KeyGenerationJob() = default; void KeyGenerationJob::slotCancel() { if (mJob) { mJob->slotCancel(); } } void KeyGenerationJob::start() { auto job = new Kleo::DefaultKeyGenerationJob(this); connect(job, &Kleo::DefaultKeyGenerationJob::result, this, &KeyGenerationJob::keyGenerated); job->start(mEmail, mName); mJob = job; } void KeyGenerationJob::keyGenerated(const GpgME::KeyGenerationResult &result) { mJob = nullptr; if (result.error()) { KMessageBox::error(qobject_cast(parent()), i18n("Error while generating new key pair: %1", QString::fromUtf8(result.error().asString())), i18n("Key Generation Error")); Q_EMIT done(); return; } auto combo = qobject_cast(parent()); combo->setDefaultKey(QLatin1String(result.fingerprint())); connect(combo, &KeySelectionCombo::keyListingFinished, this, &KeyGenerationJob::done); combo->refreshKeys(); } KeySelectionCombo::KeySelectionCombo(KeyType keyType, GpgME::Protocol protocol, QWidget *parent) : Kleo::KeySelectionCombo(parent) , mKeyType(keyType) , mProtocol(protocol) { } KeySelectionCombo::~KeySelectionCombo() = default; void KeySelectionCombo::setIdentity(const QString &name, const QString &email) { mName = name; mEmail = email; setIdFilter(email); } void KeySelectionCombo::init() { Kleo::KeySelectionCombo::init(); std::shared_ptr keyFilter(new Kleo::DefaultKeyFilter); keyFilter->setIsOpenPGP(mProtocol == GpgME::OpenPGP ? Kleo::DefaultKeyFilter::Set : Kleo::DefaultKeyFilter::NotSet); if (mKeyType == SigningKey) { keyFilter->setCanSign(Kleo::DefaultKeyFilter::Set); keyFilter->setHasSecret(Kleo::DefaultKeyFilter::Set); } else { keyFilter->setCanEncrypt(Kleo::DefaultKeyFilter::Set); } setKeyFilter(keyFilter); prependCustomItem(QIcon(), i18n("No key"), QStringLiteral("no-key")); if (mProtocol == GpgME::OpenPGP) { appendCustomItem(QIcon::fromTheme(QStringLiteral("password-generate")), i18n("Generate a new key pair"), QStringLiteral("generate-new-key")); } connect(this, &KeySelectionCombo::customItemSelected, this, &KeySelectionCombo::onCustomItemSelected); } void KeySelectionCombo::onCustomItemSelected(const QVariant &type) { if (type == QLatin1String("no-key")) { return; } else if (type == QLatin1String("generate-new-key")) { auto job = new KeyGenerationJob(mName, mEmail, this); auto dlg = new Kleo::ProgressDialog(job, i18n("Generating new key pair..."), parentWidget()); dlg->setModal(true); setEnabled(false); connect(job, &KeyGenerationJob::done, this, [this]() { setEnabled(true); }); job->start(); } } IdentityDialog::IdentityDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Edit Identity")); auto mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins({}); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &IdentityDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &IdentityDialog::reject); // // Tab Widget: General // auto page = new QWidget(this); mainLayout->addWidget(page); auto buttonBoxLayout = new QVBoxLayout; buttonBoxLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin), style()->pixelMetric(QStyle::PM_LayoutTopMargin), style()->pixelMetric(QStyle::PM_LayoutRightMargin), style()->pixelMetric(QStyle::PM_LayoutBottomMargin)); buttonBoxLayout->addWidget(buttonBox); mainLayout->addLayout(buttonBoxLayout); auto vlay = new QVBoxLayout(page); vlay->setContentsMargins({}); mTabWidget = new QTabWidget(page); mTabWidget->tabBar()->setExpanding(true); mTabWidget->setDocumentMode(true); mTabWidget->setObjectName(QStringLiteral("config-identity-tab")); vlay->addWidget(mTabWidget); auto tab = new QWidget(mTabWidget); mTabWidget->addTab(tab, i18nc("@title:tab General identity settings.", "General")); auto formLayout = new QFormLayout(tab); // "Name" line edit and label: mNameEdit = new QLineEdit(tab); KLineEditEventHandler::catchReturnKey(mNameEdit); auto label = new QLabel(i18n("&Your name:"), tab); formLayout->addRow(label, mNameEdit); label->setBuddy(mNameEdit); QString msg = i18n( "

Your name

" "

This field should contain your name as you would like " "it to appear in the email header that is sent out;

" "

if you leave this blank your real name will not " "appear, only the email address.

"); label->setWhatsThis(msg); mNameEdit->setWhatsThis(msg); // "Organization" line edit and label: mOrganizationEdit = new QLineEdit(tab); KLineEditEventHandler::catchReturnKey(mOrganizationEdit); label = new QLabel(i18n("Organi&zation:"), tab); formLayout->addRow(label, mOrganizationEdit); label->setBuddy(mOrganizationEdit); msg = i18n( "

Organization

" "

This field should have the name of your organization " "if you would like it to be shown in the email header that " "is sent out.

" "

It is safe (and normal) to leave this blank.

"); label->setWhatsThis(msg); mOrganizationEdit->setWhatsThis(msg); // "Dictionary" combo box and label: mDictionaryCombo = new Sonnet::DictionaryComboBox(tab); label = new QLabel(i18n("D&ictionary:"), tab); label->setBuddy(mDictionaryCombo); formLayout->addRow(label, mDictionaryCombo); // "Email Address" line edit and label: // (row 3: spacer) mEmailEdit = new QLineEdit(tab); + mEmailEdit->setEnabled(false); KLineEditEventHandler::catchReturnKey(mEmailEdit); label = new QLabel(i18n("&Email address:"), tab); formLayout->addRow(label, mEmailEdit); label->setBuddy(mEmailEdit); msg = i18n( "

Email address

" "

This field should have your full email address.

" "

This address is the primary one, used for all outgoing mail. " "If you have more than one address, either create a new identity, " "or add additional alias addresses in the field below.

" "

If you leave this blank, or get it wrong, people " "will have trouble replying to you.

"); label->setWhatsThis(msg); mEmailEdit->setWhatsThis(msg); auto emailValidator = new KEmailValidator(this); mEmailEdit->setValidator(emailValidator); // // Tab Widget: Security // mCryptographyTab = new QWidget(mTabWidget); mTabWidget->addTab(mCryptographyTab, i18nc("@title:tab", "Security")); formLayout = new QFormLayout(mCryptographyTab); // "OpenPGP Signature Key" requester and label: mPGPSigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::OpenPGP, mCryptographyTab); mPGPSigningKeyRequester->setObjectName("PGP Signing Key Requester"); msg = i18n( "

The OpenPGP key you choose here will be used " "to digitally sign messages. You can also use GnuPG keys.

" "

You can leave this blank, but KMail will not be able " "to digitally sign emails using OpenPGP; " "normal mail functions will not be affected.

" "

You can find out more about keys at https://www.gnupg.org

"); label = new QLabel(i18n("OpenPGP signing key:"), mCryptographyTab); label->setBuddy(mPGPSigningKeyRequester); mPGPSigningKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); auto vbox = new QVBoxLayout; mPGPSameKey = new QCheckBox(i18n("Use same key for encryption and signing")); vbox->addWidget(mPGPSigningKeyRequester); vbox->addWidget(mPGPSameKey); formLayout->addRow(label, vbox); connect(mPGPSameKey, &QCheckBox::toggled, this, [=](bool checked) { mPGPEncryptionKeyRequester->setVisible(!checked); formLayout->labelForField(mPGPEncryptionKeyRequester)->setVisible(!checked); const auto label = qobject_cast(formLayout->labelForField(vbox)); if (checked) { label->setText(i18n("OpenPGP key:")); const auto key = mPGPSigningKeyRequester->currentKey(); if (!key.isBad()) { mPGPEncryptionKeyRequester->setCurrentKey(key); } else if (mPGPSigningKeyRequester->currentData() == QLatin1String("no-key")) { mPGPEncryptionKeyRequester->setCurrentIndex(mPGPSigningKeyRequester->currentIndex()); } } else { label->setText(i18n("OpenPGP signing key:")); } }); connect(mPGPSigningKeyRequester, &KeySelectionCombo::currentKeyChanged, this, [&](const GpgME::Key &key) { if (mPGPSameKey->isChecked()) { mPGPEncryptionKeyRequester->setCurrentKey(key); } }); connect(mPGPSigningKeyRequester, &KeySelectionCombo::customItemSelected, this, [&](const QVariant &type) { if (mPGPSameKey->isChecked() && type == QLatin1String("no-key")) { mPGPEncryptionKeyRequester->setCurrentIndex(mPGPSigningKeyRequester->currentIndex()); } }); connect(mPGPSigningKeyRequester, &KeySelectionCombo::keyListingFinished, this, [this] { slotKeyListingFinished(mPGPSigningKeyRequester); }); // "OpenPGP Encryption Key" requester and label: mPGPEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::OpenPGP, mCryptographyTab); msg = i18n( "

The OpenPGP key you choose here will be used " "to encrypt messages to yourself and for the \"Attach My Public Key\" " "feature in the composer. You can also use GnuPG keys.

" "

You can leave this blank, but KMail will not be able " "to encrypt copies of outgoing messages to you using OpenPGP; " "normal mail functions will not be affected.

" "

You can find out more about keys at https://www.gnupg.org

"); label = new QLabel(i18n("OpenPGP encryption key:"), mCryptographyTab); label->setBuddy(mPGPEncryptionKeyRequester); label->setWhatsThis(msg); mPGPEncryptionKeyRequester->setWhatsThis(msg); formLayout->addRow(label, mPGPEncryptionKeyRequester); // "S/MIME Signature Key" requester and label: mSMIMESigningKeyRequester = new KeySelectionCombo(KeySelectionCombo::SigningKey, GpgME::CMS, mCryptographyTab); mSMIMESigningKeyRequester->setObjectName("SMIME Signing Key Requester"); msg = i18n( "

The S/MIME (X.509) certificate you choose here will be used " "to digitally sign messages.

" "

You can leave this blank, but KMail will not be able " "to digitally sign emails using S/MIME; " "normal mail functions will not be affected.

"); label = new QLabel(i18n("S/MIME signing certificate:"), mCryptographyTab); label->setBuddy(mSMIMESigningKeyRequester); mSMIMESigningKeyRequester->setWhatsThis(msg); label->setWhatsThis(msg); formLayout->addRow(label, mSMIMESigningKeyRequester); connect(mSMIMESigningKeyRequester, &KeySelectionCombo::keyListingFinished, this, [this] { slotKeyListingFinished(mSMIMESigningKeyRequester); }); const QGpgME::Protocol *smimeProtocol = QGpgME::smime(); label->setEnabled(smimeProtocol); mSMIMESigningKeyRequester->setEnabled(smimeProtocol); // "S/MIME Encryption Key" requester and label: mSMIMEEncryptionKeyRequester = new KeySelectionCombo(KeySelectionCombo::EncryptionKey, GpgME::CMS, mCryptographyTab); mSMIMEEncryptionKeyRequester->setObjectName("SMIME Encryption Key Requester"); msg = i18n( "

The S/MIME certificate you choose here will be used " "to encrypt messages to yourself and for the \"Attach My Certificate\" " "feature in the composer.

" "

You can leave this blank, but KMail will not be able " "to encrypt copies of outgoing messages to you using S/MIME; " "normal mail functions will not be affected.

"); label = new QLabel(i18n("S/MIME encryption certificate:"), mCryptographyTab); label->setBuddy(mSMIMEEncryptionKeyRequester); mSMIMEEncryptionKeyRequester->setWhatsThis(msg); connect(mSMIMEEncryptionKeyRequester, &KeySelectionCombo::keyListingFinished, this, [this] { slotKeyListingFinished(mSMIMEEncryptionKeyRequester); }); label->setWhatsThis(msg); formLayout->addRow(label, mSMIMEEncryptionKeyRequester); label->setEnabled(smimeProtocol); mSMIMEEncryptionKeyRequester->setEnabled(smimeProtocol); // "Preferred Crypto Message Format" combobox and label: mPreferredCryptoMessageFormat = new QComboBox(mCryptographyTab); QStringList l; l << Kleo::cryptoMessageFormatToLabel(Kleo::AutoFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::InlineOpenPGPFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::OpenPGPMIMEFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEFormat) << Kleo::cryptoMessageFormatToLabel(Kleo::SMIMEOpaqueFormat); mPreferredCryptoMessageFormat->addItems(l); label = new QLabel(i18nc("preferred format of encrypted messages", "Preferred format:"), mCryptographyTab); label->setBuddy(mPreferredCryptoMessageFormat); formLayout->addRow(label, mPreferredCryptoMessageFormat); mWarnNotEncrypt = new QCheckBox(i18n("Warn when trying to send unencrypted messages")); formLayout->addRow(QString(), mWarnNotEncrypt); // // Tab Widget: Signature // mSignatureConfigurator = new KIdentityManagementWidgets::SignatureConfigurator(mTabWidget); mTabWidget->addTab(mSignatureConfigurator, i18n("Signature")); } IdentityDialog::~IdentityDialog() = default; void IdentityDialog::slotAccepted() { // Validate email addresses const QString email = mEmailEdit->text().trimmed(); if (email.isEmpty()) { KMessageBox::error(this, i18n("You must provide an email for this identity."), i18nc("@title:window", "Empty Email Address")); return; } if (!KEmailAddress::isValidSimpleAddress(email)) { const QString errorMsg(KEmailAddress::simpleEmailAddressErrorMsg()); KMessageBox::error(this, errorMsg, i18n("Invalid Email Address")); return; } const GpgME::Key &pgpSigningKey = mPGPSigningKeyRequester->currentKey(); const GpgME::Key &pgpEncryptionKey = mPGPEncryptionKeyRequester->currentKey(); const GpgME::Key &smimeSigningKey = mSMIMESigningKeyRequester->currentKey(); const GpgME::Key &smimeEncryptionKey = mSMIMEEncryptionKeyRequester->currentKey(); QString msg; bool err = false; if (!keyMatchesEmailAddress(pgpSigningKey, email)) { msg = i18n( "One of the configured OpenPGP signing keys does not contain " "any user ID with the configured email address for this " "identity (%1).\n" "This might result in warning messages on the receiving side " "when trying to verify signatures made with this configuration.", email); err = true; } else if (!keyMatchesEmailAddress(pgpEncryptionKey, email)) { msg = i18n( "One of the configured OpenPGP encryption keys does not contain " "any user ID with the configured email address for this " "identity (%1).", email); err = true; } else if (!keyMatchesEmailAddress(smimeSigningKey, email)) { msg = i18n( "One of the configured S/MIME signing certificates does not contain " "the configured email address for this " "identity (%1).\n" "This might result in warning messages on the receiving side " "when trying to verify signatures made with this configuration.", email); err = true; } else if (!keyMatchesEmailAddress(smimeEncryptionKey, email)) { msg = i18n( "One of the configured S/MIME encryption certificates does not contain " "the configured email address for this " "identity (%1).", email); err = true; } if (err && KMessageBox::warningContinueCancel(this, msg, i18nc("@title:window", "Email Address Not Found in Key/Certificates"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn_email_not_in_certificate")) != KMessageBox::Continue) { return; } if (mSignatureConfigurator->isSignatureEnabled() && mSignatureConfigurator->signatureType() == Signature::FromFile) { QFileInfo file(mSignatureConfigurator->filePath()); if (!file.isReadable()) { KMessageBox::error(this, i18n("The signature file is not valid")); return; } } accept(); } bool IdentityDialog::keyMatchesEmailAddress(const GpgME::Key &key, const QString &email_) { if (key.isNull()) { return true; } const QString email = email_.trimmed().toLower(); const auto uids = key.userIDs(); for (const auto &uid : uids) { QString em = QString::fromUtf8(uid.email() ? uid.email() : uid.id()); if (em.isEmpty()) { continue; } if (em[0] == QLatin1Char('<')) { em = em.mid(1, em.length() - 2); } if (em.toLower() == email) { return true; } } return false; } void IdentityDialog::setIdentity(KIdentityManagementCore::Identity &ident) { setWindowTitle(i18nc("@title:window", "Edit Identity \"%1\"", ident.identityName())); // "General" tab: mNameEdit->setText(ident.fullName()); mOrganizationEdit->setText(ident.organization()); mEmailEdit->setText(ident.primaryEmailAddress()); mDictionaryCombo->setCurrentByDictionaryName(ident.dictionary()); // "Cryptography" tab: mPGPSigningKeyRequester->setDefaultKey(QLatin1String(ident.pgpSigningKey())); mPGPEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.pgpEncryptionKey())); mPGPSameKey->setChecked(ident.pgpSigningKey() == ident.pgpEncryptionKey()); mSMIMESigningKeyRequester->setDefaultKey(QLatin1String(ident.smimeSigningKey())); mSMIMEEncryptionKeyRequester->setDefaultKey(QLatin1String(ident.smimeEncryptionKey())); mPreferredCryptoMessageFormat->setCurrentIndex(format2cb(Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat()))); mWarnNotEncrypt->setChecked(ident.warnNotEncrypt()); // "Signature" tab: mSignatureConfigurator->setImageLocation(ident); mSignatureConfigurator->setSignature(ident.signature()); // set the configured email address as initial query of the key // requesters: const QString name = mNameEdit->text().trimmed(); const QString email = mEmailEdit->text().trimmed(); mPGPEncryptionKeyRequester->setIdentity(name, email); mPGPSigningKeyRequester->setIdentity(name, email); mSMIMEEncryptionKeyRequester->setIdentity(name, email); mSMIMESigningKeyRequester->setIdentity(name, email); } void IdentityDialog::updateIdentity(KIdentityManagementCore::Identity &ident) { // "General" tab: ident.setFullName(mNameEdit->text()); ident.setOrganization(mOrganizationEdit->text()); QString email = mEmailEdit->text().trimmed(); ident.setPrimaryEmailAddress(email); // "Cryptography" tab: ident.setPGPSigningKey(mPGPSigningKeyRequester->currentKey().primaryFingerprint()); ident.setPGPEncryptionKey(mPGPEncryptionKeyRequester->currentKey().primaryFingerprint()); ident.setSMIMESigningKey(mSMIMESigningKeyRequester->currentKey().primaryFingerprint()); ident.setSMIMEEncryptionKey(mSMIMEEncryptionKeyRequester->currentKey().primaryFingerprint()); ident.setPreferredCryptoMessageFormat(QLatin1String(Kleo::cryptoMessageFormatToString(cb2format(mPreferredCryptoMessageFormat->currentIndex())))); ident.setEncryptionOverride(true); ident.setWarnNotEncrypt(mWarnNotEncrypt->isChecked()); ident.setWarnNotEncrypt(mWarnNotEncrypt->isChecked()); // "Advanced" tab: ident.setDictionary(mDictionaryCombo->currentDictionaryName()); // "Signature" tab: ident.setSignature(mSignatureConfigurator->signature()); } void IdentityDialog::slotKeyListingFinished(KeySelectionCombo *combo) { mInitialLoadingFinished << combo; if (mInitialLoadingFinished.count() == 2) { Q_EMIT keyListingFinished(); } } } #include "identitydialog.moc" #include "moc_identitydialog.cpp"