diff --git a/src/gpgcardgui/CMakeLists.txt b/src/gpgcardgui/CMakeLists.txt index ecfb50f..b1d3a56 100644 --- a/src/gpgcardgui/CMakeLists.txt +++ b/src/gpgcardgui/CMakeLists.txt @@ -1,34 +1,38 @@ # Copyright (C) 2018 Intevation GmbH # # This file is Free Software under the GNU GPL (v>=2) # and comes with ABSOLUTELY NO WARRANTY! # See LICENSE.txt for details. set(EXECUTABLE_NAME "gpgcardgui") set(EXECUTABLE_SRC main.cpp gpgcardgui.cpp mainwindow.cpp tabbedcarddialog.cpp + gpgcardwidget.cpp + netkeywidget.cpp + nullpinwidget.cpp + pgpcardwidget.cpp ${CMAKE_SOURCE_DIR}/src/util/strhelp.c ${CMAKE_SOURCE_DIR}/src/util/w32-gettext.c ) add_executable(${EXECUTABLE_NAME} ${_add_executable_params} ${EXECUTABLE_SRC} ) target_link_libraries(${EXECUTABLE_NAME} Qt5::Widgets KF5::Libkleo Gpgmepp QGpgme ) if (WIN32) set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS "-municode") endif(WIN32) install(TARGETS ${EXECUTABLE_NAME} DESTINATION bin) diff --git a/src/gpgcardgui/netkeycard.h b/src/gpgcardgui/netkeycard.h new file mode 100644 index 0000000..237ba0c --- /dev/null +++ b/src/gpgcardgui/netkeycard.h @@ -0,0 +1,68 @@ +#ifndef SMARTCARD_NETKEYCARD_H +#define SMARTCARD_NETKEYCARD_H +/* smartcard/openpgpcard.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + + +#include "card.h" + +#include +#include + +#include "kleo_export.h" + +namespace Kleo +{ +namespace SmartCard +{ +/** Class to work with OpenPGP Smartcards or compatible tokens */ +class KLEO_EXPORT NetKeyCard: public Card +{ +public: + NetKeyCard() = delete; + NetKeyCard (const QString &std_out); + + void setKeyPairInfo (const std::vector &infos); + + bool hasSigGNullPin() const; + bool hasNKSNullPin() const; + + std::vector keys() const; + +private: + class Private; + std::shared_ptr d; +}; +} // namespace Smartcard +} // namespace Kleopatra + +#endif // SMARTCARD_CARD_H + diff --git a/src/gpgcardgui/netkeywidget.cpp b/src/gpgcardgui/netkeywidget.cpp new file mode 100644 index 0000000..32d173b --- /dev/null +++ b/src/gpgcardgui/netkeywidget.cpp @@ -0,0 +1,248 @@ +/* view/netkeywidget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#include "netkeywidget.h" +#include "nullpinwidget.h" +#include "ui/keytreeview.h" + +#include "libkleo_debug.h" + +#include "smartcard/netkeycard.h" +#include "models/keylistmodel.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Kleo; +using namespace Kleo::SmartCard; + +NetKeyWidget::NetKeyWidget() : + mSerialNumber(new QLabel), + mVersionLabel(new QLabel), + mLearnKeysLabel(new QLabel), + mErrorLabel(new QLabel), + mNullPinWidget(new NullPinWidget()), + mLearnKeysBtn(new QPushButton), + mChangeNKSPINBtn(new QPushButton), + mChangeSigGPINBtn(new QPushButton), + mTreeView(new KeyTreeView(this)), + mArea(new QScrollArea) +{ + auto vLay = new QVBoxLayout; + + // Set up the scroll are + mArea->setFrameShape(QFrame::NoFrame); + mArea->setWidgetResizable(true); + auto mAreaWidget = new QWidget; + mAreaWidget->setLayout(vLay); + mArea->setWidget(mAreaWidget); + auto scrollLay = new QVBoxLayout(this); + scrollLay->addWidget(mArea); + + // Add general widgets + mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft); + + mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); + + auto hLay1 = new QHBoxLayout; + hLay1->addWidget(new QLabel(i18n("Serial number:"))); + hLay1->addWidget(mSerialNumber); + hLay1->addStretch(1); + vLay->addLayout(hLay1); + + vLay->addWidget(mNullPinWidget); + + + auto line1 = new QFrame(); + line1->setFrameShape(QFrame::HLine); + vLay->addWidget(line1); + vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Certificates:"))), 0, Qt::AlignLeft); + + mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card.")); + mLearnKeysBtn->setText(i18nc("@action", "Load Certificates")); + connect(mLearnKeysBtn, &QPushButton::clicked, this, [this] () { +#if 0 + mLearnKeysBtn->setEnabled(false); + auto cmd = new LearnCardKeysCommand(GpgME::CMS); + cmd->setParentWidget(this); + cmd->start(); + TODO libkleo port + auto icon = KleopatraApplication::instance()->sysTrayIcon(); + if (icon) { + icon->setLearningInProgress(true); + } + + connect(cmd, &Command::finished, this, [icon] () { + ReaderStatus::mutableInstance()->updateStatus(); + icon->setLearningInProgress(false); + }); +#endif + }); + + auto hLay2 = new QHBoxLayout; + hLay2->addWidget(mLearnKeysLabel); + hLay2->addWidget(mLearnKeysBtn); + hLay2->addStretch(1); + vLay->addLayout(hLay2); + + mErrorLabel->setVisible(false); + vLay->addWidget(mErrorLabel); + + // The certificate view + mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView)); + mTreeView->setHierarchicalView(true); + +#if 0 + TODO libkleo port: + connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this] (const QModelIndex &idx) { + const auto klm = dynamic_cast (mTreeView->view()->model()); + if (!klm) { + qCDebug(LIBKLEO_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className(); + return; + } + auto cmd = new DetailsCommand(klm->key(idx), nullptr); + cmd->setParentWidget(this); + cmd->start(); + }); + vLay->addWidget(mTreeView); +#endif + + // The action area + auto line2 = new QFrame(); + line2->setFrameShape(QFrame::HLine); + vLay->addWidget(line2); + vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), 0, Qt::AlignLeft); + + mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN")); + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); + + connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this] () { + mChangeNKSPINBtn->setEnabled(false); + doChangePin(false); + }); + connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this] () { + mChangeSigGPINBtn->setEnabled(false); + doChangePin(true); + }); + + auto hLay3 = new QHBoxLayout(); + hLay3->addWidget(mChangeNKSPINBtn); + hLay3->addWidget(mChangeSigGPINBtn); + hLay3->addStretch(1); + + vLay->addLayout(hLay3); + vLay->addStretch(1); +} + +void NetKeyWidget::setCard(const NetKeyCard* card) +{ + mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion())); + mSerialNumber->setText(card->serialNumber()); + + /* According to users of NetKey Cards it is fairly uncommon + * to use SigG Certificates at all. So it should be optional to set the pins. */ + mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/); + + mNullPinWidget->setSigGVisible(false/*card->hasSigGNullPin()*/); + mNullPinWidget->setNKSVisible(card->hasNKSNullPin()); + mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin()); + + if (card->hasSigGNullPin()) { + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", + "Set SigG PIN")); + } else { + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", + "Change SigG PIN")); + } + + mLearnKeysBtn->setEnabled(true); + mLearnKeysBtn->setVisible(card->canLearnKeys()); + mTreeView->setVisible(!card->canLearnKeys()); + mLearnKeysLabel->setVisible(card->canLearnKeys()); + + const auto errMsg = card->errorMsg(); + if (!errMsg.isEmpty()) { + mErrorLabel->setText(QStringLiteral("%1: %2").arg(i18n("Error")).arg(errMsg)); + mErrorLabel->setVisible(true); + } else { + mErrorLabel->setVisible(false); + } + + const auto keys = card->keys(); + mTreeView->setKeys(keys); +} + +void NetKeyWidget::handleResult(const GpgME::Error &err, QPushButton *btn) +{ + btn->setEnabled(true); + if (err.isCanceled()) { + return; + } + if (err) { + KMessageBox::error(this, i18nc("@info", + "Failed to set PIN: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } +} + +void NetKeyWidget::setSigGPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mChangeSigGPINBtn); +} + +void NetKeyWidget::setNksPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mChangeNKSPINBtn); +} + +void NetKeyWidget::doChangePin(bool sigG) +{ +#if 0 + TODO libkleo port: + if (sigG) { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD PW1.CH.SIG", + this, "setSigGPinSettingResult"); + } else { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD PW1.CH", + this, "setNksPinSettingResult"); + } +#endif +} diff --git a/src/gpgcardgui/netkeywidget.h b/src/gpgcardgui/netkeywidget.h new file mode 100644 index 0000000..a03f1b4 --- /dev/null +++ b/src/gpgcardgui/netkeywidget.h @@ -0,0 +1,86 @@ +/* view/netkeywidget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + 2020 g10 Code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifndef VIEW_NETKEYWIDGET_H +#define VIEW_NETKEYWIDGET_H + +#include +#include + +#include + +#include "kleo_export.h" + +class QLabel; +class QPushButton; +class QScrollArea; + +namespace Kleo +{ +class NullPinWidget; +class KeyTreeView; + +namespace SmartCard +{ +class NetKeyCard; +} // namespace SmartCard + +class KLEO_EXPORT NetKeyWidget: public QWidget +{ + Q_OBJECT +public: + NetKeyWidget(); + + void setCard(const SmartCard::NetKeyCard* card); + +private: + void handleResult(const GpgME::Error &err, QPushButton *btn); + void doChangePin(bool sigG); + +private Q_SLOTS: + void setSigGPinSettingResult(const GpgME::Error &err); + void setNksPinSettingResult(const GpgME::Error &err); + +private: + QLabel *mSerialNumber, + *mVersionLabel, + *mLearnKeysLabel, + *mErrorLabel; + NullPinWidget *mNullPinWidget; + QPushButton *mLearnKeysBtn, + *mChangeNKSPINBtn, + *mChangeSigGPINBtn; + KeyTreeView *mTreeView; + QScrollArea *mArea; +}; +} // namespace Kleo + +#endif // VIEW_NETKEYWIDGET_H diff --git a/src/gpgcardgui/pgpcardwidget.cpp b/src/gpgcardgui/pgpcardwidget.cpp new file mode 100644 index 0000000..41ba042 --- /dev/null +++ b/src/gpgcardgui/pgpcardwidget.cpp @@ -0,0 +1,543 @@ +/* smartcard/pgpcardwiget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + Copyright (c) 2020 g10 Code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "pgpcardwidget.h" + +#include "libkleo_debug.h" + +#include "smartcard/openpgpcard.h" + +#include "gencardkeydialog.h" +#include "utils/gnupg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "models/keycache.h" +#include "utils/formatting.h" + +#include +#include +#include + +#include + +#if GPGMEPP_VERSION > 0x10801 // 1.8.1 +// TODO remove ifdef once > 1.8.1 is required +#include +# define GPGME_CAN_GENCARDKEY +#endif + +#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 +# define GPGME_CAN_GPGCARD +# include +# include +#endif + +using namespace Kleo; +using namespace Kleo::SmartCard; + +namespace { +#ifdef GPGME_CAN_GENCARDKEY +class GenKeyThread: public QThread +{ + Q_OBJECT + + public: + explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const QString &serial): + mSerial(serial.toStdString()), + mParams(params) + { + } + + GpgME::Error error() + { + return mErr; + } + + std::string bkpFile() + { + return mBkpFile; + } + protected: + void run() override { + GpgME::GpgGenCardKeyInteractor *ei = new GpgME::GpgGenCardKeyInteractor(mSerial); + ei->setKeySize(mParams.keysize); + ei->setNameUtf8(mParams.name.toStdString()); + ei->setEmailUtf8(mParams.email.toStdString()); + ei->setDoBackup(mParams.backup); + + const auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); + QGpgME::QByteArrayDataProvider dp; + GpgME::Data data(&dp); + + mErr = ctx->cardEdit(GpgME::Key(), std::unique_ptr (ei), data); + mBkpFile = ei->backupFileName(); + } + + private: + GpgME::Error mErr; + std::string mSerial; + GenCardKeyDialog::KeyParams mParams; + + std::string mBkpFile; +}; +#endif +} // Namespace + +class PGPCardWidget::Private +{ +public: + Private(): + mSerialNumber(new QLabel), + mCardHolderLabel(new QLabel), + mVersionLabel(new QLabel), + mSigningKey(new QLabel), + mEncryptionKey(new QLabel), + mAuthKey(new QLabel), + mUrlLabel(new QLabel), + mCardIsEmpty(false) + { + } + + QLabel *mSerialNumber = nullptr, + *mCardHolderLabel = nullptr, + *mVersionLabel = nullptr, + *mSigningKey = nullptr, + *mEncryptionKey = nullptr, + *mAuthKey = nullptr, + *mUrlLabel = nullptr; + QString mUrl; + bool mCardIsEmpty = false; + bool mIs21 = false; + QString mRealSerial; +}; + +PGPCardWidget::PGPCardWidget(): d(new Private()) +{ + 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(d->mVersionLabel, row++, 0, 1, 2); + d->mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); + + grid->addWidget(d->mSerialNumber, row++, 1); + d->mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); + + // Cardholder Row + grid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", + "Cardholder:")), row, 0); + + grid->addWidget(d->mCardHolderLabel, row, 1); + d->mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto nameButtton = new QPushButton; + nameButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + nameButtton->setToolTip(i18n("Change")); + grid->addWidget(nameButtton, row++, 2); + connect(nameButtton, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); + + // URL Row + grid->addWidget(new QLabel(i18nc("The URL under which a public key that " + "corresponds to a smartcard can be downloaded", + "Pubkey URL:")), row, 0); + grid->addWidget(d->mUrlLabel, row, 1); + + d->mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto urlButtton = new QPushButton; + urlButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + urlButtton->setToolTip(i18n("Change")); + grid->addWidget(urlButtton, row++, 2); + connect(urlButtton, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); + + // 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("Signature:")), row, 0); + grid->addWidget(d->mSigningKey, row++, 1); + d->mSigningKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + grid->addWidget(new QLabel(i18n("Encryption:")), row, 0); + grid->addWidget(d->mEncryptionKey, row++, 1); + d->mEncryptionKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + grid->addWidget(new QLabel(i18n("Authentication:")), row, 0); + grid->addWidget(d->mAuthKey, row++, 1); + d->mAuthKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + auto line2 = new QFrame(); + line2->setFrameShape(QFrame::HLine); + grid->addWidget(line2, row++, 0, 1, 4); + grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), row++, 0); + + auto actionLayout = new QHBoxLayout; + +#ifdef GPGME_CAN_GENCARDKEY + auto generateButton = new QPushButton(i18n("Generate new Keys")); + generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); + actionLayout->addWidget(generateButton); + connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); +#endif + + auto pinButtton = new QPushButton(i18n("Change PIN")); + pinButtton->setToolTip(i18n("Change the PIN required to unblock the smartcard.")); + actionLayout->addWidget(pinButtton); + connect(pinButtton, &QPushButton::clicked, this, [this] () {doChangePin(1);}); + + auto pukButton = new QPushButton(i18n("Change Admin PIN")); + pukButton->setToolTip(i18n("Change the PIN required to unlock the smartcard.")); + actionLayout->addWidget(pukButton); + connect(pukButton, &QPushButton::clicked, this, [this] () {doChangePin(3);}); + + auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); + pukButton->setToolTip(i18n("Change the PIN required to reset the smartcard to an empty state.")); + actionLayout->addWidget(resetCodeButton); + connect(resetCodeButton, &QPushButton::clicked, this, [this] () {doChangePin(2);}); + + actionLayout->addStretch(-1); + grid->addLayout(actionLayout, row++, 0, 1, 4); + + grid->setColumnStretch(4, -1); +} + +void PGPCardWidget::setCard(const OpenPGPCard *card) +{ + const QString version = card->cardVersion(); + + d->mIs21 = versionIsAtLeast("2.1", card->cardVersion().toLatin1().constData()); + d->mVersionLabel->setText(i18nc("First placeholder is manufacturer, second placeholder is a version number", + "%1 OpenPGP v%2 card", card->manufacturer(), + version)); + const QString sn = card->serialNumber().mid(16, 12); + d->mSerialNumber->setText(sn); + d->mRealSerial = card->serialNumber(); + + const auto holder = card->cardHolder(); + const auto url = card->pubkeyUrl(); + d->mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); + d->mUrl = url; + d->mUrlLabel->setText(url.isEmpty() ? i18n("not set") : + QStringLiteral("%1").arg(url.toHtmlEscaped())); + d->mUrlLabel->setOpenExternalLinks(true); + + updateKey(d->mSigningKey, card->sigFpr()); + updateKey(d->mEncryptionKey, card->encFpr()); + updateKey(d->mAuthKey, card->authFpr()); + d->mCardIsEmpty = card->authFpr().isEmpty() && card->sigFpr().isEmpty() && card->encFpr().isEmpty(); +} + +void PGPCardWidget::doChangePin(int slot) +{ + qCWarning(LIBKLEO_LOG) << "Not implemented."; +} + +#ifdef GPGME_CAN_GENCARDKEY +void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) +{ + const auto params = dlg->getKeyParams(); + + auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); + progress->setAutoClose(true); + progress->setMinimumDuration(0); + progress->setMaximum(0); + progress->setMinimum(0); + progress->setModal(true); + progress->setCancelButton(nullptr); + progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); + progress->setLabel(new QLabel(i18n("Thisd->may take several minutes..."))); + GenKeyThread *workerThread = new GenKeyThread(params,d->mRealSerial); + connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { + progress->accept(); + progress->deleteLater(); + genKeyDone(workerThread->error(), workerThread->bkpFile()); + delete workerThread; + }); + workerThread->start(); + progress->exec(); +} + +void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "Failed to generate new key: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (err.isCanceled()) { + return; + } + if (!backup.empty()) { + const auto bkpFile = QString::fromStdString(backup); + QFileInfo fi(bkpFile); + const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), + fi.fileName(), + QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); + if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { + KMessageBox::error(this, i18nc("@info", + "Failed tod->move backup. The backup key is still stored under: %1", bkpFile), + i18nc("@title", "Error")); + } else if (!target.isEmpty()) { + QFile::remove(bkpFile); + } + } + + KMessageBox::information(this, i18nc("@info", + "Successfully generated a new key for this card."), + i18nc("@title", "Success")); +} +#else +void PGPCardWidget::doGenKey(GenCardKeyDialog *) {} +void PGPCardWidget::genKeyDone(const GpgME::Error &, const std::string &) {} +#endif + +void PGPCardWidget::genkeyRequested() +{ + if (!d->mCardIsEmpty) { + auto ret = KMessageBox::warningContinueCancel(this, + i18n("The existing keys on this card will be deleted " + "and replaced by new keys.") + QStringLiteral("

