diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp index 0538477a3..07fd0abd9 100644 --- a/src/commands/exportcertificatecommand.cpp +++ b/src/commands/exportcertificatecommand.cpp @@ -1,360 +1,360 @@ /* -*- mode: c++; c-basic-offset:4 -*- exportcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "exportcertificatecommand.h" #include "fileoperationspreferences.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; using namespace QGpgME; class ExportCertificateCommand::Private : public Command::Private { friend class ::ExportCertificateCommand; ExportCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportCertificateCommand *qq, KeyListController *c); ~Private() override; void startExportJob(GpgME::Protocol protocol, const std::vector &keys); void cancelJobs(); void exportResult(const GpgME::Error &, const QByteArray &); void showError(const GpgME::Error &error); bool requestFileNames(GpgME::Protocol prot); void finishedIfLastJob(); private: QMap fileNames; uint jobsPending = 0; QMap outFileForSender; QPointer cmsJob; QPointer pgpJob; }; ExportCertificateCommand::Private *ExportCertificateCommand::d_func() { return static_cast(d.get()); } const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c) : Command::Private(qq, c) { } ExportCertificateCommand::Private::~Private() {} ExportCertificateCommand::ExportCertificateCommand(KeyListController *p) : Command(new Private(this, p)) { } ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { } ExportCertificateCommand::ExportCertificateCommand(const Key &key) : Command(key, new Private(this, nullptr)) { } ExportCertificateCommand::~ExportCertificateCommand() {} void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName) { if (!d->jobsPending) { d->fileNames[OpenPGP] = fileName; } } QString ExportCertificateCommand::openPGPFileName() const { return d->fileNames[OpenPGP]; } void ExportCertificateCommand::setX509FileName(const QString &fileName) { if (!d->jobsPending) { d->fileNames[CMS] = fileName; } } QString ExportCertificateCommand::x509FileName() const { return d->fileNames[CMS]; } void ExportCertificateCommand::doStart() { std::vector keys = d->keys(); if (keys.empty()) { return; } const auto firstCms = std::partition(keys.begin(), keys.end(), [](const GpgME::Key &key) { return key.protocol() != GpgME::CMS; }); std::vector openpgp, cms; std::copy(keys.begin(), firstCms, std::back_inserter(openpgp)); std::copy(firstCms, keys.end(), std::back_inserter(cms)); Q_ASSERT(!openpgp.empty() || !cms.empty()); const bool haveBoth = !cms.empty() && !openpgp.empty(); const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!cms.empty() ? CMS : OpenPGP); if (!d->requestFileNames(prot)) { Q_EMIT canceled(); d->finished(); } else { if (!openpgp.empty()) { d->startExportJob(GpgME::OpenPGP, openpgp); } if (!cms.empty()) { d->startExportJob(GpgME::CMS, cms); } } } bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol) { if (protocol == UnknownProtocol) { if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) { return true; } - /* Unkown protocol ask for first PGP Export file name */ + /* Unknown protocol ask for first PGP Export file name */ if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) { return false; } /* And then for CMS */ return requestFileNames(GpgME::CMS); } if (!fileNames[protocol].isEmpty()) { return true; } KConfigGroup config(KSharedConfig::openConfig(), "ExportDialog"); const auto lastDir = config.readEntry("LastDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); QString proposedFileName = lastDir + QLatin1Char('/'); if (keys().size() == 1) { const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); const auto key = keys().front(); auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } /* Not translated so it's better to use in tutorials etc. */ proposedFileName += QStringLiteral("%1_%2_public.%3").arg(name).arg( Formatting::prettyKeyID(key.shortKeyID())).arg( QString::fromLatin1(outputFileExtension(protocol == OpenPGP ? Class::OpenPGP | Class::Ascii | Class::Certificate : Class::CMS | Class::Ascii | Class::Certificate, usePGPFileExt))); } if (protocol == GpgME::CMS) { if (!fileNames[GpgME::OpenPGP].isEmpty()) { /* If the user has already selected a PGP file name then use that as basis * for a proposal for the S/MIME file. */ proposedFileName = fileNames[GpgME::OpenPGP]; proposedFileName.replace(QRegExp(QStringLiteral(".asc$")), QStringLiteral(".pem")); proposedFileName.replace(QRegExp(QStringLiteral(".gpg$")), QStringLiteral(".der")); proposedFileName.replace(QRegExp(QStringLiteral(".pgp$")), QStringLiteral(".der")); } } if (proposedFileName.isEmpty()) { proposedFileName = lastDir; proposedFileName += i18nc("A generic filename for exported certificates", "certificates"); proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem"); } auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(), i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)), QStringLiteral("imp"), proposedFileName, protocol == GpgME::OpenPGP ? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)") : i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)")); if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) { KMessageBox::error(parentWidgetOrView(), i18n("You have to select different filenames for different protocols."), i18n("Export Error")); return false; } const QFileInfo fi(fname); if (fi.suffix().isEmpty()) { fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem"); } fileNames[protocol] = fname; config.writeEntry("LastDirectory", fi.absolutePath()); return !fname.isEmpty(); } void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector &keys) { Q_ASSERT(protocol != GpgME::UnknownProtocol); const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); const QString fileName = fileNames[protocol]; const bool binary = protocol == GpgME::OpenPGP ? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive) : fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive); std::unique_ptr job(backend->publicKeyExportJob(!binary)); Q_ASSERT(job.get()); connect(job.get(), SIGNAL(result(GpgME::Error,QByteArray)), q, SLOT(exportResult(GpgME::Error,QByteArray))); connect(job.get(), &Job::progress, q, &Command::progress); QStringList fingerprints; fingerprints.reserve(keys.size()); for (const Key &i : keys) { fingerprints << QLatin1String(i.primaryFingerprint()); } const GpgME::Error err = job->start(fingerprints); if (err) { showError(err); finished(); return; } Q_EMIT q->info(i18n("Exporting certificates...")); ++jobsPending; const QPointer exportJob(job.release()); outFileForSender[exportJob.data()] = fileName; (protocol == CMS ? cmsJob : pgpJob) = exportJob; } void ExportCertificateCommand::Private::showError(const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n("

An error occurred while trying to export " "the certificate:

" "

%1

", QString::fromLocal8Bit(err.asString())); error(msg, i18n("Certificate Export Failed")); } void ExportCertificateCommand::doCancel() { d->cancelJobs(); } void ExportCertificateCommand::Private::finishedIfLastJob() { if (jobsPending <= 0) { finished(); } } static bool write_complete(QIODevice &iod, const QByteArray &data) { qint64 total = 0; qint64 toWrite = data.size(); while (total < toWrite) { const qint64 written = iod.write(data.data() + total, toWrite); if (written < 0) { return false; } total += written; toWrite -= written; } return true; } void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data) { Q_ASSERT(jobsPending > 0); --jobsPending; Q_ASSERT(outFileForSender.contains(q->sender())); const QString outFile = outFileForSender[q->sender()]; if (err) { showError(err); finishedIfLastJob(); return; } QSaveFile savefile(outFile); //TODO: use KIO const QString writeErrorMsg = i18n("Could not write to file %1.", outFile); const QString errorCaption = i18n("Certificate Export Failed"); if (!savefile.open(QIODevice::WriteOnly)) { error(writeErrorMsg, errorCaption); finishedIfLastJob(); return; } if (!write_complete(savefile, data) || !savefile.commit()) { error(writeErrorMsg, errorCaption); } finishedIfLastJob(); } void ExportCertificateCommand::Private::cancelJobs() { if (cmsJob) { cmsJob->slotCancel(); } if (pgpJob) { pgpJob->slotCancel(); } } #undef d #undef q #include "moc_exportcertificatecommand.cpp" diff --git a/src/kcfg/settings.kcfg b/src/kcfg/settings.kcfg index ad175ccf6..f2a83a0dd 100644 --- a/src/kcfg/settings.kcfg +++ b/src/kcfg/settings.kcfg @@ -1,170 +1,170 @@ This text will be used as placeholder text for the common name (CN) field of S/MIME certificates. If true, then the common name (CN) field of S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true This text will be used as placeholder text for the email address field of OpenPGP and S/MIME certificates. If true, then the email address field of OpenPGP and S/MIME certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true This text will be used as placeholder text for the name field of OpenPGP certificates. If true, then the name field of OpenPGP certificates will be prefilled with information gathered from the system, e.g., from the email settings of the desktop or, on Windows, from the Active Directory. true Specifies the default validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid by default, or, in other words, after how many days the key will expire. Set this to 0 for unlimited validity. If this setting is not set or if it is set to a negative value, then new OpenPGP keys will be valid for two years (possibly clamped to the allowed minimum or maximum validity period) by default. -1 Specifies the minimum validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid at least, or, in other words, after how many days the new key will expire at the earliest. 0 Specifies the maximum validity period of new OpenPGP keys in days. This setting specifies how many days a new OpenPGP key is valid at most, or, in other words, after how many days the new key will expire at the latest. If this setting is not set or if it is set to a negative value, then unlimited validity is allowed. -1 If true, hides the advanced settings button in the new certificate wizard. false sha256sum Enables support for S/MIME (CMS). If false, then Kleopatra's main UI will not offer any functionality related to S/MIME (CMS). true Allows the creation of S/MIME certificate signing requests. If false, then Kleopatra will not offer the creation of S/MIME certificate signing requests. true Allows signing of text or files with S/MIME certificates. If false, then Kleopatra will not offer functionality for creating signatures with S/MIME certificates. true true true true true true true Specifies the display order of the DN attributes of X.509 certificates. Enable usage of groups of keys. Enable usage of groups of keys to create lists of recipients. true If enabled, then Kleopatra will automatically try to retrieve the keys that were used to certify the user ids of newly imported OpenPGP keys. This is useful in combination with trusted introducers. false This is a list of URL schemes that shall be blocked by the application. This can be used to prevent the application from opening external applications for certain URLs. Searches for the certificates belonging the smartcard keys on the configured keyserver. Searches on keyservers regardless of the protocol for the smartcards key, regardless of the keyserver protocol. Default behavior is to only do this for LDAP keyservers. false Automatically load S/MIME certificates from PKCS#15 (CardOS) smartcards - If true, then Kleopatra will call gpgsm --learn if a PKCS#15 Smartcard is inserted with unkown certificates. This can take a while and blocks the smartcard while the command is running. + If true, then Kleopatra will call gpgsm --learn if a PKCS#15 Smartcard is inserted with unknown certificates. This can take a while and blocks the smartcard while the command is running. true false