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

Extract certificate to PIV card functionality from KeyToCardCommand

GnuPG-bug-id: 4794
parent 6ab94469
......@@ -233,6 +233,7 @@ set(_kleopatra_SRCS
commands/changepincommand.cpp
commands/authenticatepivcardapplicationcommand.cpp
commands/setpivcardapplicationadministrationkeycommand.cpp
commands/certificatetopivcardcommand.cpp
${_kleopatra_uiserver_files}
......
/* commands/certificatetopivcardcommand.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 "certificatetopivcardcommand.h"
#include "command_p.h"
#include "smartcard/readerstatus.h"
#include "smartcard/pivcard.h"
#include "commands/authenticatepivcardapplicationcommand.h"
#include "utils/writecertassuantransaction.h"
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <qgpgme/dataprovider.h>
#include <gpgme++/context.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
class CertificateToPIVCardCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::CertificateToPIVCardCommand;
CertificateToPIVCardCommand *q_func() const
{
return static_cast<CertificateToPIVCardCommand *>(q);
}
public:
explicit Private(CertificateToPIVCardCommand *qq, const GpgME::Subkey &key, const std::string &serialno);
explicit Private(CertificateToPIVCardCommand *qq, const std::string &cardSlot, const std::string &serialno);
~Private();
private:
void start();
void startCertificateToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
private:
std::string mSerial;
GpgME::Subkey mSubkey;
std::string cardSlot;
bool overwriteExistingAlreadyApproved = false;
bool hasBeenCanceled = false;
};
CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define q q_func()
#define d d_func()
CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq,
const GpgME::Subkey &key,
const std::string &serialno)
: Command::Private(qq, nullptr),
mSerial(serialno),
mSubkey(key)
{
}
CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq, const std::string &cardSlot_, const std::string &serialno)
: Command::Private(qq, nullptr)
, mSerial(serialno)
, cardSlot(cardSlot_)
{
}
CertificateToPIVCardCommand::Private::~Private()
{
}
namespace {
static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card)
{
if (!cardSlot.empty()) {
if (cardSlot == PIVCard::digitalSignatureKeyRef()) {
// get signing certificate matching the key grip
const std::string cardKeygrip = card->keyGrip(cardSlot);
const auto subkey = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip);
if (subkey.canSign() && subkey.parent().protocol() == GpgME::CMS) {
return subkey;
}
}
if (cardSlot == PIVCard::keyManagementKeyRef()) {
// get encryption certificate with secret subkey
}
return GpgME::Subkey();
}
return GpgME::Subkey();
}
}
void CertificateToPIVCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::start()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(mSerial);
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial)));
finished();
return;
}
mSubkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
if (mSubkey.isNull()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
finished();
return;
}
startCertificateToPIVCard();
}
void CertificateToPIVCardCommand::Private::startCertificateToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startCertificateToPIVCard()";
auto ctx = Context::createForProtocol(GpgME::CMS);
QGpgME::QByteArrayDataProvider dp;
Data data(&dp);
const Error err = ctx->exportPublicKeys(mSubkey.parent().primaryFingerprint(), data);
if (err) {
error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())),
i18nc("@title", "Error"));
finished();
return;
}
const QByteArray certificateData = dp.data();
const QString cmd = QStringLiteral("SCD WRITECERT %1")
.arg(QString::fromStdString(cardSlot));
auto transaction = std::unique_ptr<AssuanTransaction>(new WriteCertAssuanTransaction(certificateData));
ReaderStatus::mutableInstance()->startTransaction(cmd.toUtf8(), q_func(), "certificateToPIVCardDone", std::move(transaction));
}
void CertificateToPIVCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticate()";
auto cmd = new AuthenticatePIVCardApplicationCommand(mSerial, parentWidgetOrView());
connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
q, [this]() { authenticationFinished(); });
connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
q, [this]() { authenticationCanceled(); });
cmd->start();
}
void CertificateToPIVCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startCertificateToPIVCard();
}
}
void CertificateToPIVCardCommand::Private::authenticationCanceled()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationCanceled()";
hasBeenCanceled = true;
canceled();
}
CertificateToPIVCardCommand::CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno)
: Command(new Private(this, cardSlot, serialno))
{
}
CertificateToPIVCardCommand::~CertificateToPIVCardCommand()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()";
}
void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err)
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():"
<< err.asString() << "(" << err.code() << ")";
if (err) {
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
  • This causes a build issue with older libgpg-error versions. Looks like GPG_ERR_NO_AUTH was added in 1.36 and libkleo requires gpgme 1.11.1 which requires gpg-error 1.24

  • Should be fixed.

  • Confirmed, thanks for the quick fix

  • Looks like src/commands/authenticatepivcardapplicationcommand.cpp needs a similar fix in release/20.12: authenticatepivcardapplicationcommand.cpp:139:27: error: 'GPG_ERR_BAD_AUTH' was not declared in this scope

Please register or sign in to reply
d->authenticate();
return;
}
d->error(i18nc("@info",
"Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())),
i18nc("@title", "Error"));
} else if (!err.isCanceled()) {
KMessageBox::information(d->parentWidgetOrView(),
i18n("Successfully copied the certificate to the card."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void CertificateToPIVCardCommand::doStart()
{
qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::doStart()";
d->start();
}
void CertificateToPIVCardCommand::doCancel()
{
}
#undef q_func
#undef d_func
/* commands/certificatetopivcardcommand.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_CERTIFICATETOPIVCARDCOMMAND_H__
#define __KLEOPATRA_COMMANDS_CERTIFICATETOPIVCARDCOMMAND_H__
#include <commands/command.h>
namespace GpgME
{
class Error;
}
namespace Kleo
{
namespace Commands
{
class CertificateToPIVCardCommand : public Command
{
Q_OBJECT
public:
CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno);
~CertificateToPIVCardCommand() override;
public Q_SLOTS:
void certificateToPIVCardDone(const GpgME::Error &err);
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
}
}
#endif /* __KLEOPATRA_COMMANDS_CERTIFICATETOPIVCARDCOMMAND_H__ */
......@@ -3,6 +3,8 @@
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -20,20 +22,12 @@
#include "commands/authenticatepivcardapplicationcommand.h"
#include "utils/writecertassuantransaction.h"
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <QInputDialog>
#include <QDateTime>
#include <QStringList>
#include <qgpgme/dataprovider.h>
#include <gpgme++/context.h>
#include "kleopatra_debug.h"
using namespace Kleo;
......@@ -56,11 +50,10 @@ public:
private:
void start();
void startTransferToOpenPGPCard();
void startKeyToOpenPGPCard();
void startTransferToPIVCard();
void startKeyToPIVCard();
void startCertificateToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
......@@ -110,17 +103,6 @@ void KeyToCardCommand::Private::start()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()";
// for now we always use the first smart card
if (mSerial.empty()) {
const auto cards = SmartCard::ReaderStatus::instance()->getCards();
if (!cards.size() || cards[0]->serialNumber().empty()) {
error(i18n("Failed to find a smart card."));
finished();
return;
}
mSerial = cards[0]->serialNumber();
}
const auto card = SmartCard::ReaderStatus::instance()->getCard<Card>(mSerial);
if (!card) {
error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(mSerial)));
......@@ -130,11 +112,11 @@ void KeyToCardCommand::Private::start()
switch (card->appType()) {
case SmartCard::Card::OpenPGPApplication: {
startTransferToOpenPGPCard();
startKeyToOpenPGPCard();
}
break;
case SmartCard::Card::PivApplication: {
startTransferToPIVCard();
startKeyToPIVCard();
}
break;
default: {
......@@ -182,8 +164,8 @@ static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent
}
}
void KeyToCardCommand::Private::startTransferToOpenPGPCard() {
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToOpenPGPCard()";
void KeyToCardCommand::Private::startKeyToOpenPGPCard() {
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()";
const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard<OpenPGPCard>(mSerial);
if (!pgpCard) {
......@@ -247,17 +229,9 @@ void KeyToCardCommand::Private::startTransferToOpenPGPCard() {
}
namespace {
static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card)
static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &/*card*/)
{
if (!cardSlot.empty()) {
if (cardSlot == PIVCard::digitalSignatureKeyRef()) {
// get signing certificate matching the key grip
const std::string cardKeygrip = card->keyGrip(cardSlot);
const auto subkey = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip);
if (subkey.canSign() && subkey.parent().protocol() == GpgME::CMS) {
return subkey;
}
}
if (cardSlot == PIVCard::keyManagementKeyRef()) {
// get encryption certificate with secret subkey
}
......@@ -266,46 +240,11 @@ static GpgME::Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, c
return GpgME::Subkey();
}
static std::string getPIVCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent)
{
// Check if we need to ask the user for the slot
if (subKey.canSign() && !subKey.canEncrypt()) {
// Signing only
return PIVCard::digitalSignatureKeyRef();
}
if (subKey.canEncrypt() && !subKey.canSign()) {
// Encrypt only
return PIVCard::keyManagementKeyRef();
}
// Multiple uses, ask user.
QMap<QString, std::string> options;
if (subKey.canSign()) {
options.insert(i18nc("Placeholder 1 is the name of a key, e.g. 'Digital Signature Key'; "
"placeholder 2 is the identifier of a slot on a smart card", "%1 (%2)",
PIVCard::keyDisplayName(PIVCard::digitalSignatureKeyRef()), QString::fromStdString(PIVCard::digitalSignatureKeyRef())),
PIVCard::digitalSignatureKeyRef());
}
if (subKey.canEncrypt()) {
options.insert(i18nc("Placeholder 1 is the name of a key, e.g. 'Digital Signature Key'; "
"placeholder 2 is the identifier of a slot on a smart card", "%1 (%2)",
PIVCard::keyDisplayName(PIVCard::keyManagementKeyRef()), QString::fromStdString(PIVCard::keyManagementKeyRef())),
PIVCard::keyManagementKeyRef());
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"),
i18n("Please select the card slot the certificate should be written to:"), options.keys(), /* current= */ 0, /* editable= */ false, &ok);
const std::string slot = options.value(choice);
return ok ? slot : std::string();
}
}
void KeyToCardCommand::Private::startTransferToPIVCard()
void KeyToCardCommand::Private::startKeyToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToPIVCard()";
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(mSerial);
if (!pivCard) {
......@@ -314,13 +253,17 @@ void KeyToCardCommand::Private::startTransferToPIVCard()
return;
}
if (cardSlot != PIVCard::keyManagementKeyRef()) {
// key to card is only supported for encryption keys
finished();
return;
}
if (mSubkey.isNull()) {
mSubkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard);
}
if (mSubkey.isNull()) {
if (!cardSlot.empty()) {
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
}
error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
finished();
return;
}
......@@ -335,34 +278,6 @@ void KeyToCardCommand::Private::startTransferToPIVCard()
return;
}
// get card slot unless it was already selected before authentication was performed
if (cardSlot.empty()) {
cardSlot = getPIVCardSlotForKey(mSubkey, parentWidgetOrView());
if (cardSlot.empty()) {
finished();
return;
}
}
if (cardSlot == PIVCard::keyManagementKeyRef()) {
startKeyToPIVCard();
} else {
// skip key to card because it's only supported for encryption keys
startCertificateToPIVCard();
}
}
void KeyToCardCommand::Private::startKeyToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(mSerial);
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial)));
finished();
return;
}
// Check if we need to do the overwrite warning.
if (!overwriteExistingAlreadyApproved) {
const std::string existingKey = pivCard->keyGrip(cardSlot);
......@@ -393,48 +308,6 @@ void KeyToCardCommand::Private::startKeyToPIVCard()
ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), q_func(), "keyToPIVCardDone");
}
void KeyToCardCommand::Private::startCertificateToPIVCard()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startCertificateToPIVCard()";
const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(mSerial);
if (!pivCard) {
error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial)));
finished();
return;
}
// verify that public keys on the card and in the certificate match
const std::string cardKeygrip = pivCard->keyGrip(cardSlot);
const std::string certificateKeygrip = mSubkey.keyGrip();
if (cardKeygrip != certificateKeygrip) {
error(i18n("<p>The certificate does not seem to correspond to the key on the card.</p>"
"<p>Public key on card: %1<br>"
"Public key of certificate: %2</p>",
QString::fromStdString(cardKeygrip),
QString::fromStdString(certificateKeygrip)));
finished();
return;
}
auto ctx = Context::createForProtocol(GpgME::CMS);
QGpgME::QByteArrayDataProvider dp;
Data data(&dp);
const Error err = ctx->exportPublicKeys(mSubkey.parent().primaryFingerprint(), data);
if (err) {
error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())),
i18nc("@title", "Error"));
finished();
return;
}
const QByteArray certificateData = dp.data();
const QString cmd = QStringLiteral("SCD WRITECERT %1")
.arg(QString::fromStdString(cardSlot));
auto transaction = std::unique_ptr<AssuanTransaction>(new WriteCertAssuanTransaction(certificateData));
ReaderStatus::mutableInstance()->startTransaction(cmd.toUtf8(), q_func(), "certificateToPIVCardDone", std::move(transaction));
}
void KeyToCardCommand::Private::authenticate()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()";
......@@ -451,7 +324,7 @@ void KeyToCardCommand::Private::authenticationFinished()
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()";
if (!hasBeenCanceled) {
startTransferToPIVCard();
startKeyToPIVCard();
}
}
......@@ -512,11 +385,6 @@ void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err)
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::keyToPIVCardDone():"
<< err.asString() << "(" << err.code() << ")";
if (!err && !err.isCanceled()) {
d->startCertificateToPIVCard();
return;
}
if (err) {
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
......@@ -532,30 +400,6 @@ void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err)
d->finished();
}
void KeyToCardCommand::certificateToPIVCardDone(const GpgME::Error &err)
{
qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::certificateToPIVCardDone():"
<< err.asString() << "(" << err.code() << ")";
if (err) {
// gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
d->authenticate();
return;
}
d->error(i18nc("@info",
"Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())),
i18nc("@title", "Error"));
} else if (!err.isCanceled()) {
KMessageBox::information(d->parentWidgetOrView(),
i18n("Successfully copied the certificate to the card."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
void KeyToCardCommand::deleteDone(const GpgME::Error &err)
{
if (err) {
......
......@@ -3,6 +3,8 @@
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH