diff --git a/src/commands/createcsrforcardkeycommand.cpp b/src/commands/createcsrforcardkeycommand.cpp index b20bb802f..6e4cea2e6 100644 --- a/src/commands/createcsrforcardkeycommand.cpp +++ b/src/commands/createcsrforcardkeycommand.cpp @@ -1,295 +1,296 @@ /* -*- 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(); 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 != OpenPGPCard::AppName && appName != PIVCard::AppName) { + 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, SIGNAL(result(const GpgME::KeyGenerationResult &, const QByteArray &)), q, SLOT(slotResult(const GpgME::KeyGenerationResult &, const QByteArray &))); KeyParameters keyParameters(KeyParameters::CMS); keyParameters.setKeyType(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())), i18nc("@title", "Error")); 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())), i18nc("@title", "Error")); } 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, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(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/view/netkeywidget.cpp b/src/view/netkeywidget.cpp index 767dcb94c..87f473df1 100644 --- a/src/view/netkeywidget.cpp +++ b/src/view/netkeywidget.cpp @@ -1,229 +1,306 @@ /* view/netkeywidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "netkeywidget.h" #include "nullpinwidget.h" #include "keytreeview.h" #include "kleopatraapplication.h" #include "systrayicon.h" #include "kleopatra_debug.h" #include "smartcard/netkeycard.h" #include "smartcard/readerstatus.h" #include "commands/changepincommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" +#include "commands/createcsrforcardkeycommand.h" #include "commands/learncardkeyscommand.h" #include "commands/detailscommand.h" #include +#include #include #include #include #include #include #include #include #include +#include +#include + using namespace Kleo; using namespace Kleo::SmartCard; using namespace Kleo::Commands; NetKeyWidget::NetKeyWidget(QWidget *parent) : QWidget(parent), mSerialNumberLabel(new QLabel(this)), mVersionLabel(new QLabel(this)), mLearnKeysLabel(new QLabel(this)), mErrorLabel(new QLabel(this)), mNullPinWidget(new NullPinWidget(this)), mLearnKeysBtn(new QPushButton(this)), mChangeNKSPINBtn(new QPushButton(this)), mChangeSigGPINBtn(new QPushButton(this)), mTreeView(new KeyTreeView(this)), mArea(new QScrollArea) { auto vLay = new QVBoxLayout; // Set up the scroll are mArea->setFrameShape(QFrame::NoFrame); mArea->setWidgetResizable(true); auto mAreaWidget = new QWidget; mAreaWidget->setLayout(vLay); mArea->setWidget(mAreaWidget); auto scrollLay = new QVBoxLayout(this); scrollLay->setContentsMargins(0, 0, 0, 0); scrollLay->addWidget(mArea); // Add general widgets mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft); mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); auto hLay1 = new QHBoxLayout; hLay1->addWidget(new QLabel(i18n("Serial number:"))); hLay1->addWidget(mSerialNumberLabel); hLay1->addStretch(1); vLay->addLayout(hLay1); vLay->addWidget(mNullPinWidget); auto line1 = new QFrame(); line1->setFrameShape(QFrame::HLine); vLay->addWidget(line1); vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Certificates:"))), 0, Qt::AlignLeft); mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card.")); mLearnKeysBtn->setText(i18nc("@action", "Load Certificates")); connect(mLearnKeysBtn, &QPushButton::clicked, this, [this] () { mLearnKeysBtn->setEnabled(false); auto cmd = new LearnCardKeysCommand(GpgME::CMS); cmd->setParentWidget(this); cmd->start(); auto icon = KleopatraApplication::instance()->sysTrayIcon(); if (icon) { icon->setLearningInProgress(true); } connect(cmd, &Command::finished, this, [icon] () { ReaderStatus::mutableInstance()->updateStatus(); icon->setLearningInProgress(false); }); }); auto hLay2 = new QHBoxLayout; hLay2->addWidget(mLearnKeysLabel); hLay2->addWidget(mLearnKeysBtn); hLay2->addStretch(1); vLay->addLayout(hLay2); mErrorLabel->setVisible(false); vLay->addWidget(mErrorLabel); // The certificate view mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView)); mTreeView->setHierarchicalView(true); connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this] (const QModelIndex &idx) { const auto klm = dynamic_cast (mTreeView->view()->model()); if (!klm) { qCDebug(KLEOPATRA_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className(); return; } auto cmd = new DetailsCommand(klm->key(idx), nullptr); cmd->setParentWidget(this); cmd->start(); }); vLay->addWidget(mTreeView); // The action area auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); vLay->addWidget(line2); vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), 0, Qt::AlignLeft); auto actionLayout = new QHBoxLayout(); if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) { mKeyForCardKeysButton = new QPushButton(this); mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key")); mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card.")); actionLayout->addWidget(mKeyForCardKeysButton); connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys); } + if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184 + mCreateCSRButton = new QPushButton(this); + mCreateCSRButton->setText(i18nc("@action:button", "Create CSR")); + mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card")); + mCreateCSRButton->setEnabled(false); + actionLayout->addWidget(mCreateCSRButton); + connect(mCreateCSRButton, &QPushButton::clicked, this, [this] () { createCSR(); }); + } + mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN")); mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::nksPinKeyRef()); }); connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::sigGPinKeyRef()); }); actionLayout->addWidget(mChangeNKSPINBtn); actionLayout->addWidget(mChangeSigGPINBtn); actionLayout->addStretch(1); vLay->addLayout(actionLayout); vLay->addStretch(1); } NetKeyWidget::~NetKeyWidget() { } +namespace +{ +std::vector getUsableSigningKeys(const NetKeyCard *netKeyCard) +{ + std::vector keys; + + for (const auto &key : netKeyCard->keyInfos()) { + if (key.canSign() && (((key.keyRef.substr(0, 9) == "NKS-NKS3.") && !netKeyCard->hasNKSNullPin()) || + ((key.keyRef.substr(0, 9) == "NKS-SIGG.") && !netKeyCard->hasSigGNullPin()))) { + keys.push_back(key); + } + } + + return keys; +} +} + void NetKeyWidget::setCard(const NetKeyCard* card) { mSerialNumber = card->serialNumber(); mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion())); mSerialNumberLabel->setText(QString::fromStdString(mSerialNumber)); mNullPinWidget->setSerialNumber(mSerialNumber); /* According to users of NetKey Cards it is fairly uncommon * to use SigG Certificates at all. So it should be optional to set the pins. */ mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/); mNullPinWidget->setSigGVisible(false/*card->hasSigGNullPin()*/); mNullPinWidget->setNKSVisible(card->hasNKSNullPin()); mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin()); if (card->hasSigGNullPin()) { mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN")); } else { mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); } mLearnKeysBtn->setEnabled(true); mLearnKeysBtn->setVisible(card->canLearnKeys()); mTreeView->setVisible(!card->canLearnKeys()); mLearnKeysLabel->setVisible(card->canLearnKeys()); const auto errMsg = card->errorMsg(); if (!errMsg.isEmpty()) { mErrorLabel->setText(QStringLiteral("%1: %2").arg(i18n("Error"), errMsg)); mErrorLabel->setVisible(true); } else { mErrorLabel->setVisible(false); } const auto keys = card->keys(); mTreeView->setKeys(keys); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin() && card->hasSigningKey() && card->hasEncryptionKey()); } + if (mCreateCSRButton) { + mCreateCSRButton->setEnabled(!getUsableSigningKeys(card).empty()); + } } void NetKeyWidget::doChangePin(const std::string &keyRef) { auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->start(); } void NetKeyWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, NetKeyCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } + +namespace +{ +std::string getKeyRef(const std::vector &keys, QWidget *parent) +{ + QStringList options; + for (const auto &key : keys) { + options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip)); + } + + bool ok; + const QString choice = QInputDialog::getItem(parent, i18n("Select Key"), + i18n("Please select the key you want to create a certificate signing request for:"), options, /* current= */ 0, /* editable= */ false, &ok); + return ok ? keys[options.indexOf(choice)].keyRef : std::string(); +} +} + +void NetKeyWidget::createCSR() +{ + const auto netKeyCard = ReaderStatus::instance()->getCard(mSerialNumber); + if (!netKeyCard) { + KMessageBox::error(this, + i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber))); + return; + } + const auto usableKeys = getUsableSigningKeys(netKeyCard.get()); + if (usableKeys.empty()) { + KMessageBox::error(this, + i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard.")); + return; + } + const auto keyRef = getKeyRef(usableKeys, this); + if (keyRef.empty()) { + return; + } + auto cmd = new CreateCSRForCardKeyCommand(keyRef, mSerialNumber, NetKeyCard::AppName, this); + this->setEnabled(false); + connect(cmd, &CreateCSRForCardKeyCommand::finished, + this, [this]() { + this->setEnabled(true); + }); + cmd->start(); +} diff --git a/src/view/netkeywidget.h b/src/view/netkeywidget.h index 0d3729e43..a62e33131 100644 --- a/src/view/netkeywidget.h +++ b/src/view/netkeywidget.h @@ -1,59 +1,61 @@ /* view/netkeywidget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef VIEW_NETKEYWIDGET_H #define VIEW_NETKEYWIDGET_H #include #include #include class QLabel; class QPushButton; class QScrollArea; namespace Kleo { class NullPinWidget; class KeyTreeView; namespace SmartCard { class NetKeyCard; } // namespace SmartCard class NetKeyWidget: public QWidget { Q_OBJECT public: explicit NetKeyWidget(QWidget *parent = nullptr); ~NetKeyWidget(); void setCard(const SmartCard::NetKeyCard *card); private: void doChangePin(const std::string &keyRef); void createKeyFromCardKeys(); + void createCSR(); private: std::string mSerialNumber; QLabel *mSerialNumberLabel = nullptr, *mVersionLabel = nullptr, *mLearnKeysLabel = nullptr, *mErrorLabel = nullptr; NullPinWidget *mNullPinWidget = nullptr; QPushButton *mLearnKeysBtn = nullptr, *mKeyForCardKeysButton = nullptr, + *mCreateCSRButton = nullptr, *mChangeNKSPINBtn = nullptr, *mChangeSigGPINBtn = nullptr; KeyTreeView *mTreeView = nullptr; QScrollArea *mArea = nullptr; }; } // namespace Kleo #endif // VIEW_NETKEYWIDGET_H