diff --git a/src/commands/createcsrforcardkeycommand.cpp b/src/commands/createcsrforcardkeycommand.cpp index 2659eafd0..ce9f249f8 100644 --- a/src/commands/createcsrforcardkeycommand.cpp +++ b/src/commands/createcsrforcardkeycommand.cpp @@ -1,295 +1,295 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/createcsrforcardkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "createcsrforcardkeycommand.h" #include "cardcommand_p.h" #include "dialogs/createcsrforcardkeydialog.h" #include "smartcard/netkeycard.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "utils/filedialog.h" #include "utils/keyparameters.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace Kleo::SmartCard; using namespace GpgME; using namespace QGpgME; class CreateCSRForCardKeyCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::CreateCSRForCardKeyCommand; CreateCSRForCardKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(CreateCSRForCardKeyCommand *qq, const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent); ~Private() override; private: void start(); void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const KeyGenerationResult &result, const QByteArray &request); QUrl saveRequest(const QByteArray &request); void ensureDialogCreated(); private: std::string appName; std::string keyRef; QStringList keyUsages; QPointer dialog; }; CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() { return static_cast(d.get()); } const CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() CreateCSRForCardKeyCommand::Private::Private(CreateCSRForCardKeyCommand *qq, const std::string &keyRef_, const std::string &serialNumber, const std::string &appName_, QWidget *parent) : CardCommand::Private(qq, serialNumber, parent) , appName(appName_) , keyRef(keyRef_) { } CreateCSRForCardKeyCommand::Private::~Private() { } namespace { QStringList getKeyUsages(const KeyPairInfo &keyInfo) { // note: gpgsm does not support creating CSRs for authentication certificates QStringList usages; if (keyInfo.canCertify()) { usages.push_back(QStringLiteral("cert")); } if (keyInfo.canSign()) { usages.push_back(QStringLiteral("sign")); } if (keyInfo.canEncrypt()) { usages.push_back(QStringLiteral("encrypt")); } return usages; } } void CreateCSRForCardKeyCommand::Private::start() { if (appName != NetKeyCard::AppName && appName != OpenPGPCard::AppName && appName != PIVCard::AppName) { qCWarning(KLEOPATRA_LOG) << "CreateCSRForCardKeyCommand does not support card application" << QString::fromStdString(appName); finished(); return; } const auto card = ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } const KeyPairInfo &keyInfo = card->keyInfo(keyRef); keyUsages = getKeyUsages(keyInfo); ensureDialogCreated(); dialog->setWindowTitle(i18n("Certificate Details")); if (!card->cardHolder().isEmpty()) { dialog->setName(card->cardHolder()); } dialog->show(); } void CreateCSRForCardKeyCommand::Private::slotDialogAccepted() { const Error err = ReaderStatus::switchCardAndApp(serialNumber(), appName); if (err) { finished(); return; } const auto backend = smime(); if (!backend) { finished(); return; } KeyGenerationJob *const job = backend->keyGenerationJob(); if (!job) { finished(); return; } Job::context(job)->setArmor(true); connect(job, &KeyGenerationJob::result, q, [this](const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData) { slotResult(result, pubKeyData); }); KeyParameters keyParameters(KeyParameters::CMS); - keyParameters.setKeyType(QString::fromStdString(keyRef)); + keyParameters.setCardKeyRef(QString::fromStdString(keyRef)); keyParameters.setKeyUsages(keyUsages); keyParameters.setDN(dialog->dn()); keyParameters.setEmail(dialog->email()); if (const Error err = job->start(keyParameters.toString())) { error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", QString::fromUtf8(err.asString()))); finished(); } } void CreateCSRForCardKeyCommand::Private::slotDialogRejected() { canceled(); } void CreateCSRForCardKeyCommand::Private::slotResult(const KeyGenerationResult &result, const QByteArray &request) { if (result.error().isCanceled()) { // do nothing } else if (result.error()) { error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", QString::fromUtf8(result.error().asString()))); } else { const QUrl url = saveRequest(request); if (!url.isEmpty()) { information(xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", url.toLocalFile()), i18nc("@title", "Request Saved")); } } finished(); } namespace { struct SaveToFileResult { QUrl url; QString errorMessage; }; SaveToFileResult saveRequestToFile(const QString &filename, const QByteArray &request, QIODevice::OpenMode mode) { QFile file(filename); if (file.open(mode)) { const auto bytesWritten = file.write(request); if (bytesWritten < request.size()) { return { QUrl(), file.errorString() }; } return { QUrl::fromLocalFile(file.fileName()), QString() }; } return { QUrl(), file.errorString() }; } } QUrl CreateCSRForCardKeyCommand::Private::saveRequest(const QByteArray &request) { const QString proposedFilename = QLatin1String("request_%1.p10").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_HHmmss"))); while (true) { const QString filePath = FileDialog::getSaveFileNameEx( parentWidgetOrView(), i18nc("@title", "Save Request"), QStringLiteral("save_csr"), proposedFilename, i18n("PKCS#10 Requests (*.p10)")); if (filePath.isEmpty()) { // user canceled the dialog return QUrl(); } const auto result = saveRequestToFile(filePath, request, QIODevice::NewOnly); if (result.url.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Writing request to file" << filePath << "failed:" << result.errorMessage; error(xi18nc("@info", "Saving the request failed.%1", result.errorMessage), i18nc("@title", "Error Saving Request")); } else { return result.url; } } } void CreateCSRForCardKeyCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new CreateCSRForCardKeyDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); }); connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); }); } CreateCSRForCardKeyCommand::CreateCSRForCardKeyCommand(const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent) : CardCommand(new Private(this, keyRef, serialNumber, appName, parent)) { } CreateCSRForCardKeyCommand::~CreateCSRForCardKeyCommand() { } void CreateCSRForCardKeyCommand::doStart() { d->start(); } void CreateCSRForCardKeyCommand::doCancel() { } #undef d #undef q #include "moc_createcsrforcardkeycommand.cpp" diff --git a/src/utils/keyparameters.cpp b/src/utils/keyparameters.cpp index 022420737..5894ee5eb 100644 --- a/src/utils/keyparameters.cpp +++ b/src/utils/keyparameters.cpp @@ -1,198 +1,224 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/keyparameters.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyparameters.h" #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; namespace { QString encodeDomainName(const QString &domain) { const QByteArray encodedDomain = QUrl::toAce(domain); return encodedDomain.isEmpty() ? domain : QString::fromLatin1(encodedDomain); } QString encodeEmail(const QString &email) { const int at = email.lastIndexOf(QLatin1Char('@')); if (at < 0) { return email; } return email.left(at + 1) + encodeDomainName(email.mid(at + 1)); } } class KeyParameters::Private { friend class ::Kleo::KeyParameters; Protocol protocol; - QString keyType; + + Subkey::PubkeyAlgo keyType = Subkey::AlgoUnknown; + QString cardKeyRef; + + Subkey::PubkeyAlgo subkeyType = Subkey::AlgoUnknown; + QMap parameters; public: explicit Private(Protocol proto) : protocol(proto) { } void setValue(const QString &key, const QString &value) { parameters[key] = QStringList() << value; } void addValue(const QString &key, const QString &value) { parameters[key].push_back(value); } }; KeyParameters::KeyParameters(Protocol protocol) : d{new Private{protocol}} { } KeyParameters::~KeyParameters() = default; KeyParameters::KeyParameters(const KeyParameters &other) : d{new Private{*other.d}} { } KeyParameters &KeyParameters::operator=(const KeyParameters &other) { *d = *other.d; return *this; } KeyParameters::KeyParameters(KeyParameters &&other) = default; KeyParameters &KeyParameters::operator=(KeyParameters &&other) = default; void KeyParameters::setKeyType(Subkey::PubkeyAlgo type) { - d->keyType = QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(type)); + d->keyType = type; +} + +GpgME::Subkey::PubkeyAlgo KeyParameters::keyType() const +{ + return d->keyType; +} + +void KeyParameters::setCardKeyRef(const QString &cardKeyRef) +{ + d->cardKeyRef = cardKeyRef; } -void KeyParameters::setKeyType(const QString &cardKeyRef) +QString KeyParameters::cardKeyRef() const { - d->keyType = QLatin1String("card:") + cardKeyRef; + return d->cardKeyRef; } void KeyParameters::setKeyLength(unsigned int length) { d->setValue(QStringLiteral("Key-Length"), QString::number(length)); } void KeyParameters::setKeyCurve(const QString &curve) { d->setValue(QStringLiteral("Key-Curve"), curve); } void KeyParameters::setKeyUsages(const QStringList &usages) { d->setValue(QStringLiteral("Key-Usage"), usages.join(QLatin1Char(' '))); } void KeyParameters::setSubkeyType(Subkey::PubkeyAlgo type) { - d->setValue(QStringLiteral("Subkey-Type"), QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(type))); + d->subkeyType = type; +} + +Subkey::PubkeyAlgo KeyParameters::subkeyType() const +{ + return d->subkeyType; } void KeyParameters::setSubkeyLength(unsigned int length) { d->setValue(QStringLiteral("Subkey-Length"), QString::number(length)); } void KeyParameters::setSubkeyCurve(const QString &curve) { d->setValue(QStringLiteral("Subkey-Curve"), curve); } void KeyParameters::setSubkeyUsages(const QStringList &usages) { d->setValue(QStringLiteral("Subkey-Usage"), usages.join(QLatin1Char(' '))); } void KeyParameters::setExpirationDate(const QDate &date) { d->setValue(QStringLiteral("Expire-Date"), date.toString(Qt::ISODate)); } void KeyParameters::setName(const QString &name) { d->setValue(QStringLiteral("Name-Real"), name); } void KeyParameters::setDN(const QString &dn) { d->setValue(QStringLiteral("Name-DN"), dn); } void KeyParameters::setEmail(const QString &email) { d->setValue(QStringLiteral("Name-Email"), (d->protocol == CMS) ? encodeEmail(email) : email); } void KeyParameters::addEmail(const QString& email) { d->addValue(QStringLiteral("Name-Email"), (d->protocol == CMS) ? encodeEmail(email) : email); } void KeyParameters::addDomainName(const QString& domain) { d->addValue(QStringLiteral("Name-DNS"), encodeDomainName(domain)); } void KeyParameters::addURI(const QString& uri) { d->addValue(QStringLiteral("Name-URI"), uri); } QString KeyParameters::toString() const { QStringList keyParameters; keyParameters.push_back(QLatin1String("")); if (d->protocol == OpenPGP) { // for backward compatibility with GnuPG 2.0 and earlier keyParameters.push_back(QStringLiteral("%ask-passphrase")); } // add Key-Type as first parameter - if (!d->keyType.isEmpty()) { - keyParameters.push_back(QLatin1String("Key-Type:") + d->keyType); + if (!d->cardKeyRef.isEmpty()) { + keyParameters.push_back(QLatin1String{"Key-Type:card:"} + d->cardKeyRef); + } else if (d->keyType != Subkey::AlgoUnknown) { + keyParameters.push_back(QLatin1String{"Key-Type:"} + QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(d->keyType))); } else { qCWarning(KLEOPATRA_LOG) << "KeyParameters::toString(): Key type is unset/empty"; } + if (d->subkeyType != Subkey::AlgoUnknown) { + keyParameters.push_back(QLatin1String{"Subkey-Type:"} + QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(d->subkeyType))); + } + for (auto it = d->parameters.constBegin(); it != d->parameters.constEnd(); ++it) { for (const auto &v : it.value()) { keyParameters.push_back(it.key() + QLatin1Char(':') + v); } } keyParameters.push_back(QLatin1String("")); return keyParameters.join(QLatin1Char('\n')); } diff --git a/src/utils/keyparameters.h b/src/utils/keyparameters.h index e5a42f5dd..f39dcf56f 100644 --- a/src/utils/keyparameters.h +++ b/src/utils/keyparameters.h @@ -1,69 +1,72 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/keyparameters.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include class QDate; class QString; #include namespace Kleo { class KeyParameters { public: enum Protocol { OpenPGP, CMS }; explicit KeyParameters(Protocol protocol); ~KeyParameters(); KeyParameters(const KeyParameters &other); KeyParameters &operator=(const KeyParameters &other); KeyParameters(KeyParameters &&other); KeyParameters &operator=(KeyParameters &&other); void setKeyType(GpgME::Subkey::PubkeyAlgo type); - void setKeyType(const QString &cardKeyRef); + GpgME::Subkey::PubkeyAlgo keyType() const; + void setCardKeyRef(const QString &cardKeyRef); + QString cardKeyRef() const; void setKeyLength(unsigned int length); void setKeyCurve(const QString &curve); void setKeyUsages(const QStringList &usages); void setSubkeyType(GpgME::Subkey::PubkeyAlgo type); + GpgME::Subkey::PubkeyAlgo subkeyType() const; void setSubkeyLength(unsigned int length); void setSubkeyCurve(const QString &curve); void setSubkeyUsages(const QStringList &usages); void setExpirationDate(const QDate &date); void setName(const QString &name); void setDN(const QString &dn); void setEmail(const QString &email); void addEmail(const QString &email); void addDomainName(const QString &domain); void addURI(const QString &uri); QString toString() const; private: class Private; std::unique_ptr d; }; }