File indexing completed on 2024-06-23 05:13:40
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 commands/exportsecretkeycommand.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2022 g10 Code GmbH 0006 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include <config-kleopatra.h> 0012 0013 #include "command_p.h" 0014 #include "exportsecretkeycommand.h" 0015 0016 #include "fileoperationspreferences.h" 0017 #include "utils/filedialog.h" 0018 #include <utils/applicationstate.h> 0019 0020 #include <Libkleo/Classify> 0021 #include <Libkleo/Formatting> 0022 0023 #include <KLocalizedString> 0024 #include <KSharedConfig> 0025 0026 #include <QGpgME/ExportJob> 0027 #include <QGpgME/Protocol> 0028 0029 #include <QFileInfo> 0030 #include <QStandardPaths> 0031 0032 #include <gpgme++/context.h> 0033 0034 #include <algorithm> 0035 #include <memory> 0036 #include <vector> 0037 0038 #include <kleopatra_debug.h> 0039 0040 using namespace Kleo; 0041 using namespace Kleo::Commands; 0042 using namespace GpgME; 0043 0044 namespace 0045 { 0046 0047 QString openPGPCertificateFileExtension() 0048 { 0049 return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt()); 0050 } 0051 0052 QString cmsCertificateFileExtension() 0053 { 0054 return outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM, 0055 /*usePGPFileExt=*/false); 0056 } 0057 0058 QString certificateFileExtension(GpgME::Protocol protocol) 0059 { 0060 switch (protocol) { 0061 case GpgME::OpenPGP: 0062 return openPGPCertificateFileExtension(); 0063 case GpgME::CMS: 0064 return cmsCertificateFileExtension(); 0065 default: 0066 qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; 0067 return QStringLiteral("txt"); 0068 } 0069 } 0070 0071 QString proposeFilename(const Key &key) 0072 { 0073 QString filename; 0074 0075 auto name = Formatting::prettyName(key); 0076 if (name.isEmpty()) { 0077 name = Formatting::prettyEMail(key); 0078 } 0079 const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); 0080 /* Not translated so it's better to use in tutorials etc. */ 0081 filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID); 0082 filename.replace(u'/', u'_'); 0083 0084 return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol()); 0085 } 0086 0087 QString secretKeyFileFilters(GpgME::Protocol protocol) 0088 { 0089 switch (protocol) { 0090 case GpgME::OpenPGP: 0091 return i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.asc *.gpg *.pgp)"}; 0092 case GpgME::CMS: 0093 return i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.p12)"}; 0094 default: 0095 qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; 0096 return i18nc("description of filename filter", "All Files") + QLatin1StringView{" (*)"}; 0097 } 0098 } 0099 0100 QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent) 0101 { 0102 auto filename = FileDialog::getSaveFileNameEx(parent, 0103 i18nc("@title:window", "Secret Key Backup"), 0104 QStringLiteral("imp"), 0105 proposedFilename, 0106 secretKeyFileFilters(key.protocol())); 0107 0108 if (!filename.isEmpty()) { 0109 const QFileInfo fi{filename}; 0110 if (fi.suffix().isEmpty()) { 0111 filename += u'.' + certificateFileExtension(key.protocol()); 0112 } 0113 ApplicationState::setLastUsedExportDirectory(filename); 0114 } 0115 0116 return filename; 0117 } 0118 0119 QString errorCaption() 0120 { 0121 return i18nc("@title:window", "Secret Key Backup Error"); 0122 } 0123 0124 } 0125 0126 class ExportSecretKeyCommand::Private : public Command::Private 0127 { 0128 friend class ::ExportSecretKeyCommand; 0129 ExportSecretKeyCommand *q_func() const 0130 { 0131 return static_cast<ExportSecretKeyCommand *>(q); 0132 } 0133 0134 public: 0135 explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr); 0136 ~Private() override; 0137 0138 void start(); 0139 void cancel(); 0140 0141 private: 0142 std::unique_ptr<QGpgME::ExportJob> startExportJob(const Key &key); 0143 void onExportJobResult(const Error &err, const QByteArray &keyData); 0144 void showError(const Error &err); 0145 0146 private: 0147 QString filename; 0148 QPointer<QGpgME::ExportJob> job; 0149 }; 0150 0151 ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() 0152 { 0153 return static_cast<Private *>(d.get()); 0154 } 0155 const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const 0156 { 0157 return static_cast<const Private *>(d.get()); 0158 } 0159 0160 #define d d_func() 0161 #define q q_func() 0162 0163 ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c) 0164 : Command::Private{qq, c} 0165 { 0166 } 0167 0168 ExportSecretKeyCommand::Private::~Private() = default; 0169 0170 void ExportSecretKeyCommand::Private::start() 0171 { 0172 const Key key = this->key(); 0173 0174 if (key.isNull()) { 0175 finished(); 0176 return; 0177 } 0178 0179 filename = requestFilename(key, proposeFilename(key), parentWidgetOrView()); 0180 if (filename.isEmpty()) { 0181 canceled(); 0182 return; 0183 } 0184 0185 auto exportJob = startExportJob(key); 0186 if (!exportJob) { 0187 finished(); 0188 return; 0189 } 0190 job = exportJob.release(); 0191 } 0192 0193 void ExportSecretKeyCommand::Private::cancel() 0194 { 0195 if (job) { 0196 job->slotCancel(); 0197 } 0198 job.clear(); 0199 } 0200 0201 std::unique_ptr<QGpgME::ExportJob> ExportSecretKeyCommand::Private::startExportJob(const Key &key) 0202 { 0203 const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive); 0204 const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); 0205 Q_ASSERT(backend); 0206 std::unique_ptr<QGpgME::ExportJob> exportJob{backend->secretKeyExportJob(armor)}; 0207 Q_ASSERT(exportJob); 0208 0209 if (key.protocol() == GpgME::CMS) { 0210 exportJob->setExportFlags(GpgME::Context::ExportPKCS12); 0211 } 0212 0213 connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) { 0214 onExportJobResult(err, keyData); 0215 }); 0216 connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); 0217 0218 const GpgME::Error err = exportJob->start({QLatin1StringView{key.primaryFingerprint()}}); 0219 if (err) { 0220 showError(err); 0221 return {}; 0222 } 0223 Q_EMIT q->info(i18nc("@info:status", "Backing up secret key...")); 0224 0225 return exportJob; 0226 } 0227 0228 void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) 0229 { 0230 if (err.isCanceled()) { 0231 finished(); 0232 return; 0233 } 0234 0235 if (err) { 0236 showError(err); 0237 finished(); 0238 return; 0239 } 0240 0241 if (keyData.isEmpty()) { 0242 error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."), errorCaption()); 0243 finished(); 0244 return; 0245 } 0246 0247 QFile f{filename}; 0248 if (!f.open(QIODevice::WriteOnly)) { 0249 error(xi18nc("@info", "Cannot open file <filename>%1</filename> for writing.", filename), errorCaption()); 0250 finished(); 0251 return; 0252 } 0253 0254 const auto bytesWritten = f.write(keyData); 0255 if (bytesWritten != keyData.size()) { 0256 error(xi18nc("@info", "Writing key to file <filename>%1</filename> failed.", filename), errorCaption()); 0257 finished(); 0258 return; 0259 } 0260 0261 information(i18nc("@info", "The backup of the secret key was created successfully."), i18nc("@title:window", "Secret Key Backup")); 0262 finished(); 0263 } 0264 0265 void ExportSecretKeyCommand::Private::showError(const Error &err) 0266 { 0267 error(xi18nc("@info", 0268 "<para>An error occurred during the backup of the secret key:</para>" 0269 "<para><message>%1</message></para>", 0270 Formatting::errorAsString(err)), 0271 errorCaption()); 0272 } 0273 0274 ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller) 0275 : Command{view, new Private{this, controller}} 0276 { 0277 } 0278 0279 ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key) 0280 : Command{key, new Private{this}} 0281 { 0282 } 0283 0284 ExportSecretKeyCommand::~ExportSecretKeyCommand() = default; 0285 0286 void ExportSecretKeyCommand::doStart() 0287 { 0288 d->start(); 0289 } 0290 0291 void ExportSecretKeyCommand::doCancel() 0292 { 0293 d->cancel(); 0294 } 0295 0296 #undef d 0297 #undef q 0298 0299 #include "moc_exportsecretkeycommand.cpp"