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

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/createcsrforcardkeycommand.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2020 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 "createcsrforcardkeycommand.h"
0014 
0015 #include "cardcommand_p.h"
0016 
0017 #include "dialogs/createcsrforcardkeydialog.h"
0018 
0019 #include "smartcard/netkeycard.h"
0020 #include "smartcard/openpgpcard.h"
0021 #include "smartcard/pivcard.h"
0022 #include "smartcard/readerstatus.h"
0023 
0024 #include "utils/filedialog.h"
0025 #include "utils/keyparameters.h"
0026 
0027 #include <Libkleo/Formatting>
0028 #include <Libkleo/KeyUsage>
0029 
0030 #include <KLocalizedString>
0031 
0032 #include <KLocalizedString>
0033 #include <QDateTime>
0034 #include <QFile>
0035 #include <QUrl>
0036 
0037 #include <QGpgME/KeyGenerationJob>
0038 #include <QGpgME/Protocol>
0039 
0040 #include <gpgme++/context.h>
0041 #include <gpgme++/engineinfo.h>
0042 #include <gpgme++/keygenerationresult.h>
0043 
0044 #include <gpgme.h>
0045 
0046 #include "kleopatra_debug.h"
0047 
0048 using namespace Kleo;
0049 using namespace Kleo::Commands;
0050 using namespace Kleo::Dialogs;
0051 using namespace Kleo::SmartCard;
0052 using namespace GpgME;
0053 using namespace QGpgME;
0054 
0055 class CreateCSRForCardKeyCommand::Private : public CardCommand::Private
0056 {
0057     friend class ::Kleo::Commands::CreateCSRForCardKeyCommand;
0058     CreateCSRForCardKeyCommand *q_func() const
0059     {
0060         return static_cast<CreateCSRForCardKeyCommand *>(q);
0061     }
0062 
0063 public:
0064     explicit Private(CreateCSRForCardKeyCommand *qq, const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent);
0065     ~Private() override;
0066 
0067 private:
0068     void start();
0069 
0070     void slotDialogAccepted();
0071     void slotDialogRejected();
0072     void slotResult(const KeyGenerationResult &result, const QByteArray &request);
0073 
0074     QUrl saveRequest(const QByteArray &request);
0075 
0076     void ensureDialogCreated();
0077 
0078 private:
0079     std::string appName;
0080     std::string keyRef;
0081     KeyUsage keyUsage;
0082     QPointer<CreateCSRForCardKeyDialog> dialog;
0083 };
0084 
0085 CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func()
0086 {
0087     return static_cast<Private *>(d.get());
0088 }
0089 const CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() const
0090 {
0091     return static_cast<const Private *>(d.get());
0092 }
0093 
0094 #define d d_func()
0095 #define q q_func()
0096 
0097 CreateCSRForCardKeyCommand::Private::Private(CreateCSRForCardKeyCommand *qq,
0098                                              const std::string &keyRef_,
0099                                              const std::string &serialNumber,
0100                                              const std::string &appName_,
0101                                              QWidget *parent)
0102     : CardCommand::Private(qq, serialNumber, parent)
0103     , appName(appName_)
0104     , keyRef(keyRef_)
0105 {
0106 }
0107 
0108 CreateCSRForCardKeyCommand::Private::~Private()
0109 {
0110 }
0111 
0112 namespace
0113 {
0114 KeyUsage getKeyUsage(const KeyPairInfo &keyInfo)
0115 {
0116     // note: gpgsm does not support creating CSRs for authentication certificates
0117     KeyUsage usage;
0118     if (keyInfo.canCertify()) {
0119         usage.setCanCertify(true);
0120     }
0121     if (keyInfo.canSign()) {
0122         usage.setCanSign(true);
0123     }
0124     if (keyInfo.canEncrypt()) {
0125         usage.setCanEncrypt(true);
0126     }
0127     return usage;
0128 }
0129 }
0130 
0131 void CreateCSRForCardKeyCommand::Private::start()
0132 {
0133     if (appName != NetKeyCard::AppName && appName != OpenPGPCard::AppName && appName != PIVCard::AppName) {
0134         qCWarning(KLEOPATRA_LOG) << "CreateCSRForCardKeyCommand does not support card application" << QString::fromStdString(appName);
0135         finished();
0136         return;
0137     }
0138 
0139     const auto card = ReaderStatus::instance()->getCard(serialNumber(), appName);
0140     if (!card) {
0141         error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber())));
0142         finished();
0143         return;
0144     }
0145 
0146     const KeyPairInfo &keyInfo = card->keyInfo(keyRef);
0147     keyUsage = getKeyUsage(keyInfo);
0148 
0149     ensureDialogCreated();
0150 
0151     dialog->setWindowTitle(i18nc("@title:window", "Certificate Details"));
0152     if (!card->cardHolder().isEmpty()) {
0153         dialog->setName(card->cardHolder());
0154     }
0155 
0156     dialog->show();
0157 }
0158 
0159 void CreateCSRForCardKeyCommand::Private::slotDialogAccepted()
0160 {
0161     const Error err = ReaderStatus::switchCardAndApp(serialNumber(), appName);
0162     if (err) {
0163         finished();
0164         return;
0165     }
0166 
0167     const auto backend = smime();
0168     if (!backend) {
0169         finished();
0170         return;
0171     }
0172 
0173     KeyGenerationJob *const job = backend->keyGenerationJob();
0174     if (!job) {
0175         finished();
0176         return;
0177     }
0178 
0179     Job::context(job)->setArmor(true);
0180 
0181     connect(job, &KeyGenerationJob::result, q, [this](const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData) {
0182         slotResult(result, pubKeyData);
0183     });
0184 
0185     KeyParameters keyParameters(KeyParameters::CMS);
0186     keyParameters.setCardKeyRef(QString::fromStdString(keyRef));
0187     keyParameters.setKeyUsage(keyUsage);
0188     keyParameters.setDN(dialog->dn());
0189     keyParameters.setEmail(dialog->email());
0190 
0191     if (const Error err = job->start(keyParameters.toString())) {
0192         error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(err)));
0193         finished();
0194     }
0195 }
0196 
0197 void CreateCSRForCardKeyCommand::Private::slotDialogRejected()
0198 {
0199     canceled();
0200 }
0201 
0202 void CreateCSRForCardKeyCommand::Private::slotResult(const KeyGenerationResult &result, const QByteArray &request)
0203 {
0204     if (result.error().isCanceled()) {
0205         // do nothing
0206     } else if (result.error()) {
0207         error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(result.error())));
0208     } else {
0209         const QUrl url = saveRequest(request);
0210         if (!url.isEmpty()) {
0211             information(xi18nc("@info",
0212                                "<para>Successfully wrote request to <filename>%1</filename>.</para>"
0213                                "<para>You should now send the request to the Certification Authority (CA).</para>",
0214                                url.toLocalFile()),
0215                         i18nc("@title", "Request Saved"));
0216         }
0217     }
0218 
0219     finished();
0220 }
0221 
0222 namespace
0223 {
0224 struct SaveToFileResult {
0225     QUrl url;
0226     QString errorMessage;
0227 };
0228 
0229 SaveToFileResult saveRequestToFile(const QString &filename, const QByteArray &request, QIODevice::OpenMode mode)
0230 {
0231     QFile file(filename);
0232     if (file.open(mode)) {
0233         const auto bytesWritten = file.write(request);
0234         if (bytesWritten < request.size()) {
0235             return {QUrl(), file.errorString()};
0236         }
0237         return {QUrl::fromLocalFile(file.fileName()), QString()};
0238     }
0239     return {QUrl(), file.errorString()};
0240 }
0241 }
0242 
0243 QUrl CreateCSRForCardKeyCommand::Private::saveRequest(const QByteArray &request)
0244 {
0245     const QString proposedFilename = QLatin1StringView("request_%1.p10").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_HHmmss")));
0246 
0247     while (true) {
0248         const QString filePath = FileDialog::getSaveFileNameEx(parentWidgetOrView(),
0249                                                                i18nc("@title", "Save Request"),
0250                                                                QStringLiteral("save_csr"),
0251                                                                proposedFilename,
0252                                                                i18n("PKCS#10 Requests (*.p10)"));
0253         if (filePath.isEmpty()) {
0254             // user canceled the dialog
0255             return QUrl();
0256         }
0257         const auto result = saveRequestToFile(filePath, request, QIODevice::NewOnly);
0258         if (result.url.isEmpty()) {
0259             qCDebug(KLEOPATRA_LOG) << "Writing request to file" << filePath << "failed:" << result.errorMessage;
0260             error(xi18nc("@info", "<para>Saving the request failed.</para><para><message>%1</message></para>", result.errorMessage),
0261                   i18nc("@title", "Error Saving Request"));
0262         } else {
0263             return result.url;
0264         }
0265     }
0266 }
0267 
0268 void CreateCSRForCardKeyCommand::Private::ensureDialogCreated()
0269 {
0270     if (dialog) {
0271         return;
0272     }
0273 
0274     dialog = new CreateCSRForCardKeyDialog;
0275     applyWindowID(dialog);
0276     dialog->setAttribute(Qt::WA_DeleteOnClose);
0277 
0278     connect(dialog, &QDialog::accepted, q, [this]() {
0279         slotDialogAccepted();
0280     });
0281     connect(dialog, &QDialog::rejected, q, [this]() {
0282         slotDialogRejected();
0283     });
0284 }
0285 
0286 CreateCSRForCardKeyCommand::CreateCSRForCardKeyCommand(const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent)
0287     : CardCommand(new Private(this, keyRef, serialNumber, appName, parent))
0288 {
0289 }
0290 
0291 CreateCSRForCardKeyCommand::~CreateCSRForCardKeyCommand()
0292 {
0293 }
0294 
0295 void CreateCSRForCardKeyCommand::doStart()
0296 {
0297     d->start();
0298 }
0299 
0300 void CreateCSRForCardKeyCommand::doCancel()
0301 {
0302 }
0303 
0304 #undef d
0305 #undef q
0306 
0307 #include "moc_createcsrforcardkeycommand.cpp"