") + + i18n("It will no longer be possible to decrypt past communication " + "encrypted for the existing key."), + i18n("Secret Key Deletion"), + KStandardGuiItem::guiItem(KStandardGuiItem::Delete), + KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); + + if (ret != KMessageBox::Continue) { + return; + } + } + + GenCardKeyDialog *dlg = new GenCardKeyDialog(this); + std::vector sizes; + sizes.push_back(1024); + sizes.push_back(2048); + sizes.push_back(3072); + // There is probably a better way to check for capabilities + if (d->mIs21) { + sizes.push_back(4096); + } + dlg->setSupportedSizes(sizes); + connect(dlg, &QDialog::accepted, this, [this, dlg] () { + doGenKey(dlg); + dlg->deleteLater(); + }); + dlg->setModal(true); + dlg->show(); +} + +void PGPCardWidget::changePinResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "PIN change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "Code successfully changed."), + i18nc("@title", "Success")); + } +} + +void PGPCardWidget::changeNameRequested() +{ + QString text =d->mCardHolderLabel->text(); + while (true) { + bool ok = false; + text = QInputDialog::getText(this, i18n("Change cardholder"), + i18n("New name:"), QLineEdit::Normal, + text, &ok, Qt::WindowFlags(), + Qt::ImhLatinOnly); + if (!ok) { + return; + } + // Some additional restrictions imposed by gnupg + if (text.contains(QLatin1Char('<'))) { + KMessageBox::error(this, i18nc("@info", + "The \"<\" characterd->may not be used."), + i18nc("@title", "Error")); + continue; + } + if (text.contains(QLatin1String(" "))) { + KMessageBox::error(this, i18nc("@info", + "Double spaces are not allowed"), + i18nc("@title", "Error")); + continue; + } + if (text.size() > 38) { + KMessageBox::error(this, i18nc("@info", + "The size of the named->may not exceed 38 characters."), + i18nc("@title", "Error")); + } + break; + } + auto parts = text.split(QLatin1Char(' ')); + const auto lastName = parts.takeLast(); + const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance() + ->startSimpleTransaction(QStringLiteral("SCD SETATTR DISP-NAME %1").arg(formatted).toUtf8().constData(), + this, "changeNameResult"); +#endif +} + +void PGPCardWidget::changeNameResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "Name change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "Name successfully changed."), + i18nc("@title", "Success")); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance()->updateStatus(); +#endif + } +} + +void PGPCardWidget::changeUrlRequested() +{ + QString text =d->mUrl; + while (true) { + bool ok = false; + text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), + i18n("New pubkey URL:"), QLineEdit::Normal, + text, &ok, Qt::WindowFlags(), + Qt::ImhLatinOnly); + if (!ok) { + return; + } + // Some additional restrictions imposed by gnupg + if (text.size() > 254) { + KMessageBox::error(this, i18nc("@info", + "The size of the URLd->may not exceed 254 characters."), + i18nc("@title", "Error")); + } + break; + } +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance() + ->startSimpleTransaction(QStringLiteral("SCD SETATTR PUBKEY-URL %1").arg(text).toUtf8().constData(), + this, "changeUrlResult"); +#endif +} + +void PGPCardWidget::changeUrlResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "URL change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "URL successfully changed."), + i18nc("@title", "Success")); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance()->updateStatus(); +#endif + } +} + +void PGPCardWidget::updateKey(QLabel *label, const QString &fpr) +{ + label->setText(fpr); + + if (fpr.isEmpty()) { + label->setText(i18n("Slot empty")); + return; + } + + std::vector vec; + std::string keyid = fpr.toStdString(); + keyid.erase(0, keyid.size() - 16); + vec.push_back(keyid); + const auto subkeys = KeyCache::instance()->findSubkeysByKeyID(vec); + if (subkeys.empty() || subkeys[0].isNull()) { + label->setToolTip(i18n("Public key not found.")); + return; + } + QStringList toolTips; + for (const auto &sub: subkeys) { + // Yep you can have one subkey associated with multiple + // primary keys. + toolTips << Formatting::toolTip(sub.parent(), Formatting::Validity | + Formatting::StorageLocation | + Formatting::ExpiryDates | + Formatting::UserIDs | + Formatting::Fingerprint); + } + label->setToolTip(toolTips.join(QLatin1String("
"))); + return; +} + +#include "pgpcardwidget.moc" diff --git a/src/gpgcardgui/pgpcardwidget.h b/src/gpgcardgui/pgpcardwidget.h new file mode 100644 index 0000000..a28a2cd --- /dev/null +++ b/src/gpgcardgui/pgpcardwidget.h @@ -0,0 +1,80 @@ +/* smartcard/pgpcardwiget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifndef VIEW_PGPCARDWIDGET_H +#define VIEW_PGPCARDWIDGET_H + +#include +#include + +#include +#include + +#include "kleo_export.h" + +class QLabel; + +namespace Kleo +{ +class GenCardKeyDialog; + +namespace SmartCard +{ +class OpenPGPCard; +} // namespace SmartCard + +class KLEO_EXPORT PGPCardWidget: public QWidget +{ + Q_OBJECT +public: + PGPCardWidget(); + + void setCard(const SmartCard::OpenPGPCard* card); + void doChangePin(int slot); + void doGenKey(GenCardKeyDialog *dlg); + void genKeyDone(const GpgME::Error &err, const std::string &backup); + +public Q_SLOTS: + void genkeyRequested(); + void changePinResult(const GpgME::Error &err); + void changeNameRequested(); + void changeNameResult(const GpgME::Error &err); + void changeUrlRequested(); + void changeUrlResult(const GpgME::Error &err); + +private: + void updateKey(QLabel *label, const QString &fpr); + class Private; + std::shared_ptr d; +}; +} // namespace Kleo + +#endif // VIEW_PGPCARDWIDGET_H diff --git a/src/gpgcardgui/placeholderwidget.cpp b/src/gpgcardgui/placeholderwidget.cpp new file mode 100644 index 0000000..eb00e25 --- /dev/null +++ b/src/gpgcardgui/placeholderwidget.cpp @@ -0,0 +1,33 @@ +namespace { +class PlaceHolderWidget: public QWidget +{ + Q_OBJECT +public: + PlaceHolderWidget() + { + auto lay = new QVBoxLayout; + lay->addStretch(-1); + + const QStringList supported = QStringList() << QStringLiteral("OpenPGP > v2.0") + << QStringLiteral("YubiKey") + << QStringLiteral("Gnuk Token") + << QStringLiteral("NetKey v3"); + lay->addWidget(new QLabel(QStringLiteral("\t\t

") + + i18n("Please insert a compatible smartcard.") + QStringLiteral("

"))); + lay->addSpacing(10); + lay->addWidget(new QLabel(QStringLiteral("\t\t") + + i18n("Kleopatra currently supports the following card types:") + + QStringLiteral("
  • ") + supported.join(QLatin1String("
  • ")) + + QStringLiteral("
"))); + lay->addSpacing(10); + lay->addWidget(new QLabel(i18n("Refresh the view (F5) to update the smartcard status."))); + lay->addStretch(-1); + + auto hLay = new QHBoxLayout(this); + hLay->addStretch(-1); + hLay->addLayout(lay); + hLay->addStretch(-1); + lay->addStretch(-1); + } +}; +} // namespace diff --git a/src/gpgcardgui/placeholderwidget.h b/src/gpgcardgui/placeholderwidget.h new file mode 100644 index 0000000..d1f2504 --- /dev/null +++ b/src/gpgcardgui/placeholderwidget.h @@ -0,0 +1,6 @@ +class PlaceHolderWidget: public QWidget +{ + Q_OBJECT +public: + PlaceHolderWidget(); +};