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

Add basic support for PIV smartcards

Display app version, serial number, and keys (for now key grips only)
for PIV smartcards (e.g. some Yubikeys).

GnuPG-bug-id: 4794
parent 9b981d65
Pipeline #30988 passed with stage
in 15 minutes and 28 seconds
......@@ -104,6 +104,7 @@ set(_kleopatra_SRCS
view/smartcardwidget.cpp
view/padwidget.cpp
view/pgpcardwidget.cpp
view/pivcardwidget.cpp
view/netkeywidget.cpp
view/nullpinwidget.cpp
view/tabwidget.cpp
......@@ -237,6 +238,7 @@ set(_kleopatra_SRCS
smartcard/card.cpp
smartcard/openpgpcard.cpp
smartcard/netkeycard.cpp
smartcard/pivcard.cpp
aboutdata.cpp
systrayicon.cpp
......
......@@ -25,6 +25,7 @@ public:
enum AppType {
UnknownApplication,
OpenPGPApplication,
PivApplication,
NksApplication,
P15Application,
DinSigApplication,
......
/* smartcard/pivcard.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 "pivcard.h"
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
PIVCard::PIVCard()
{
setAppType(Card::PivApplication);
}
PIVCard::PIVCard(const std::string &serialno): PIVCard()
{
setSerialNumber(serialno);
}
std::string PIVCard::pivAuthenticationKeyGrip() const
{
return mMetaInfo.value("PIV-AUTH-KEYPAIRINFO");
}
std::string PIVCard::cardAuthenticationKeyGrip() const
{
return mMetaInfo.value("CARD-AUTH-KEYPAIRINFO");
}
std::string PIVCard::digitalSignatureKeyGrip() const
{
return mMetaInfo.value("SIG-KEYPAIRINFO");
}
std::string PIVCard::keyManagementKeyGrip() const
{
return mMetaInfo.value("ENC-KEYPAIRINFO");
}
namespace {
static int parseAppVersion(const std::string &s) {
// s is a hex-encoded, unsigned int-packed version tuple
bool ok;
const auto appVersion = QByteArray::fromStdString(s).toUInt(&ok, 16);
return ok ? appVersion : -1;
}
}
void PIVCard::setCardInfo(const std::vector< std::pair<std::string, std::string> > &infos)
{
qCDebug(KLEOPATRA_LOG) << "Card" << serialNumber().c_str() << "info:";
for (const auto &pair: infos) {
qCDebug(KLEOPATRA_LOG) << pair.first.c_str() << ":" << pair.second.c_str();
if (pair.first == "APPVERSION") {
setAppVersion(parseAppVersion(pair.second));
} else if (pair.first == "KEYPAIRINFO") {
const auto values = QString::fromStdString(pair.second).split(QLatin1Char(' '));
if (values.size() != 3) {
qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO entry" << QString::fromStdString(pair.second);
setStatus(Card::CardError);
continue;
}
const auto grip = values[0].toStdString();
const auto slotId = values[1];
const auto usage = values[2];
if (slotId == QLatin1String("PIV.9A")) {
mMetaInfo.insert(std::string("PIV-AUTH-") + pair.first, grip);
} else if (slotId == QLatin1String("PIV.9E")) {
mMetaInfo.insert(std::string("CARD-AUTH-") + pair.first, grip);
} else if (slotId == QLatin1String("PIV.9C")) {
mMetaInfo.insert(std::string("SIG-") + pair.first, grip);
} else if (slotId == QLatin1String("PIV.9D")) {
mMetaInfo.insert(std::string("ENC-") + pair.first, grip);
} else {
qCDebug(KLEOPATRA_LOG) << "Unhandled keyslot";
}
} else {
mMetaInfo.insert(pair.first, pair.second);
}
}
}
std::string PIVCard::displaySerialNumber() const
{
return mDisplaySerialNumber;
}
void PIVCard::setDisplaySerialNumber(const std::string &serialno)
{
mDisplaySerialNumber = serialno;
}
bool PIVCard::operator == (const Card& rhs) const
{
const PIVCard *other = dynamic_cast<const PIVCard *>(&rhs);
if (!other) {
return false;
}
return Card::operator ==(rhs)
&& pivAuthenticationKeyGrip() == other->pivAuthenticationKeyGrip()
&& cardAuthenticationKeyGrip() == other->cardAuthenticationKeyGrip()
&& digitalSignatureKeyGrip() == other->digitalSignatureKeyGrip()
&& keyManagementKeyGrip() == other->keyManagementKeyGrip();
}
/* smartcard/pivcard.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 SMARTCARD_PIVCARD_H
#define SMARTCARD_PIVCARD_H
#include <QMap>
#include "card.h"
namespace Kleo
{
namespace SmartCard
{
/** Class to work with PIV smartcards or compatible tokens */
class PIVCard: public Card
{
public:
PIVCard ();
PIVCard (const std::string &serialno);
std::string pivAuthenticationKeyGrip() const;
std::string cardAuthenticationKeyGrip() const;
std::string digitalSignatureKeyGrip() const;
std::string keyManagementKeyGrip() const;
void setCardInfo (const std::vector< std::pair<std::string, std::string> > &infos);
std::string displaySerialNumber() const;
void setDisplaySerialNumber(const std::string &sn);
bool operator == (const Card& other) const override;
private:
std::string mDisplaySerialNumber;
QMap <std::string, std::string> mMetaInfo;
};
} // namespace Smartcard
} // namespace Kleopatra
#endif // SMARTCARD_PIVCARD_H
......@@ -3,6 +3,8 @@
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -25,6 +27,7 @@
#include "kleopatra_debug.h"
#include "openpgpcard.h"
#include "netkeycard.h"
#include "pivcard.h"
#include <QStringList>
#include <QFileInfo>
......@@ -104,6 +107,7 @@ static QDebug operator<<(QDebug s, const std::vector< std::pair<std::string, std
static const char *app_types[] = {
"_", // will hopefully never be used as an app-type :)
"openpgp",
"piv",
"nks",
"p15",
"dinsig",
......@@ -123,7 +127,6 @@ static Card::AppType parse_app_type(const std::string &s)
return Card::UnknownApplication;
}
return static_cast<Card::AppType>(it - std::begin(app_types));
}
static int parse_app_version(const std::string &s)
......@@ -287,6 +290,28 @@ static void handle_openpgp_card(std::shared_ptr<Card> &ci, std::shared_ptr<Conte
ci.reset(ret);
}
static void handle_piv_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
auto pivCard = new PIVCard();
pivCard->setSerialNumber(ci->serialNumber());
const auto displaySerialnumber = scd_getattr_status(gpg_agent, "$DISPSERIALNO", err);
if (err.code()) {
qCWarning(KLEOPATRA_LOG) << "handle_piv_card(): Error on GETATTR $DISPSERIALNO:"
<< "GpgME::Error(" << err.encodedError() << " (" << err.asString() << "))";
}
pivCard->setDisplaySerialNumber(err.code() ? ci->serialNumber() : displaySerialnumber);
const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err);
if (err.code()) {
ci->setStatus(Card::CardError);
return;
}
pivCard->setCardInfo(info);
ci.reset(pivCard);
}
static void handle_netkey_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent)
{
Error err;
......@@ -388,6 +413,10 @@ static std::shared_ptr<Card> get_card_status(unsigned int slot, std::shared_ptr<
qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end";
handle_openpgp_card(ci, gpg_agent);
return ci;
} else if (ci->appType() == Card::PivApplication) {
qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end";
handle_piv_card(ci, gpg_agent);
return ci;
} else {
qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << verbatimType.c_str();
return ci;
......
/* view/pivcardwiget.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 "pivcardwidget.h"
#include "smartcard/pivcard.h"
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>
#include <KLocalizedString>
using namespace Kleo;
using namespace Kleo::SmartCard;
namespace {
static QString formatVersion(int value)
{
if (value < 0) {
return QLatin1String("n/a");
}
const unsigned int a = ((value >> 24) & 0xff);
const unsigned int b = ((value >> 16) & 0xff);
const unsigned int c = ((value >> 8) & 0xff);
const unsigned int d = ((value ) & 0xff);
if (a) {
return QStringLiteral("%1.%2.%3.%4").arg(QString::number(a), QString::number(b), QString::number(c), QString::number(d));
} else if (b) {
return QStringLiteral("%1.%2.%3").arg(QString::number(b), QString::number(c), QString::number(d));
} else if (c) {
return QStringLiteral("%1.%2").arg(QString::number(c), QString::number(d));
}
return QString::number(d);
}
} // Namespace
PIVCardWidget::PIVCardWidget(QWidget *parent):
QWidget(parent),
mSerialNumber(new QLabel(this)),
mVersionLabel(new QLabel(this)),
mPivAuthenticationKey(new QLabel(this)),
mCardAuthenticationKey(new QLabel(this)),
mSigningKey(new QLabel(this)),
mEncryptionKey(new QLabel(this)),
mCardIsEmpty(false)
{
auto grid = new QGridLayout;
int row = 0;
// Set up the scroll are
auto area = new QScrollArea;
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
auto areaWidget = new QWidget;
auto areaVLay = new QVBoxLayout(areaWidget);
areaVLay->addLayout(grid);
areaVLay->addStretch(1);
area->setWidget(areaWidget);
auto myLayout = new QVBoxLayout(this);
myLayout->addWidget(area);
// Version and Serialnumber
grid->addWidget(mVersionLabel, row++, 0, 1, 2);
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
grid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
grid->addWidget(mSerialNumber, row++, 1);
mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction);
// The keys
auto line1 = new QFrame();
line1->setFrameShape(QFrame::HLine);
grid->addWidget(line1, row++, 0, 1, 4);
grid->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:"))), row++, 0);
grid->addWidget(new QLabel(i18n("PIV authentication:")), row, 0);
grid->addWidget(mPivAuthenticationKey, row++, 1);
mPivAuthenticationKey->setTextInteractionFlags(Qt::TextBrowserInteraction);
grid->addWidget(new QLabel(i18n("Card authentication:")), row, 0);
grid->addWidget(mCardAuthenticationKey, row++, 1);
mCardAuthenticationKey->setTextInteractionFlags(Qt::TextBrowserInteraction);
grid->addWidget(new QLabel(i18n("Digital signature:")), row, 0);
grid->addWidget(mSigningKey, row++, 1);
mSigningKey->setTextInteractionFlags(Qt::TextBrowserInteraction);
grid->addWidget(new QLabel(i18n("Key management:")), row, 0);
grid->addWidget(mEncryptionKey, row++, 1);
mEncryptionKey->setTextInteractionFlags(Qt::TextBrowserInteraction);
auto line2 = new QFrame();
line2->setFrameShape(QFrame::HLine);
grid->addWidget(line2, row++, 0, 1, 4);
grid->setColumnStretch(4, -1);
}
void PIVCardWidget::setCard(const PIVCard *card)
{
mVersionLabel->setText(i18nc("Placeholder is a version number", "PIV v%1 card", formatVersion(card->appVersion())));
if (card->displaySerialNumber() != card->serialNumber()) {
mSerialNumber->setText(QStringLiteral("%1 (%2)").arg(QString::fromStdString(card->displaySerialNumber()),
QString::fromStdString(card->serialNumber())));
} else {
mSerialNumber->setText(QString::fromStdString(card->serialNumber()));
}
updateKey(mPivAuthenticationKey, card->pivAuthenticationKeyGrip());
updateKey(mCardAuthenticationKey, card->cardAuthenticationKeyGrip());
updateKey(mSigningKey, card->digitalSignatureKeyGrip());
updateKey(mEncryptionKey, card->keyManagementKeyGrip());
mCardIsEmpty = card->pivAuthenticationKeyGrip().empty()
&& card->cardAuthenticationKeyGrip().empty()
&& card->digitalSignatureKeyGrip().empty()
&& card->keyManagementKeyGrip().empty();
}
void PIVCardWidget::updateKey(QLabel *label, const std::string &grip)
{
label->setText(QString::fromStdString(grip));
if (grip.empty()) {
label->setText(i18n("Slot empty"));
return;
}
}
/* view/pivcardwiget.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 VIEW_PIVCARDWIDGET_H
#define VIEW_PIVCARDWIDGET_H
#include <QWidget>
class QLabel;
namespace Kleo
{
class GenCardKeyDialog;
namespace SmartCard
{
class PIVCard;
} // namespace SmartCard
class PIVCardWidget: public QWidget
{
Q_OBJECT
public:
explicit PIVCardWidget(QWidget *parent = nullptr);
void setCard(const SmartCard::PIVCard* card);
private:
void updateKey(QLabel *label, const std::string &fpr);
QLabel *mSerialNumber = nullptr,
*mVersionLabel = nullptr,
*mPivAuthenticationKey = nullptr,
*mCardAuthenticationKey = nullptr,
*mSigningKey = nullptr,
*mEncryptionKey = nullptr;
bool mCardIsEmpty = false;
};
} // namespace Kleo
#endif // VIEW_PIVCARDWIDGET_H
......@@ -11,8 +11,10 @@
#include "smartcard/readerstatus.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/netkeycard.h"
#include "smartcard/pivcard.h"
#include "view/pgpcardwidget.h"
#include "view/netkeywidget.h"
#include "view/pivcardwidget.h"
#include "kleopatra_debug.h"
......@@ -40,7 +42,8 @@ public:
const QStringList supported = QStringList() << QStringLiteral("OpenPGP v2.0 - v3.3")
<< QStringLiteral("Gnuk")
<< QStringLiteral("NetKey v3");
<< QStringLiteral("NetKey v3")
<< QStringLiteral("PIV");
lay->addWidget(new QLabel(QStringLiteral("\t\t<h3>") +
i18n("Please insert a compatible smartcard.") + QStringLiteral("</h3>"), this));
lay->addSpacing(10);
......@@ -89,6 +92,9 @@ public:
mNetKeyWidget = new NetKeyWidget(q);
mStack->addWidget(mNetKeyWidget);
mPIVCardWidget = new PIVCardWidget(q);
mStack->addWidget(mPIVCardWidget);
mPlaceHolderWidget = new PlaceHolderWidget(q);
mStack->addWidget(mPlaceHolderWidget);
......@@ -113,6 +119,9 @@ public:
} else if (card->appType() == Card::NksApplication) {
mNetKeyWidget->setCard(static_cast<NetKeyCard *> (card.get()));
mStack->setCurrentWidget(mNetKeyWidget);
} else if (card->appType() == Card::PivApplication) {
mPIVCardWidget->setCard(static_cast<PIVCard *> (card.get()));
mStack->setCurrentWidget(mPIVCardWidget);
} else {
mStack->setCurrentWidget(mPlaceHolderWidget);
}
......@@ -122,6 +131,7 @@ private:
SmartCardWidget *const q;
NetKeyWidget *mNetKeyWidget;
PGPCardWidget *mPGPCardWidget;
PIVCardWidget *mPIVCardWidget;
PlaceHolderWidget *mPlaceHolderWidget;
QStackedWidget *mStack;
};
......
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