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

Transfer certificate data of X.509 encryption certificate to PIV cards

* Add WriteCertAssuanTransaction implementing the inquire() callback
for sending the certificate data to scdaemon.
* Add startTransaction() to ReaderStatus for starting a transaction using
a custom AssuanTransaction subclass.
* Make KeyToCardCommand transfer the certificate data additionally to the
secret key data of X.509 encryption certificates to PIV cards.

GnuPG-bug-id: 4794
parent ce81c052
Pipeline #33686 passed with stage
in 33 minutes and 14 seconds
......@@ -89,6 +89,7 @@ set(_kleopatra_SRCS
utils/clipboardmenu.cpp
utils/kuniqueservice.cpp
utils/remarks.cpp
utils/writecertassuantransaction.cpp
selftest/selftest.cpp
selftest/enginecheck.cpp
......
......@@ -20,11 +20,17 @@
#include "commands/authenticatepivcardapplicationcommand.h"
#include "utils/writecertassuantransaction.h"
#include <KLocalizedString>
#include <QInputDialog>
#include <QDateTime>
#include <QStringList>
#include <KLocalizedString>
#include <qgpgme/dataprovider.h>
#include <gpgme++/context.h>
#include "kleopatra_debug.h"
......@@ -49,6 +55,7 @@ private:
void start();
void startTransferToOpenPGPCard();
void startTransferToPIVCard();
void startCertificateToPIVCard();
void authenticate();
void authenticationFinished();
void authenticationCanceled();
......@@ -265,6 +272,57 @@ void KeyToCardCommand::Private::startTransferToPIVCard()
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;
}
// Check if we need to do the overwrite warning.
if (!overwriteExistingAlreadyApproved) {
const std::string existingKey = pivCard->keyGrip(PIVCard::keyManagementKeyRef());
if (!existingKey.empty()) {
const QString message = i18nc("@info",
"<p>This card already contains a key in this slot. Continuing will <b>overwrite</b> that key.</p>"
"<p>If there is no backup the existing key will be irrecoverably lost.</p>") +
i18n("The existing key has the key grip:") +
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(existingKey)) +
i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.");
const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message,
i18nc("@title:window", "Overwrite existing key"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (choice != KMessageBox::Continue) {
finished();
return;
}
overwriteExistingAlreadyApproved = true;
}
}
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();
// Now do the deed
const QString cmd = QStringLiteral("SCD WRITECERT %1")
.arg(QString::fromStdString(PIVCard::keyManagementKeyRef()));
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()";
......@@ -352,6 +410,11 @@ 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) {
......@@ -362,12 +425,32 @@ void KeyToCardCommand::keyToPIVCardDone(const GpgME::Error &err)
d->error(i18nc("@info",
"Moving the key to the card failed: %1", QString::fromUtf8(err.asString())),
i18nc("@title", "Error"));
}
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 key to the card."),
i18n("Successfully copied the certificate to the card."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
d->finished();
}
......
......@@ -40,6 +40,7 @@ public:
public Q_SLOTS:
void keyToOpenPGPCardDone(const GpgME::Error &err);
void keyToPIVCardDone(const GpgME::Error &err);
void certificateToPIVCardDone(const GpgME::Error &err);
void deleteDone(const GpgME::Error &err);
private:
......
......@@ -156,25 +156,31 @@ static Card::PinState parse_pin_state(const QString &s)
}
}
static std::unique_ptr<DefaultAssuanTransaction> gpgagent_transact(std::shared_ptr<Context> &gpgAgent, const char *command, Error &err)
template<typename T>
static std::unique_ptr<T> gpgagent_transact(std::shared_ptr<Context> &gpgAgent, const char *command, std::unique_ptr<T> transaction, Error &err)
{
qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")";
err = gpgAgent->assuanTransact(command);
err = gpgAgent->assuanTransact(command, std::move(transaction));
if (err.code()) {
qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString());
if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context";
gpgAgent.reset();
}
return std::unique_ptr<DefaultAssuanTransaction>();
return std::unique_ptr<T>();
}
std::unique_ptr<AssuanTransaction> t = gpgAgent->takeLastAssuanTransaction();
return std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction*>(t.release()));
return std::unique_ptr<T>(dynamic_cast<T*>(t.release()));
}
static std::unique_ptr<DefaultAssuanTransaction> gpgagent_default_transact(std::shared_ptr<Context> &gpgAgent, const char *command, Error &err)
{
return gpgagent_transact(gpgAgent, command, std::unique_ptr<DefaultAssuanTransaction>(new DefaultAssuanTransaction), err);
}
const std::vector< std::pair<std::string, std::string> > gpgagent_statuslines(std::shared_ptr<Context> gpgAgent, const char *what, Error &err)
{
const std::unique_ptr<DefaultAssuanTransaction> t = gpgagent_transact(gpgAgent, what, err);
const std::unique_ptr<DefaultAssuanTransaction> t = gpgagent_default_transact(gpgAgent, what, err);
if (t.get()) {
qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines();
return t->statusLines();
......@@ -182,7 +188,6 @@ const std::vector< std::pair<std::string, std::string> > gpgagent_statuslines(st
qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL";
return std::vector<std::pair<std::string, std::string> >();
}
}
static const std::string gpgagent_status(const std::shared_ptr<Context> &gpgAgent, const char *what, Error &err)
......@@ -360,7 +365,7 @@ static void handle_netkey_card(std::shared_ptr<Card> &ci, std::shared_ptr<Contex
nkCard->setPinStates(states);
// check for keys to learn:
const std::unique_ptr<DefaultAssuanTransaction> result = gpgagent_transact(gpg_agent, "SCD LEARN --keypairinfo", err);
const std::unique_ptr<DefaultAssuanTransaction> result = gpgagent_default_transact(gpg_agent, "SCD LEARN --keypairinfo", err);
if (err.code() || !result.get()) {
if (err) {
ci->setErrorMsg(QString::fromLatin1(err.asString()));
......@@ -440,10 +445,11 @@ struct Transaction {
QByteArray command;
QPointer<QObject> receiver;
const char *slot;
AssuanTransaction* assuanTransaction;
};
static const Transaction updateTransaction = { "__update__", nullptr, nullptr };
static const Transaction quitTransaction = { "__quit__", nullptr, nullptr };
static const Transaction updateTransaction = { "__update__", nullptr, nullptr, nullptr };
static const Transaction quitTransaction = { "__quit__", nullptr, nullptr, nullptr };
namespace
{
......@@ -522,6 +528,7 @@ private:
QByteArray command;
bool nullSlot = false;
AssuanTransaction* assuanTransaction = nullptr;
std::list<Transaction> item;
std::vector<std::shared_ptr<Card> > oldCards;
......@@ -551,6 +558,8 @@ private:
// we can release the mutex again:
command = item.front().command;
nullSlot = !item.front().slot;
// we take ownership of the assuan transaction
std::swap(assuanTransaction, item.front().assuanTransaction);
oldCards = m_cardInfos;
}
......@@ -607,7 +616,11 @@ private:
}
} else {
GpgME::Error err;
(void)gpgagent_transact(gpgAgent, command.constData(), err);
if (assuanTransaction) {
(void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr<AssuanTransaction>(assuanTransaction), err);
} else {
(void)gpgagent_default_transact(gpgAgent, command.constData(), err);
}
KDAB_SYNCHRONIZED(m_mutex)
// splice 'item' into m_finishedTransactions:
......@@ -742,7 +755,13 @@ std::vector<Card::PinState> ReaderStatus::pinStates(unsigned int slot) const
void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot)
{
const Transaction t = { command, receiver, slot };
const Transaction t = { command, receiver, slot, nullptr };
d->addTransaction(t);
}
void ReaderStatus::startTransaction(const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr<AssuanTransaction> transaction)
{
const Transaction t = { command, receiver, slot, transaction.release() };
d->addTransaction(t);
}
......
......@@ -20,6 +20,11 @@
#include "kleopatra_debug.h"
namespace GpgME
{
class AssuanTransaction;
}
namespace Kleo
{
namespace SmartCard
......@@ -36,6 +41,7 @@ public:
static ReaderStatus *mutableInstance();
void startSimpleTransaction(const QByteArray &cmd, QObject *receiver, const char *slot);
void startTransaction(const QByteArray &cmd, QObject *receiver, const char *slot, std::unique_ptr<GpgME::AssuanTransaction> transaction);
Card::Status cardStatus(unsigned int slot) const;
bool anyCardHasNullPin() const;
......
/* utils/writecertassuantransaction.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 "writecertassuantransaction.h"
#include <QByteArray>
#include <gpgme++/data.h>
#include <string.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace GpgME;
WriteCertAssuanTransaction::WriteCertAssuanTransaction(const QByteArray &certificateData)
: DefaultAssuanTransaction()
, mCertData(certificateData.constData(), certificateData.size())
{
}
WriteCertAssuanTransaction::~WriteCertAssuanTransaction()
{
}
namespace {
static bool startsWithKeyword(const char *string, const char *keyword)
{
// simplified version of has_leading_keyword() in gnupg/common/stringhelp.c
if (!string || !keyword) {
return false;
}
const size_t n = strlen(keyword);
return !strncmp(string, keyword, n) && (!string[n] || string[n] == ' ' || string[n] == '\t');
}
}
Data WriteCertAssuanTransaction::inquire(const char *name, const char *args, Error &err)
{
(void)args; (void)err;
qCDebug(KLEOPATRA_LOG) << "WriteCertAssuanTransaction::inquire() - name:" << name;
if (startsWithKeyword(name, "CERTDATA")) {
return mCertData;
} else {
return Data::null;
}
}
/* utils/writecertassuantransaction.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_WRITECERTASSUANTRANSACTION_H__
#define __KLEOPATRA_WRITECERTASSUANTRANSACTION_H__
#include <gpgme++/data.h>
#include <gpgme++/defaultassuantransaction.h>
class QByteArray;
namespace Kleo
{
class WriteCertAssuanTransaction: public GpgME::DefaultAssuanTransaction
{
public:
explicit WriteCertAssuanTransaction(const QByteArray &certificateData);
~WriteCertAssuanTransaction();
private:
GpgME::Data inquire(const char *name, const char *args, GpgME::Error &err) override;
private:
GpgME::Data mCertData;
};
} // namespace Kleo
#endif // __KLEOPATRA_WRITECERTASSUANTRANSACTION_H__
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