Commit e093eac2 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Allow creation of CSRs for card keys of PIV cards

Certificate signing requests can only be created for signing keys
and encryption keys. gpgsm doesn't support authentication keys.
In this first iteration, the ASCII-armored request is simply shown
in a result dialog from where it can be copied and pasted.

GnuPG-bug-id: 5127
parent f35ea48e
Pipeline #43747 passed with stage
in 19 minutes and 38 seconds
......@@ -148,6 +148,7 @@ set(_kleopatra_SRCS
dialogs/pivcardapplicationadministrationkeyinputdialog.cpp
dialogs/certificatedetailsinputwidget.cpp
dialogs/createcsrforcardkeydialog.cpp
dialogs/csrcreationresultdialog.cpp
crypto/controller.cpp
crypto/certificateresolver.cpp
......@@ -251,6 +252,7 @@ set(_kleopatra_SRCS
commands/certificatetopivcardcommand.cpp
commands/importcertificatefrompivcardcommand.cpp
commands/createopenpgpkeyfromcardkeyscommand.cpp
commands/createcsrforcardkeycommand.cpp
${_kleopatra_uiserver_files}
......
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "createcsrforcardkeycommand.h"
#include "cardcommand_p.h"
#include "dialogs/createcsrforcardkeydialog.h"
#include "dialogs/csrcreationresultdialog.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include "utils/keyparameters.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <QUrl>
#include <QGpgME/Protocol>
#include <QGpgME/KeyGenerationJob>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme.h>
#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<CreateCSRForCardKeyCommand *>(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);
void ensureDialogCreated();
private:
std::string appName;
std::string keyRef;
QStringList keyUsages;
QPointer<CreateCSRForCardKeyDialog> dialog;
};
CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() const
{
return static_cast<const Private *>(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 != 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"));
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 {
auto resultDialog = new CSRCreationResultDialog;
applyWindowID(resultDialog);
resultDialog->setAttribute(Qt::WA_DeleteOnClose);
resultDialog->setCSR(request);
resultDialog->show();
}
finished();
}
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"
/* -*- mode: c++; c-basic-offset:4 -*-
commands/createcsrforcardkeycommand.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_COMMANDS_CREATECSRFORCARDKEYCOMMAND_H__
#define __KLEOPATRA_COMMANDS_CREATECSRFORCARDKEYCOMMAND_H__
#include "cardcommand.h"
namespace Kleo
{
namespace Commands
{
class CreateCSRForCardKeyCommand : public CardCommand
{
Q_OBJECT
public:
explicit CreateCSRForCardKeyCommand(const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent = nullptr);
~CreateCSRForCardKeyCommand() override;
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
Q_PRIVATE_SLOT(d_func(), void slotDialogAccepted())
Q_PRIVATE_SLOT(d_func(), void slotDialogRejected())
Q_PRIVATE_SLOT(d_func(), void slotResult(const GpgME::KeyGenerationResult &, const QByteArray &))
};
} // namespace Commands
} // namespace Kleo
#endif // __KLEOPATRA_COMMANDS_CREATECSRFORCARDKEYCOMMAND_H__
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/csrcreationresultdialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "csrcreationresultdialog.h"
#include <KConfigGroup>
#include <KGuiItem>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
using namespace Kleo;
using namespace Kleo::Dialogs;
class CSRCreationResultDialog::Private
{
friend class ::Kleo::Dialogs::CSRCreationResultDialog;
CSRCreationResultDialog *const q;
struct {
QPlainTextEdit *csrBrowser = nullptr;
QDialogButtonBox *buttonBox = nullptr;
} ui;
QByteArray csr;
public:
Private(CSRCreationResultDialog *qq)
: q(qq)
{
auto mainLayout = new QVBoxLayout(q);
{
auto label = new QLabel(i18n("The certificate signing request was created successfully. Please find the result and suggested next steps below."));
label->setWordWrap(true);
mainLayout->addWidget(label);
}
mainLayout->addWidget(new KSeparator(Qt::Horizontal));
ui.csrBrowser = new QPlainTextEdit();
ui.csrBrowser->setLineWrapMode(QPlainTextEdit::NoWrap);
ui.csrBrowser->setReadOnly(true);
ui.csrBrowser->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mainLayout->addWidget(ui.csrBrowser);
mainLayout->addWidget(new KSeparator(Qt::Horizontal));
ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
KGuiItem::assign(ui.buttonBox->button(QDialogButtonBox::Close), KStandardGuiItem::close());
connect(ui.buttonBox, &QDialogButtonBox::clicked, q, &QDialog::close);
mainLayout->addWidget(ui.buttonBox);
// calculate default size with enough space for the text edit
const auto fm = ui.csrBrowser->fontMetrics();
const QSize sizeHint = q->sizeHint();
const QSize defaultSize = QSize(qMax(sizeHint.width(), 90 * fm.horizontalAdvance(QLatin1Char('x'))),
sizeHint.height() - ui.csrBrowser->sizeHint().height() + 10 * fm.lineSpacing());
restoreGeometry(defaultSize);
}
~Private()
{
saveGeometry();
}
private:
void saveGeometry()
{
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "CSRCreationResultDialog");
cfgGroup.writeEntry("Size", q->size());
cfgGroup.sync();
}
void restoreGeometry(const QSize &defaultSize)
{
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "CSRCreationResultDialog");
const QSize size = cfgGroup.readEntry("Size", defaultSize);
if (size.isValid()) {
q->resize(size);
}
}
};
CSRCreationResultDialog::CSRCreationResultDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setWindowTitle(i18nc("@title:window", "CSR Created"));
}
CSRCreationResultDialog::~CSRCreationResultDialog()
{
}
void CSRCreationResultDialog::setCSR(const QByteArray &csr)
{
d->csr = csr;
d->ui.csrBrowser->setPlainText(QString::fromLatin1(csr));
}
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/csrcreationresultdialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_DIALOGS_CSRCREATIONRESULTDIALOG_H__
#define __KLEOPATRA_DIALOGS_CSRCREATIONRESULTDIALOG_H__
#include <QDialog>
namespace Kleo
{
namespace Dialogs
{
class CSRCreationResultDialog : public QDialog
{
Q_OBJECT
public:
explicit CSRCreationResultDialog(QWidget *parent = nullptr);
~CSRCreationResultDialog() override;
void setCSR(const QByteArray &csr);
private:
class Private;
const std::unique_ptr<Private> d;
};
}
}
#endif // __KLEOPATRA_DIALOGS_CSRCREATIONRESULTDIALOG_H__
......@@ -80,6 +80,11 @@ void KeyParameters::setKeyType(Subkey::PubkeyAlgo type)
d->keyType = QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(type));
}
void KeyParameters::setKeyType(const QString &cardKeyRef)
{
d->keyType = QLatin1String("card:") + cardKeyRef;
}
void KeyParameters::setKeyLength(unsigned int length)
{
d->setValue(QStringLiteral("Key-Length"), QString::number(length));
......
......@@ -34,6 +34,7 @@ public:
~KeyParameters();
void setKeyType(GpgME::Subkey::PubkeyAlgo type);
void setKeyType(const QString &cardKeyRef);
void setKeyLength(unsigned int length);
void setKeyCurve(const QString &curve);
void setKeyUsages(const QStringList &usages);
......
......@@ -13,6 +13,7 @@
#include "commands/certificatetopivcardcommand.h"
#include "commands/changepincommand.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include "commands/importcertificatefrompivcardcommand.h"
#include "commands/keytocardcommand.h"
......@@ -56,6 +57,9 @@ static void layoutKeyWidgets(QGridLayout *grid, const QString &keyName, const PI
grid->addWidget(keyWidgets.certificateInfo, row, 1, 1, 2);
grid->addWidget(keyWidgets.writeCertificateButton, row, 3);
grid->addWidget(keyWidgets.importCertificateButton, row, 4);
if (keyWidgets.createCSRButton) {
grid->addWidget(keyWidgets.createCSRButton, row, 5);
}
}
static int toolTipOptions()
......@@ -182,6 +186,13 @@ PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const KeyPairInfo &key
keyWidgets.generateButton->setEnabled(false);
connect(keyWidgets.generateButton, &QPushButton::clicked,
this, [this, keyRef] () { generateKey(keyRef); });
if (keyInfo.canSign() || keyInfo.canEncrypt()) {
keyWidgets.createCSRButton = new QPushButton(i18nc("@action:button", "Create CSR"), this);
keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
keyWidgets.createCSRButton->setEnabled(false);
connect(keyWidgets.createCSRButton, &QPushButton::clicked,
this, [this, keyRef] () { createCSR(keyRef); });
}
keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate"));
keyWidgets.writeCertificateButton->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card"));
keyWidgets.writeCertificateButton->setEnabled(false);
......@@ -237,6 +248,9 @@ void PIVCardWidget::updateKeyWidgets(const std::string &keyRef, const PIVCard *c
widgets.generateButton->setText(i18nc("@action:button", "Generate"));
widgets.generateButton->setToolTip(
i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef)));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(false);
}
widgets.writeCertificateButton->setEnabled(false);
widgets.importCertificateButton->setEnabled(false);
} else {
......@@ -264,6 +278,9 @@ void PIVCardWidget::updateKeyWidgets(const std::string &keyRef, const PIVCard *c
widgets.generateButton->setText(i18nc("@action:button", "Replace"));
widgets.generateButton->setToolTip(
i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef)));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(true);
}
}
widgets.generateButton->setEnabled(true);
......@@ -281,6 +298,17 @@ void PIVCardWidget::generateKey(const std::string &keyref)
cmd->start();
}
void PIVCardWidget::createCSR(const std::string &keyref)
{
auto cmd = new CreateCSRForCardKeyCommand(keyref, mCardSerialNumber, PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PIVCardWidget::writeCertificateToCard(const std::string &keyref)
{
auto cmd = new CertificateToPIVCardCommand(keyref, mCardSerialNumber);
......
......@@ -41,6 +41,7 @@ public:
QLabel *keyAlgorithm = nullptr;
QLabel *certificateInfo = nullptr;
QPushButton *generateButton = nullptr;
QPushButton *createCSRButton = nullptr;
QPushButton *writeCertificateButton = nullptr;
QPushButton *importCertificateButton = nullptr;
QPushButton *writeKeyButton = nullptr;
......@@ -50,6 +51,7 @@ private:
KeyWidgets createKeyWidgets(const SmartCard::KeyPairInfo &keyInfo);
void updateKeyWidgets(const std::string &keyRef, const SmartCard::PIVCard *card);
void generateKey(const std::string &keyref);
void createCSR(const std::string &keyref);
void writeCertificateToCard(const std::string &keyref);
void importCertificateFromCard(const std::string &keyref);
void writeKeyToCard(const std::string &keyref);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment