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"