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

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/genrevokecommand.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
0006     SPDX-FileContributor: Intevation GmbH
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-kleopatra.h>
0012 
0013 #include "genrevokecommand.h"
0014 
0015 #include <utils/applicationstate.h>
0016 
0017 #include <Libkleo/Formatting>
0018 #include <Libkleo/GnuPG>
0019 
0020 #include <gpgme++/key.h>
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 
0025 #include <QFile>
0026 #include <QFileDialog>
0027 #include <QProcess>
0028 #include <QTextStream>
0029 
0030 #include "command_p.h"
0031 #include "kleopatra_debug.h"
0032 
0033 using namespace Kleo;
0034 using namespace Kleo::Commands;
0035 using namespace GpgME;
0036 
0037 GenRevokeCommand::GenRevokeCommand(QAbstractItemView *v, KeyListController *c)
0038     : GnuPGProcessCommand(v, c)
0039 {
0040 }
0041 
0042 GenRevokeCommand::GenRevokeCommand(KeyListController *c)
0043     : GnuPGProcessCommand(c)
0044 {
0045 }
0046 
0047 GenRevokeCommand::GenRevokeCommand(const Key &key)
0048     : GnuPGProcessCommand(key)
0049 {
0050 }
0051 
0052 // Fixup the revocation certificate similar to GnuPG
0053 void GenRevokeCommand::postSuccessHook(QWidget *parentWidget)
0054 {
0055     QFile f(mOutputFileName);
0056 
0057     if (!f.open(QIODevice::ReadOnly)) {
0058         // Should never happen because in this case we would not have had a success.
0059         KMessageBox::error(parentWidget, QStringLiteral("Failed to access the created output file."), errorCaption());
0060         return;
0061     }
0062     const QString revCert = QString::fromLocal8Bit(f.readAll());
0063     f.close();
0064 
0065     if (!f.open(QIODevice::WriteOnly)) {
0066         KMessageBox::error(parentWidget, QStringLiteral("Failed to write to the created output file."), errorCaption());
0067         return;
0068     }
0069 
0070     QTextStream s(&f);
0071 
0072     s << i18n("This is a revocation certificate for the OpenPGP key:") << "\n\n" //
0073       << "             " << Formatting::prettyNameAndEMail(d->key()) << "\n" //
0074       << "Fingerprint: " << d->key().primaryFingerprint() << "\n\n"
0075       << i18n(
0076              "A revocation certificate is a kind of \"kill switch\" to publicly\n"
0077              "declare that a key shall not anymore be used.  It is not possible\n"
0078              "to retract such a revocation certificate once it has been published.")
0079       << "\n\n"
0080       << i18n(
0081              "Use it to revoke this key in case of a compromise or loss of\n"
0082              "the secret key.")
0083       << "\n\n"
0084       << i18n(
0085              "To avoid an accidental use of this file, a colon has been inserted\n"
0086              "before the 5 dashes below.  Remove this colon with a text editor\n"
0087              "before importing and publishing this revocation certificate.")
0088       << "\n\n:" << revCert;
0089     s.flush();
0090     qCDebug(KLEOPATRA_LOG) << "revocation certificate stored as:" << mOutputFileName;
0091 
0092     f.close();
0093     KMessageBox::information(d->parentWidgetOrView(),
0094                              i18nc("@info",
0095                                    "Certificate successfully created.<br><br>"
0096                                    "Note:<br>To prevent accidental import of the revocation<br>"
0097                                    "it is required to manually edit the certificate<br>"
0098                                    "before it can be imported."),
0099                              i18nc("@title:window", "Revocation certificate created"));
0100 }
0101 
0102 /* Well not much to do with GnuPGProcessCommand anymore I guess.. */
0103 void GenRevokeCommand::doStart()
0104 {
0105     auto proposedFileName = ApplicationState::lastUsedExportDirectory() + u'/' + QString::fromLatin1(d->key().primaryFingerprint()) + QLatin1StringView{".rev"};
0106     while (mOutputFileName.isEmpty()) {
0107         mOutputFileName = QFileDialog::getSaveFileName(d->parentWidgetOrView(),
0108                                                        i18n("Generate revocation certificate"),
0109                                                        proposedFileName,
0110                                                        QStringLiteral("%1 (*.rev)").arg(i18n("Revocation Certificates ")),
0111                                                        {},
0112                                                        QFileDialog::DontConfirmOverwrite);
0113         if (mOutputFileName.isEmpty()) {
0114             d->finished();
0115             return;
0116         }
0117         if (!mOutputFileName.endsWith(QLatin1StringView(".rev"))) {
0118             mOutputFileName += QLatin1StringView(".rev");
0119         }
0120         const QFileInfo fi{mOutputFileName};
0121         if (fi.exists()) {
0122             auto sel = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
0123                                                        xi18n("The file <filename>%1</filename> already exists. Do you wish to overwrite it?", fi.fileName()),
0124                                                        i18nc("@title:window", "Overwrite File?"),
0125                                                        KStandardGuiItem::overwrite(),
0126                                                        KStandardGuiItem::cancel(),
0127                                                        {},
0128                                                        KMessageBox::Notify | KMessageBox::Dangerous);
0129             if (sel == KMessageBox::ButtonCode::SecondaryAction) {
0130                 proposedFileName = mOutputFileName;
0131                 mOutputFileName.clear();
0132             }
0133         }
0134     }
0135     ApplicationState::setLastUsedExportDirectory(mOutputFileName);
0136 
0137     auto proc = process();
0138     // We do custom io
0139     disconnect(m_procReadyReadStdErrConnection);
0140     proc->setReadChannel(QProcess::StandardOutput);
0141 
0142     GnuPGProcessCommand::doStart();
0143 
0144     connect(proc, &QProcess::readyReadStandardOutput, this, [proc]() {
0145         while (proc->canReadLine()) {
0146             const QString line = QString::fromUtf8(proc->readLine()).trimmed();
0147             // Command-fd is a stable interface, while this is all kind of hacky we
0148             // are on a deadline :-/
0149             if (line == QLatin1StringView("[GNUPG:] GET_BOOL gen_revoke.okay")) {
0150                 proc->write("y\n");
0151             } else if (line == QLatin1StringView("[GNUPG:] GET_LINE ask_revocation_reason.code")) {
0152                 proc->write("0\n");
0153             } else if (line == QLatin1StringView("[GNUPG:] GET_LINE ask_revocation_reason.text")) {
0154                 proc->write("\n");
0155             } else if (line == QLatin1StringView("[GNUPG:] GET_BOOL openfile.overwrite.okay")) {
0156                 // We asked before
0157                 proc->write("y\n");
0158             } else if (line == QLatin1StringView("[GNUPG:] GET_BOOL ask_revocation_reason.okay")) {
0159                 proc->write("y\n");
0160             }
0161         }
0162     });
0163 }
0164 
0165 QStringList GenRevokeCommand::arguments() const
0166 {
0167     const Key key = d->key();
0168 
0169     return {
0170         gpgPath(),
0171         QStringLiteral("--command-fd"),
0172         QStringLiteral("0"),
0173         QStringLiteral("--status-fd"),
0174         QStringLiteral("1"),
0175         QStringLiteral("-o"),
0176         mOutputFileName,
0177         QStringLiteral("--gen-revoke"),
0178         QLatin1StringView(key.primaryFingerprint()),
0179     };
0180 }
0181 
0182 QString GenRevokeCommand::errorCaption() const
0183 {
0184     return i18nc("@title:window", "Error creating revocation certificate");
0185 }
0186 
0187 QString GenRevokeCommand::crashExitMessage(const QStringList &) const
0188 {
0189     // We show a success message so a failure is either the user aborted
0190     // or a bug.
0191     qCDebug(KLEOPATRA_LOG) << "Crash exit of GenRevokeCommand";
0192     return QString();
0193 }
0194 
0195 QString GenRevokeCommand::errorExitMessage(const QStringList &) const
0196 {
0197     // We show a success message so a failure is either the user aborted
0198     // or a bug.
0199     qCDebug(KLEOPATRA_LOG) << "Error exit of GenRevokeCommand";
0200     return QString();
0201 }
0202 
0203 #include "moc_genrevokecommand.cpp"