diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 8e5977916..09b94b2ee 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,499 +1,559 @@ /* commands/setinitialpincommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytocardcommand.h" #include "kleopatra_debug.h" #include "command_p.h" #include "smartcard/readerstatus.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "commands/authenticatepivcardapplicationcommand.h" #include "utils/writecertassuantransaction.h" #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class KeyToCardCommand::Private : public Command::Private { friend class ::Kleo::Commands::KeyToCardCommand; KeyToCardCommand *q_func() const { return static_cast(q); } public: explicit Private(KeyToCardCommand *qq, KeyListController *c); explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &key, const std::string &serialno); ~Private(); private: void start(); void startTransferToOpenPGPCard(); void startTransferToPIVCard(); + void startKeyToPIVCard(); void startCertificateToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); private: std::string mSerial; GpgME::Subkey mSubkey; + std::string cardSlot; bool overwriteExistingAlreadyApproved = false; bool hasBeenCanceled = false; }; KeyToCardCommand::Private *KeyToCardCommand::d_func() { return static_cast(d.get()); } const KeyToCardCommand::Private *KeyToCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() KeyToCardCommand::Private::Private(KeyToCardCommand *qq, KeyListController *c) : Command::Private(qq, c) { } KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &key, const std::string &serialno) : Command::Private(qq, nullptr), mSerial(serialno), mSubkey(key) { } KeyToCardCommand::Private::~Private() { } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; const auto card = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } switch (card->appType()) { case SmartCard::Card::OpenPGPApplication: { if (mSubkey.parent().protocol() == GpgME::OpenPGP) { startTransferToOpenPGPCard(); } else { error(i18n("Sorry! This key cannot be transferred to an OpenPGP card.")); finished(); } } break; case SmartCard::Card::PivApplication: { if (mSubkey.parent().protocol() == GpgME::CMS) { startTransferToPIVCard(); } else { error(i18n("Sorry! This key cannot be transferred to a PIV card.")); finished(); } } break; default: { error(i18n("Sorry! Transferring keys to this card is not supported.")); finished(); } } } namespace { static int getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) { // Signing only return 1; } if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) { // Encrypt only return 2; } if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) { // Auth only return 3; } // Multiple uses, ask user. QStringList options; if (subKey.canSign() || subKey.canCertify()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Signature (%1)", 1); } if (subKey.canEncrypt()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Encryption (%1)", 2); } if (subKey.canAuthenticate()) { options << i18nc("Placeholder is the number of a slot on a smart card", "Authentication (%1)", 3); } bool ok; - const QString choice = QInputDialog::getItem(parent, i18n("Select Slot"), - i18n("Please select the slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); + const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), + i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); const int slot = options.indexOf(choice) + 1; return ok ? slot : -1; } } void KeyToCardCommand::Private::startTransferToOpenPGPCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToOpenPGPCard()"; const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } const auto slot = getOpenPGPCardSlotForKey(mSubkey, parentWidgetOrView()); if (slot < 1) { finished(); return; } // Check if we need to do the overwrite warning. std::string existingKey; QString encKeyWarning; if (slot == 1) { existingKey = pgpCard->sigFpr(); } else if (slot == 2) { existingKey = pgpCard->encFpr(); encKeyWarning = i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."); } else if (slot == 3) { existingKey = pgpCard->authFpr(); } if (!existingKey.empty()) { const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the fingerprint:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + encKeyWarning; 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; } } // Now do the deed const auto time = QDateTime::fromSecsSinceEpoch(mSubkey.creationTime()); const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss")); const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 OPENPGP.%3 %4") .arg(QString::fromLatin1(mSubkey.keyGrip()), QString::fromStdString(mSerial)) .arg(slot) .arg(timestamp); ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), q_func(), "keyToOpenPGPCardDone"); } +namespace { +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 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() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startTransferToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(mSerial); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(mSerial))); finished(); return; } - if (!mSubkey.canEncrypt()) { - error(i18n("Sorry! Only encryption keys can be transferred to a PIV card.")); + if (!mSubkey.canEncrypt() && !mSubkey.canSign()) { + error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card.")); + finished(); + 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(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 std::string existingKey = pivCard->keyGrip(cardSlot); + if (!existingKey.empty() && (existingKey != mSubkey.keyGrip())) { + const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ? + i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : + QString(); const QString message = i18nc("@info", "

This card already contains a key in this slot. Continuing will overwrite that key.

" "

If there is no backup the existing key will be irrecoverably lost.

") + i18n("The existing key has the key grip:") + QStringLiteral("
%1
").arg(QString::fromStdString(existingKey)) + - i18n("It will no longer be possible to decrypt past communication encrypted for the existing key."); + decryptionWarning; 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; } } - // Now do the deed const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3") .arg(QString::fromLatin1(mSubkey.keyGrip()), QString::fromStdString(mSerial)) - .arg(QString::fromStdString(PIVCard::keyManagementKeyRef())); + .arg(QString::fromStdString(cardSlot)); 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(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", - "

This card already contains a key in this slot. Continuing will overwrite that key.

" - "

If there is no backup the existing key will be irrecoverably lost.

") + - i18n("The existing key has the key grip:") + - QStringLiteral("
%1
").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; - } + // 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("

The certificate does not seem to correspond to the key on the card.

" + "

Public key on card: %1
" + "Public key of certificate: %2

", + 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(); - // Now do the deed const QString cmd = QStringLiteral("SCD WRITECERT %1") - .arg(QString::fromStdString(PIVCard::keyManagementKeyRef())); + .arg(QString::fromStdString(cardSlot)); auto transaction = std::unique_ptr(new WriteCertAssuanTransaction(certificateData)); ReaderStatus::mutableInstance()->startTransaction(cmd.toUtf8(), q_func(), "certificateToPIVCardDone", std::move(transaction)); } void KeyToCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(mSerial, parentWidgetOrView()); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void KeyToCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startTransferToPIVCard(); } } void KeyToCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } KeyToCardCommand::KeyToCardCommand(KeyListController *c) : Command(new Private(this, c)) { } KeyToCardCommand::KeyToCardCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { } KeyToCardCommand::KeyToCardCommand(const GpgME::Key &key) : Command(key, new Private(this, nullptr)) { } KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &key, const std::string &serialno) : Command(new Private(this, key, serialno)) { } KeyToCardCommand::~KeyToCardCommand() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()"; } bool KeyToCardCommand::supported() { return true; } void KeyToCardCommand::keyToOpenPGPCardDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Moving the key to the card failed: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } else if (!err.isCanceled()) { /* TODO DELETE_KEY is too strong, because it also deletes the stub * of the secret key. I could not find out how GnuPG does this. Question * to GnuPG Developers is pending an answer if (KMessageBox::questionYesNo(d->parentWidgetOrView(), i18n("Do you want to delete the key on this computer?"), i18nc("@title:window", "Key transferred to card")) == KMessageBox::Yes) { const QString cmd = QStringLiteral("DELETE_KEY --force %1").arg(d->mSubkey.keyGrip()); // Using readerstatus is a bit overkill but it's an easy way to talk to the agent. ReaderStatus::mutableInstance()->startSimpleTransaction(cmd.toUtf8(), this, "deleteDone"); } */ KMessageBox::information(d->parentWidgetOrView(), i18n("Successfully copied the key to the card."), i18nc("@title", "Success")); } d->finished(); } 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) { d->authenticate(); return; } 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 certificate to the card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } d->finished(); } void KeyToCardCommand::deleteDone(const GpgME::Error &err) { if (err) { d->error(i18nc("@info", "Failed to delete the key: %1", QString::fromUtf8(err.asString())), i18nc("@title", "Error")); } d->finished(); } void KeyToCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()"; if (d->mSubkey.isNull()) { const std::vector keys = d->keys(); if (keys.size() != 1 || !keys.front().hasSecret() || keys.front().subkey(0).isNull()) { d->finished(); return; } d->mSubkey = keys.front().subkey(0); } if (d->mSerial.empty()) { const auto cards = SmartCard::ReaderStatus::instance()->getCards(); if (!cards.size() || cards[0]->serialNumber().empty()) { d->error(i18n("Failed to find a smart card.")); d->finished(); return; } d->mSerial = cards[0]->serialNumber(); } d->start(); } void KeyToCardCommand::doCancel() { } #undef q_func #undef d_func diff --git a/src/smartcard/pivcard.cpp b/src/smartcard/pivcard.cpp index b33cb375d..be0852a51 100644 --- a/src/smartcard/pivcard.cpp +++ b/src/smartcard/pivcard.cpp @@ -1,146 +1,159 @@ /* smartcard/pivcard.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 "pivcard.h" #include #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); } // static std::string PIVCard::pivAuthenticationKeyRef() { return std::string("PIV.9A"); } // static std::string PIVCard::cardAuthenticationKeyRef() { return std::string("PIV.9E"); } // static std::string PIVCard::digitalSignatureKeyRef() { return std::string("PIV.9C"); } // static std::string PIVCard::keyManagementKeyRef() { return std::string("PIV.9D"); } // static std::string PIVCard::pinKeyRef() { return std::string("PIV.80"); } // static std::string PIVCard::pukKeyRef() { return std::string("PIV.81"); } +// static +QString PIVCard::keyDisplayName(const std::string &keyRef) +{ + static const QMap displayNames = { + { PIVCard::pivAuthenticationKeyRef(), i18n("PIV Authentication Key") }, + { PIVCard::cardAuthenticationKeyRef(), i18n("Card Authentication Key") }, + { PIVCard::digitalSignatureKeyRef(), i18n("Digital Signature Key") }, + { PIVCard::keyManagementKeyRef(), i18n("Key Management Key") }, + }; + + return displayNames.value(keyRef); +} + // static std::vector< std::pair > PIVCard::supportedAlgorithms(const std::string &keyRef) { if (keyRef == PIVCard::keyManagementKeyRef()) { return { { "rsa2048", i18n("RSA key transport (2048 bits)") }, { "nistp256", i18n("ECDH (Curve P-256)") }, { "nistp384", i18n("ECDH (Curve P-384)") } }; } else if (keyRef == PIVCard::digitalSignatureKeyRef()) { return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, { "nistp384", i18n("ECDSA (Curve P-384)") } }; } // NIST SP 800-78-4 does not allow Curve P-384 for PIV Authentication key or Card Authentication key return { { "rsa2048", i18n("RSA (2048 bits)") }, { "nistp256", i18n("ECDSA (Curve P-256)") }, }; } std::string PIVCard::keyGrip(const std::string& keyRef) const { return mMetaInfo.value("KEYPAIRINFO-" + keyRef); } 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 > &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 keyRef = values[1].toStdString(); //const auto usage = values[2]; mMetaInfo.insert("KEYPAIRINFO-" + keyRef, grip); } 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(&rhs); if (!other) { return false; } return Card::operator ==(rhs) && mMetaInfo == other->mMetaInfo; } diff --git a/src/smartcard/pivcard.h b/src/smartcard/pivcard.h index 76f5c0865..9e27b93d9 100644 --- a/src/smartcard/pivcard.h +++ b/src/smartcard/pivcard.h @@ -1,53 +1,54 @@ /* smartcard/pivcard.h 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 */ #ifndef SMARTCARD_PIVCARD_H #define SMARTCARD_PIVCARD_H #include #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); static std::string pivAuthenticationKeyRef(); static std::string cardAuthenticationKeyRef(); static std::string digitalSignatureKeyRef(); static std::string keyManagementKeyRef(); static std::string pinKeyRef(); static std::string pukKeyRef(); + static QString keyDisplayName(const std::string &keyRef); static std::vector< std::pair > supportedAlgorithms(const std::string &keyRef); std::string keyGrip(const std::string &keyRef) const; void setCardInfo (const std::vector< std::pair > &infos); std::string displaySerialNumber() const; void setDisplaySerialNumber(const std::string &sn); bool operator == (const Card& other) const override; private: std::string mDisplaySerialNumber; QMap mMetaInfo; }; } // namespace Smartcard } // namespace Kleopatra #endif // SMARTCARD_PIVCARD_H diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp index 7b9a4f145..e0104dd82 100644 --- a/src/view/pivcardwidget.cpp +++ b/src/view/pivcardwidget.cpp @@ -1,264 +1,252 @@ /* view/pivcardwiget.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 "pivcardwidget.h" #include "commands/changepincommand.h" #include "commands/pivgeneratecardkeycommand.h" #include "commands/setpivcardapplicationadministrationkeycommand.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; 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); } - -static QString keyDisplayName(std::string keyRef) -{ - static const QMap displayNames = { - { PIVCard::pivAuthenticationKeyRef(), i18n("PIV Authentication Key") }, - { PIVCard::cardAuthenticationKeyRef(), i18n("Card Authentication Key") }, - { PIVCard::digitalSignatureKeyRef(), i18n("Digital Signature Key") }, - { PIVCard::keyManagementKeyRef(), i18n("Key Management Key") }, - }; - - return displayNames.value(keyRef); -} } // Namespace PIVCardWidget::PIVCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mVersionLabel(new QLabel(this)), mPIVAuthenticationKey(new QLabel(this)), mCardAuthenticationKey(new QLabel(this)), mDigitalSignatureKey(new QLabel(this)), mKeyManagementKey(new QLabel(this)), mGeneratePIVAuthenticationKeyBtn(new QPushButton(this)), mGenerateCardAuthenticationKeyBtn(new QPushButton(this)), mGenerateDigitalSignatureKeyBtn(new QPushButton(this)), mGenerateKeyManagementKeyBtn(new QPushButton(this)) { 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("%1").arg(i18n("Keys:"))), row++, 0); grid->addWidget(new QLabel(i18n("PIV authentication:")), row, 0); grid->addWidget(mPIVAuthenticationKey, row, 1); mPIVAuthenticationKey->setTextInteractionFlags(Qt::TextBrowserInteraction); mGeneratePIVAuthenticationKeyBtn->setText(i18n("Generate")); mGeneratePIVAuthenticationKeyBtn->setEnabled(false); grid->addWidget(mGeneratePIVAuthenticationKeyBtn, row, 2); connect(mGeneratePIVAuthenticationKeyBtn, &QPushButton::clicked, this, &PIVCardWidget::generatePIVAuthenticationKey); row++; grid->addWidget(new QLabel(i18n("Card authentication:")), row, 0); grid->addWidget(mCardAuthenticationKey, row, 1); mCardAuthenticationKey->setTextInteractionFlags(Qt::TextBrowserInteraction); mGenerateCardAuthenticationKeyBtn->setText(i18n("Generate")); mGeneratePIVAuthenticationKeyBtn->setEnabled(false); grid->addWidget(mGenerateCardAuthenticationKeyBtn, row, 2); connect(mGenerateCardAuthenticationKeyBtn, &QPushButton::clicked, this, &PIVCardWidget::generateCardAuthenticationKey); row++; grid->addWidget(new QLabel(i18n("Digital signature:")), row, 0); grid->addWidget(mDigitalSignatureKey, row, 1); mDigitalSignatureKey->setTextInteractionFlags(Qt::TextBrowserInteraction); mGenerateDigitalSignatureKeyBtn->setText(i18n("Generate")); mGeneratePIVAuthenticationKeyBtn->setEnabled(false); grid->addWidget(mGenerateDigitalSignatureKeyBtn, row, 2); connect(mGenerateDigitalSignatureKeyBtn, &QPushButton::clicked, this, &PIVCardWidget::generateDigitalSignatureKey); row++; grid->addWidget(new QLabel(i18n("Key management:")), row, 0); grid->addWidget(mKeyManagementKey, row, 1); mKeyManagementKey->setTextInteractionFlags(Qt::TextBrowserInteraction); mGenerateKeyManagementKeyBtn->setText(i18n("Generate")); mGeneratePIVAuthenticationKeyBtn->setEnabled(false); grid->addWidget(mGenerateKeyManagementKeyBtn, row, 2); connect(mGenerateKeyManagementKeyBtn, &QPushButton::clicked, this, &PIVCardWidget::generateKeyManagementKey); row++; auto line2 = new QFrame(); line2->setFrameShape(QFrame::HLine); grid->addWidget(line2, row++, 0, 1, 4); auto actionLayout = new QHBoxLayout; { auto button = new QPushButton(i18n("Change PIN")); button->setToolTip(i18n("Change the PIV Card Application PIN that activates the PIV Card and enables private key operations using the stored keys.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pinKeyRef()); }); } { auto button = new QPushButton(i18n("Change PUK")); button->setToolTip(i18n("Change the PIN Unblocking Key that enables a reset of the PIN.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pukKeyRef()); }); } { auto button = new QPushButton(i18n("Change Admin Key")); button->setToolTip(i18n("Change the PIV Card Application Administration Key that is used by the " "PIV Card Application to authenticate the PIV Card Application Administrator and by the " "administrator (resp. Kleopatra) to authenticate the PIV Card Application.")); actionLayout->addWidget(button); connect(button, &QPushButton::clicked, this, [this] () { setAdminKey(); }); } actionLayout->addStretch(-1); grid->addLayout(actionLayout, row++, 0, 1, 4); grid->setColumnStretch(4, -1); } PIVCardWidget::~PIVCardWidget() { } void PIVCardWidget::setCard(const PIVCard *card) { mCardSerialNumber = card->serialNumber(); 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(PIVCard::pivAuthenticationKeyRef(), card, mPIVAuthenticationKey, mGeneratePIVAuthenticationKeyBtn); updateKey(PIVCard::cardAuthenticationKeyRef(), card, mCardAuthenticationKey, mGenerateCardAuthenticationKeyBtn); updateKey(PIVCard::digitalSignatureKeyRef(), card, mDigitalSignatureKey, mGenerateDigitalSignatureKeyBtn); updateKey(PIVCard::keyManagementKeyRef(), card, mKeyManagementKey, mGenerateKeyManagementKeyBtn); } void PIVCardWidget::updateKey(const std::string &keyRef, const PIVCard *card, QLabel *label, QPushButton *button) { const std::string grip = card->keyGrip(keyRef); label->setText(grip.empty() ? i18n("Slot empty") : QString::fromStdString(grip)); button->setText(grip.empty() ? i18n("Generate") : i18n("Replace")); button->setToolTip(grip.empty() ? - i18nc("Placeholder is the display name of a key", "Generate %1", keyDisplayName(keyRef)) : - i18nc("Placeholder is the display name of a key", "Replace %1 with new key", keyDisplayName(keyRef))); + i18nc("Placeholder is the display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef)) : + i18nc("Placeholder is the display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef))); button->setEnabled(true); } void PIVCardWidget::generateKey(const std::string &keyref) { auto cmd = new PIVGenerateCardKeyCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &PIVGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyref); cmd->start(); } void PIVCardWidget::generatePIVAuthenticationKey() { generateKey(PIVCard::pivAuthenticationKeyRef()); } void PIVCardWidget::generateCardAuthenticationKey() { generateKey(PIVCard::cardAuthenticationKeyRef()); } void PIVCardWidget::generateDigitalSignatureKey() { generateKey(PIVCard::digitalSignatureKeyRef()); } void PIVCardWidget::generateKeyManagementKey() { generateKey(PIVCard::keyManagementKeyRef()); } void PIVCardWidget::changePin(const std::string &keyRef) { auto cmd = new ChangePinCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->start(); } void PIVCardWidget::setAdminKey() { auto cmd = new SetPIVCardApplicationAdministrationKeyCommand(mCardSerialNumber, this); this->setEnabled(false); connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); }