File indexing completed on 2024-06-23 05:13:43

0001 /*  commands/importperkeycommand.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
0005     SPDX-FileContributor: Intevation GmbH
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "importpaperkeycommand.h"
0013 
0014 #include <Libkleo/Formatting>
0015 #include <Libkleo/GnuPG>
0016 
0017 #include <QGpgME/ExportJob>
0018 #include <QGpgME/ImportJob>
0019 #include <QGpgME/Protocol>
0020 #include <gpgme++/importresult.h>
0021 #include <gpgme++/key.h>
0022 
0023 #include <Libkleo/KeyCache>
0024 
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 
0028 #include <QFileDialog>
0029 #include <QTextStream>
0030 
0031 #include "command_p.h"
0032 #include "kleopatra_debug.h"
0033 
0034 using namespace Kleo;
0035 using namespace Kleo::Commands;
0036 using namespace GpgME;
0037 
0038 ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k)
0039     : GnuPGProcessCommand(k)
0040 {
0041 }
0042 
0043 QStringList ImportPaperKeyCommand::arguments() const
0044 {
0045     const Key key = d->key();
0046     return {
0047         paperKeyInstallPath(),
0048         QStringLiteral("--pubring"),
0049         mTmpDir.path() + QStringLiteral("/pubkey.gpg"),
0050         QStringLiteral("--secrets"),
0051         mTmpDir.path() + QStringLiteral("/secrets.txt"),
0052         QStringLiteral("--output"),
0053         mTmpDir.path() + QStringLiteral("/seckey.gpg"),
0054     };
0055 }
0056 
0057 void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data)
0058 {
0059     if (err) {
0060         d->error(Formatting::errorAsString(err), errorCaption());
0061         d->finished();
0062         return;
0063     }
0064     if (!mTmpDir.isValid()) {
0065         // Should not happen so no i18n
0066         d->error(QStringLiteral("Failed to get temporary directory"), errorCaption());
0067         qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir";
0068         d->finished();
0069         return;
0070     }
0071     const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg");
0072     QFile f(fileName);
0073     if (!f.open(QIODevice::WriteOnly)) {
0074         d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
0075         qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
0076         d->finished();
0077         return;
0078     }
0079     f.write(data);
0080     f.close();
0081 
0082     // Copy and sanitize input a bit
0083     QFile input(mFileName);
0084 
0085     if (!input.open(QIODevice::ReadOnly)) {
0086         d->error(xi18n("Cannot open <filename>%1</filename> for reading.", mFileName), errorCaption());
0087         d->finished();
0088         return;
0089     }
0090     const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt");
0091     QFile out(outName);
0092     if (!out.open(QIODevice::WriteOnly)) {
0093         // Should not happen
0094         d->error(QStringLiteral("Failed to create temporary file"), errorCaption());
0095         qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing";
0096         d->finished();
0097         return;
0098     }
0099 
0100     QTextStream in(&input);
0101     while (!in.atEnd()) {
0102         // Paperkey is picky, tabs may not be part. Neither may be empty lines.
0103         const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral("  ")) + QLatin1Char('\n');
0104         out.write(line.toUtf8());
0105     }
0106     input.close();
0107     out.close();
0108 
0109     GnuPGProcessCommand::doStart();
0110 }
0111 
0112 void ImportPaperKeyCommand::postSuccessHook(QWidget *)
0113 {
0114     qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully.";
0115 
0116     QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg"));
0117     if (!secKey.open(QIODevice::ReadOnly)) {
0118         d->error(QStringLiteral("Failed to open temporary secret"), errorCaption());
0119         qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file";
0120         Q_EMIT finished();
0121         return;
0122     }
0123     auto data = secKey.readAll();
0124     secKey.close();
0125 
0126     auto importjob = QGpgME::openpgp()->importJob();
0127     auto result = importjob->exec(data);
0128     delete importjob;
0129     if (result.error()) {
0130         d->error(Formatting::errorAsString(result.error()), errorCaption());
0131         Q_EMIT finished();
0132         return;
0133     }
0134     if (!result.numSecretKeysImported() || (result.numSecretKeysUnchanged() == result.numSecretKeysImported())) {
0135         d->error(i18n("Failed to restore any secret keys."), errorCaption());
0136         Q_EMIT finished();
0137         return;
0138     }
0139 
0140     // Refresh the key after success
0141     KeyCache::mutableInstance()->reload(OpenPGP);
0142     Q_EMIT finished();
0143     d->information(xi18nc("@info", "Successfully restored the secret key parts from <filename>%1</filename>", mFileName));
0144     return;
0145 }
0146 
0147 void ImportPaperKeyCommand::doStart()
0148 {
0149     if (paperKeyInstallPath().isNull()) {
0150         KMessageBox::error(d->parentWidgetOrView(),
0151                            xi18nc("@info",
0152                                   "<para><application>Kleopatra</application> uses "
0153                                   "<application>PaperKey</application> to import your "
0154                                   "text backup.</para>"
0155                                   "<para>Please make sure it is installed.</para>"),
0156                            i18nc("@title", "Failed to find PaperKey executable."));
0157         return;
0158     }
0159 
0160     mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(),
0161                                              i18n("Select input file"),
0162                                              QString(),
0163                                              QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup"))
0164 #ifdef Q_OS_WIN
0165                                              /* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in
0166                                               * my (aheinecke) Windows 10 environment when invoked here.
0167                                               * In other places it works, with the same arguments as in other places (e.g. import)
0168                                               * it works. But not here. Maybe it's our (gpg4win) build? But why did it only
0169                                               * crash here?
0170                                               *
0171                                               * It does not crash immediately, the program flow continues for a while before it
0172                                               * crashes so this is hard to debug.
0173                                               *
0174                                               * There are some reports about this
0175                                               * QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they
0176                                               * describe them differently also not really reproducible.
0177                                               * Anyway this works for now and for such an exotic feature its good enough for now.
0178                                               */
0179                                              ,
0180                                              nullptr,
0181                                              QFileDialog::DontUseNativeDialog
0182 #endif
0183     );
0184     if (mFileName.isEmpty()) {
0185         d->finished();
0186         return;
0187     }
0188 
0189     auto exportJob = QGpgME::openpgp()->publicKeyExportJob();
0190     connect(exportJob, &QGpgME::ExportJob::result, this, &ImportPaperKeyCommand::exportResult);
0191     exportJob->start(QStringList() << QLatin1StringView(d->key().primaryFingerprint()));
0192 }
0193 
0194 QString ImportPaperKeyCommand::errorCaption() const
0195 {
0196     return i18nc("@title:window", "Error importing secret key");
0197 }
0198 
0199 QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const
0200 {
0201     return xi18nc("@info",
0202                   "<para>The GPG process that tried to restore the secret key "
0203                   "ended prematurely because of an unexpected error.</para>"
0204                   "<para>Please check the output of <icode>%1</icode> for details.</para>",
0205                   args.join(QLatin1Char(' ')));
0206 }
0207 
0208 QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const
0209 {
0210     return xi18nc("@info",
0211                   "<para>An error occurred while trying to restore the secret key.</para> "
0212                   "<para>The output from <command>%1</command> was:</para>"
0213                   "<para><message>%2</message></para>",
0214                   args[0],
0215                   errorString());
0216 }
0217 
0218 QString ImportPaperKeyCommand::successMessage(const QStringList &) const
0219 {
0220     return QString();
0221 }
0222 
0223 #include "moc_importpaperkeycommand.cpp"