File indexing completed on 2024-06-02 05:24:13
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 commands/exportsecretsubkeycommand.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 "exportsecretsubkeycommand.h" 0015 0016 #include "fileoperationspreferences.h" 0017 #include <utils/applicationstate.h> 0018 #include <utils/filedialog.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 <algorithm> 0033 #include <memory> 0034 #include <vector> 0035 0036 using namespace Kleo; 0037 using namespace GpgME; 0038 0039 namespace 0040 { 0041 0042 QString openPGPCertificateFileExtension() 0043 { 0044 return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt()); 0045 } 0046 0047 QString proposeFilename(const std::vector<Subkey> &subkeys) 0048 { 0049 QString filename; 0050 0051 if (subkeys.size() == 1) { 0052 const auto subkey = subkeys.front(); 0053 const auto key = subkey.parent(); 0054 auto name = Formatting::prettyName(key); 0055 if (name.isEmpty()) { 0056 name = Formatting::prettyEMail(key); 0057 } 0058 const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); 0059 const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData()); 0060 const auto usage = Formatting::usageString(subkey).replace(QLatin1StringView{", "}, QLatin1String{"_"}); 0061 /* Not translated so it's better to use in tutorials etc. */ 0062 filename = QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg(name, shortKeyID, shortSubkeyID, usage); 0063 } else { 0064 filename = i18nc("Generic filename for exported subkeys", "subkeys"); 0065 } 0066 filename.replace(u'/', u'_'); 0067 0068 return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension(); 0069 } 0070 0071 QString requestFilename(const std::vector<Subkey> &subkeys, const QString &proposedFilename, QWidget *parent) 0072 { 0073 auto filename = FileDialog::getSaveFileNameEx(parent, 0074 i18ncp("@title:window", "Export Subkey", "Export Subkeys", subkeys.size()), 0075 QStringLiteral("imp"), 0076 proposedFilename, 0077 i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.asc *.gpg *.pgp)"}); 0078 0079 if (!filename.isEmpty()) { 0080 const QFileInfo fi{filename}; 0081 if (fi.suffix().isEmpty()) { 0082 filename += u'.' + openPGPCertificateFileExtension(); 0083 } 0084 ApplicationState::setLastUsedExportDirectory(filename); 0085 } 0086 0087 return filename; 0088 } 0089 0090 template<typename SubkeyContainer> 0091 QStringList getSubkeyFingerprints(const SubkeyContainer &subkeys) 0092 { 0093 QStringList fingerprints; 0094 0095 fingerprints.reserve(subkeys.size()); 0096 std::transform(std::begin(subkeys), std::end(subkeys), std::back_inserter(fingerprints), [](const auto &subkey) { 0097 return QLatin1StringView{subkey.fingerprint()} + u'!'; 0098 }); 0099 0100 return fingerprints; 0101 } 0102 } 0103 0104 class ExportSecretSubkeyCommand::Private : public Command::Private 0105 { 0106 friend class ::ExportSecretSubkeyCommand; 0107 ExportSecretSubkeyCommand *q_func() const 0108 { 0109 return static_cast<ExportSecretSubkeyCommand *>(q); 0110 } 0111 0112 public: 0113 explicit Private(ExportSecretSubkeyCommand *qq); 0114 ~Private() override; 0115 0116 void start(); 0117 void cancel(); 0118 0119 private: 0120 std::unique_ptr<QGpgME::ExportJob> startExportJob(const std::vector<Subkey> &subkeys); 0121 void onExportJobResult(const Error &err, const QByteArray &keyData); 0122 void showError(const Error &err); 0123 0124 private: 0125 std::vector<Subkey> subkeys; 0126 QString filename; 0127 QPointer<QGpgME::ExportJob> job; 0128 }; 0129 0130 ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() 0131 { 0132 return static_cast<Private *>(d.get()); 0133 } 0134 const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const 0135 { 0136 return static_cast<const Private *>(d.get()); 0137 } 0138 0139 #define d d_func() 0140 #define q q_func() 0141 0142 ExportSecretSubkeyCommand::Private::Private(ExportSecretSubkeyCommand *qq) 0143 : Command::Private{qq} 0144 { 0145 } 0146 0147 ExportSecretSubkeyCommand::Private::~Private() = default; 0148 0149 void ExportSecretSubkeyCommand::Private::start() 0150 { 0151 if (subkeys.empty()) { 0152 finished(); 0153 return; 0154 } 0155 0156 filename = requestFilename(subkeys, proposeFilename(subkeys), parentWidgetOrView()); 0157 if (filename.isEmpty()) { 0158 canceled(); 0159 return; 0160 } 0161 0162 auto exportJob = startExportJob(subkeys); 0163 if (!exportJob) { 0164 finished(); 0165 return; 0166 } 0167 job = exportJob.release(); 0168 } 0169 0170 void ExportSecretSubkeyCommand::Private::cancel() 0171 { 0172 if (job) { 0173 job->slotCancel(); 0174 } 0175 job.clear(); 0176 } 0177 0178 std::unique_ptr<QGpgME::ExportJob> ExportSecretSubkeyCommand::Private::startExportJob(const std::vector<Subkey> &subkeys) 0179 { 0180 const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive); 0181 std::unique_ptr<QGpgME::ExportJob> exportJob{QGpgME::openpgp()->secretSubkeyExportJob(armor)}; 0182 Q_ASSERT(exportJob); 0183 0184 connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) { 0185 onExportJobResult(err, keyData); 0186 }); 0187 connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); 0188 0189 const GpgME::Error err = exportJob->start(getSubkeyFingerprints(subkeys)); 0190 if (err) { 0191 showError(err); 0192 return {}; 0193 } 0194 Q_EMIT q->info(i18nc("@info:status", "Exporting subkeys...")); 0195 0196 return exportJob; 0197 } 0198 0199 void ExportSecretSubkeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) 0200 { 0201 if (err) { 0202 showError(err); 0203 finished(); 0204 return; 0205 } 0206 0207 if (err.isCanceled()) { 0208 finished(); 0209 return; 0210 } 0211 0212 if (keyData.isEmpty()) { 0213 error(i18nc("@info", "The result of the export is empty."), i18nc("@title:window", "Export Failed")); 0214 finished(); 0215 return; 0216 } 0217 0218 QFile f{filename}; 0219 if (!f.open(QIODevice::WriteOnly)) { 0220 error(xi18nc("@info", "Cannot open file <filename>%1</filename> for writing.", filename), i18nc("@title:window", "Export Failed")); 0221 finished(); 0222 return; 0223 } 0224 0225 const auto bytesWritten = f.write(keyData); 0226 if (bytesWritten != keyData.size()) { 0227 error(xi18ncp("@info", 0228 "Writing subkey to file <filename>%2</filename> failed.", 0229 "Writing subkeys to file <filename>%2</filename> failed.", 0230 subkeys.size(), 0231 filename), 0232 i18nc("@title:window", "Export Failed")); 0233 finished(); 0234 return; 0235 } 0236 0237 information(i18ncp("@info", "The subkey was exported successfully.", "%1 subkeys were exported successfully.", subkeys.size()), 0238 i18nc("@title:window", "Secret Key Backup")); 0239 finished(); 0240 } 0241 0242 void ExportSecretSubkeyCommand::Private::showError(const Error &err) 0243 { 0244 error(xi18nc("@info", 0245 "<para>An error occurred during the export:</para>" 0246 "<para><message>%1</message></para>", 0247 Formatting::errorAsString(err)), 0248 i18nc("@title:window", "Export Failed")); 0249 } 0250 0251 ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector<GpgME::Subkey> &subkeys) 0252 : Command{new Private{this}} 0253 { 0254 d->subkeys = subkeys; 0255 } 0256 0257 ExportSecretSubkeyCommand::~ExportSecretSubkeyCommand() = default; 0258 0259 void ExportSecretSubkeyCommand::doStart() 0260 { 0261 d->start(); 0262 } 0263 0264 void ExportSecretSubkeyCommand::doCancel() 0265 { 0266 d->cancel(); 0267 } 0268 0269 #undef d 0270 #undef q 0271 0272 #include "moc_exportsecretsubkeycommand.cpp"