diff --git a/src/dialogs/editgroupdialog.cpp b/src/dialogs/editgroupdialog.cpp index 6fc6a6c3c..6b3923e10 100644 --- a/src/dialogs/editgroupdialog.cpp +++ b/src/dialogs/editgroupdialog.cpp @@ -1,306 +1,308 @@ /* dialogs/editgroupdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "editgroupdialog.h" #include "commands/detailscommand.h" #include "view/keytreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { auto createOpenPGPOnlyKeyFilter() { auto filter = std::make_shared(); filter->setIsOpenPGP(DefaultKeyFilter::Set); return filter; } } class EditGroupDialog::Private { friend class ::Kleo::Dialogs::EditGroupDialog; EditGroupDialog *const q; struct { QLineEdit *groupNameEdit = nullptr; QLineEdit *availableKeysFilter = nullptr; KeyTreeView *availableKeysList = nullptr; QLineEdit *groupKeysFilter = nullptr; KeyTreeView *groupKeysList = nullptr; QDialogButtonBox *buttonBox = nullptr; } ui; AbstractKeyListModel *availableKeysModel = nullptr; AbstractKeyListModel *groupKeysModel = nullptr; public: Private(EditGroupDialog *qq) : q(qq) { auto mainLayout = new QVBoxLayout(q); auto groupNameLayout = new QHBoxLayout(); groupNameLayout->addWidget(new QLabel(i18nc("Name of a group of keys", "Name:"))); ui.groupNameEdit = new QLineEdit(); groupNameLayout->addWidget(ui.groupNameEdit); mainLayout->addLayout(groupNameLayout); mainLayout->addWidget(new KSeparator(Qt::Horizontal)); auto centerLayout = new QVBoxLayout(); auto availableKeysLayout = new QVBoxLayout(); availableKeysLayout->addWidget(new QLabel(i18n("Available keys:"))); ui.availableKeysFilter = new QLineEdit(); ui.availableKeysFilter->setClearButtonEnabled(true); ui.availableKeysFilter->setPlaceholderText(i18nc("Placeholder text", "Search...")); availableKeysLayout->addWidget(ui.availableKeysFilter); availableKeysModel = AbstractKeyListModel::createFlatKeyListModel(q); availableKeysModel->useKeyCache(true, KeyList::AllKeys); ui.availableKeysList = new KeyTreeView(q); ui.availableKeysList->view()->setRootIsDecorated(false); ui.availableKeysList->setFlatModel(availableKeysModel); ui.availableKeysList->setHierarchicalView(false); if (!Settings{}.cmsEnabled()) { ui.availableKeysList->setKeyFilter(createOpenPGPOnlyKeyFilter()); } availableKeysLayout->addWidget(ui.availableKeysList, /*stretch=*/ 1); centerLayout->addLayout(availableKeysLayout, /*stretch=*/ 1); auto buttonsLayout = new QHBoxLayout(); buttonsLayout->addStretch(1); auto addButton = new QPushButton(); addButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); + addButton->setAccessibleName(i18nc("@action:button", "Add Selected Keys")); addButton->setToolTip(i18n("Add the selected keys to the group")); addButton->setEnabled(false); buttonsLayout->addWidget(addButton); auto removeButton = new QPushButton(); removeButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); + addButton->setAccessibleName(i18nc("@action:button", "Remove Selected Keys")); removeButton->setToolTip(i18n("Remove the selected keys from the group")); removeButton->setEnabled(false); buttonsLayout->addWidget(removeButton); buttonsLayout->addStretch(1); centerLayout->addLayout(buttonsLayout); auto groupKeysLayout = new QVBoxLayout(); groupKeysLayout->addWidget(new QLabel(i18n("Group keys:"))); ui.groupKeysFilter = new QLineEdit(); ui.groupKeysFilter->setClearButtonEnabled(true); ui.groupKeysFilter->setPlaceholderText(i18nc("Placeholder text", "Search...")); groupKeysLayout->addWidget(ui.groupKeysFilter); groupKeysModel = AbstractKeyListModel::createFlatKeyListModel(q); ui.groupKeysList = new KeyTreeView(q); ui.groupKeysList->view()->setRootIsDecorated(false); ui.groupKeysList->setFlatModel(groupKeysModel); ui.groupKeysList->setHierarchicalView(false); groupKeysLayout->addWidget(ui.groupKeysList, /*stretch=*/ 1); centerLayout->addLayout(groupKeysLayout, /*stretch=*/ 1); mainLayout->addLayout(centerLayout); mainLayout->addWidget(new KSeparator(Qt::Horizontal)); ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *okButton = ui.buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(okButton, KStandardGuiItem::ok()); KGuiItem::assign(ui.buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); okButton->setEnabled(false); mainLayout->addWidget(ui.buttonBox); connect(ui.groupNameEdit, &QLineEdit::textChanged, q, [okButton] (const QString &text) { okButton->setEnabled(!text.trimmed().isEmpty()); }); connect(ui.availableKeysFilter, &QLineEdit::textChanged, ui.availableKeysList, &KeyTreeView::setStringFilter); connect(ui.availableKeysList->view()->selectionModel(), &QItemSelectionModel::selectionChanged, q, [addButton] (const QItemSelection &selected, const QItemSelection &) { addButton->setEnabled(!selected.isEmpty()); }); connect(ui.availableKeysList->view(), &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &index) { showKeyDetails(index); }); connect(ui.groupKeysFilter, &QLineEdit::textChanged, ui.groupKeysList, &KeyTreeView::setStringFilter); connect(ui.groupKeysList->view()->selectionModel(), &QItemSelectionModel::selectionChanged, q, [removeButton] (const QItemSelection &selected, const QItemSelection &) { removeButton->setEnabled(!selected.isEmpty()); }); connect(ui.groupKeysList->view(), &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &index) { showKeyDetails(index); }); connect(addButton, &QPushButton::clicked, q, [this] () { addKeysToGroup(); }); connect(removeButton, &QPushButton::clicked, q, [this] () { removeKeysFromGroup(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditGroupDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditGroupDialog::reject); // calculate default size with enough space for the key list const auto fm = q->fontMetrics(); const QSize sizeHint = q->sizeHint(); const QSize defaultSize = QSize(qMax(sizeHint.width(), 150 * fm.horizontalAdvance(QLatin1Char('x'))), sizeHint.height()); restoreLayout(defaultSize); } ~Private() { saveLayout(); } private: void saveLayout() { KConfigGroup configGroup(KSharedConfig::openConfig(), "EditGroupDialog"); configGroup.writeEntry("Size", q->size()); KConfigGroup availableKeysConfig = configGroup.group("AvailableKeysView"); ui.availableKeysList->saveLayout(availableKeysConfig); KConfigGroup groupKeysConfig = configGroup.group("GroupKeysView"); ui.groupKeysList->saveLayout(groupKeysConfig); configGroup.sync(); } void restoreLayout(const QSize &defaultSize) { const KConfigGroup configGroup(KSharedConfig::openConfig(), "EditGroupDialog"); const KConfigGroup availableKeysConfig = configGroup.group("AvailableKeysView"); ui.availableKeysList->restoreLayout(availableKeysConfig); const KConfigGroup groupKeysConfig = configGroup.group("GroupKeysView"); ui.groupKeysList->restoreLayout(groupKeysConfig); const QSize size = configGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } } void showKeyDetails(const QModelIndex &index) { if (!index.isValid()) { return; } const auto key = index.model()->data(index, KeyList::KeyRole).value(); if (!key.isNull()) { auto cmd = new DetailsCommand(key, nullptr); cmd->setParentWidget(q); cmd->start(); } } void addKeysToGroup(); void removeKeysFromGroup(); }; void EditGroupDialog::Private::addKeysToGroup() { const std::vector selectedGroupKeys = ui.groupKeysList->selectedKeys(); const std::vector selectedKeys = ui.availableKeysList->selectedKeys(); groupKeysModel->addKeys(selectedKeys); ui.groupKeysList->selectKeys(selectedGroupKeys); } void EditGroupDialog::Private::removeKeysFromGroup() { const std::vector selectedKeys = ui.groupKeysList->selectedKeys(); for (const Key &key : selectedKeys) { groupKeysModel->removeKey(key); } } EditGroupDialog::EditGroupDialog(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Edit Group")); } EditGroupDialog::~EditGroupDialog() = default; void EditGroupDialog::setInitialFocus(FocusWidget widget) { switch (widget) { case GroupName: d->ui.groupNameEdit->setFocus(); break; case KeysFilter: d->ui.availableKeysFilter->setFocus(); break; default: qCDebug(KLEOPATRA_LOG) << "EditGroupDialog::setInitialFocus - invalid focus widget:" << widget; } } void EditGroupDialog::setGroupName(const QString &name) { d->ui.groupNameEdit->setText(name); } QString EditGroupDialog::groupName() const { return d->ui.groupNameEdit->text().trimmed(); } void EditGroupDialog::setGroupKeys(const std::vector &keys) { d->groupKeysModel->setKeys(keys); } std::vector EditGroupDialog::groupKeys() const { std::vector keys; keys.reserve(d->groupKeysModel->rowCount()); for (int row = 0; row < d->groupKeysModel->rowCount(); ++row) { const QModelIndex index = d->groupKeysModel->index(row, 0); keys.push_back(d->groupKeysModel->key(index)); } return keys; } diff --git a/src/newcertificatewizard/listwidget.cpp b/src/newcertificatewizard/listwidget.cpp index ab554b8d4..7e6860fa9 100644 --- a/src/newcertificatewizard/listwidget.cpp +++ b/src/newcertificatewizard/listwidget.cpp @@ -1,224 +1,228 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/listwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "listwidget.h" #include "ui_listwidget.h" #include #include #include #include #include #include using namespace Kleo::NewCertificateUi; namespace { class ItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ItemDelegate(QObject *p = nullptr) : QItemDelegate(p), m_rx() {} explicit ItemDelegate(const QRegularExpression &rx, QObject *p = nullptr) : QItemDelegate(p), m_rx(rx) {} void setRegExpFilter(const QRegularExpression &rx) { m_rx = rx; } const QRegularExpression ®ExpFilter() const { return m_rx; } QWidget *createEditor(QWidget *p, const QStyleOptionViewItem &o, const QModelIndex &i) const override { QWidget *w = QItemDelegate::createEditor(p, o, i); if (m_rx.isValid()) if (auto const le = qobject_cast(w)) { le->setValidator(new QRegularExpressionValidator(m_rx, le)); } return w; } private: QRegularExpression m_rx; }; } class ListWidget::Private { friend class ::Kleo::NewCertificateUi::ListWidget; ListWidget *const q; public: explicit Private(ListWidget *qq) : q(qq), stringListModel(), ui(q) { ui.listView->setModel(&stringListModel); ui.listView->setItemDelegate(&delegate); connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged())); connect(&stringListModel, &QAbstractItemModel::dataChanged, q, &ListWidget::itemsChanged); connect(&stringListModel, &QAbstractItemModel::rowsInserted, q, &ListWidget::itemsChanged); connect(&stringListModel, &QAbstractItemModel::rowsRemoved, q, &ListWidget::itemsChanged); } private: void slotAdd() { const int idx = stringListModel.rowCount(); if (stringListModel.insertRows(idx, 1)) { stringListModel.setData(stringListModel.index(idx), defaultValue); editRow(idx); } } void slotRemove() { const int idx = selectedRow(); stringListModel.removeRows(idx, 1); selectRow(idx); } void slotUp() { const int idx = selectedRow(); swapRows(idx - 1, idx); selectRow(idx - 1); } void slotDown() { const int idx = selectedRow(); swapRows(idx, idx + 1); selectRow(idx + 1); } void slotSelectionChanged() { enableDisableActions(); } private: void editRow(int idx) { const QModelIndex mi = stringListModel.index(idx); if (!mi.isValid()) { return; } ui.listView->setCurrentIndex(mi); ui.listView->edit(mi); } QModelIndexList selectedIndexes() const { return ui.listView->selectionModel()->selectedRows(); } int selectedRow() const { const QModelIndexList mil = selectedIndexes(); return mil.empty() ? -1 : mil.front().row(); } void selectRow(int idx) { const QModelIndex mi = stringListModel.index(idx); if (mi.isValid()) { ui.listView->selectionModel()->select(mi, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } } void swapRows(int r1, int r2) { if (r1 < 0 || r2 < 0 || r1 >= stringListModel.rowCount() || r2 >= stringListModel.rowCount()) { return; } const QModelIndex m1 = stringListModel.index(r1); const QModelIndex m2 = stringListModel.index(r2); const QVariant data1 = m1.data(); const QVariant data2 = m2.data(); stringListModel.setData(m1, data2); stringListModel.setData(m2, data1); } void enableDisableActions() { const QModelIndexList mil = selectedIndexes(); ui.removeTB->setEnabled(!mil.empty()); ui.upTB->setEnabled(mil.size() == 1 && mil.front().row() > 0); ui.downTB->setEnabled(mil.size() == 1 && mil.back().row() < stringListModel.rowCount() - 1); } private: QStringListModel stringListModel; ItemDelegate delegate; QString defaultValue; struct UI : Ui_ListWidget { explicit UI(ListWidget *q) : Ui_ListWidget() { setupUi(q); addTB->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); + addTB->setAccessibleName(i18nc("@action:button", "Add Entry")); removeTB->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); + removeTB->setAccessibleName(i18nc("@action:button", "Remove Entry")); upTB->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); + upTB->setAccessibleName(i18nc("@action:button", "Move Up")); downTB->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); + downTB->setAccessibleName(i18nc("@action:button", "Move Down")); } } ui; }; ListWidget::ListWidget(QWidget *p) : QWidget(p), d(new Private(this)) { } ListWidget::~ListWidget() {} QStringList ListWidget::items() const { return d->stringListModel.stringList(); } void ListWidget::setItems(const QStringList &items) { d->stringListModel.setStringList(items); } QRegularExpression ListWidget::regExpFilter() const { return d->delegate.regExpFilter(); } void ListWidget::setRegExpFilter(const QRegularExpression &rx) { d->delegate.setRegExpFilter(rx); } QString ListWidget::defaultValue() const { return d->defaultValue; } void ListWidget::setDefaultValue(const QString &df) { d->defaultValue = df; } #include "moc_listwidget.cpp" #include "listwidget.moc" diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index 56e75e5da..d147352b4 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,505 +1,507 @@ /* view/pgpcardwiget.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-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "pgpcardwidget.h" #include "openpgpkeycardwidget.h" #include "kleopatra_debug.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "dialogs/gencardkeydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { class GenKeyThread: public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial): mSerial(serial), mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { auto ei = new GpgME::GpgGenCardKeyInteractor(mSerial); ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); ei->setKeySize(QByteArray::fromStdString(mParams.algorithm).toInt()); 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; }; } // Namespace PGPCardWidget::PGPCardWidget(QWidget *parent): QWidget(parent), mSerialNumber(new QLabel(this)), mCardHolderLabel(new QLabel(this)), mVersionLabel(new QLabel(this)), mUrlLabel(new QLabel(this)), mCardIsEmpty(false) { // Set up the scroll area auto myLayout = new QVBoxLayout(this); myLayout->setContentsMargins(0, 0, 0, 0); auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); myLayout->addWidget(area); auto areaWidget = new QWidget; area->setWidget(areaWidget); auto areaVLay = new QVBoxLayout(areaWidget); auto cardInfoGrid = new QGridLayout; { int row = 0; // Version and Serialnumber cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0); cardInfoGrid->addWidget(mSerialNumber, row, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; // Cardholder Row cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); cardInfoGrid->addWidget(mCardHolderLabel, row, 1); mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); } row++; // URL Row cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that " "corresponds to a smartcard can be downloaded", "Pubkey URL:")), row, 0); cardInfoGrid->addWidget(mUrlLabel, row, 1); mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); } cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); } areaVLay->addLayout(cardInfoGrid); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); // The keys areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:")))); mKeysWidget = new OpenPGPKeyCardWidget{this}; areaVLay->addWidget(mKeysWidget); connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:")))); auto actionLayout = new QHBoxLayout; { 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); } { auto pinButton = new QPushButton(i18n("Change PIN")); pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard.")); actionLayout->addWidget(pinButton); connect(pinButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::pinKeyRef()); }); } { auto unblockButton = new QPushButton(i18n("Unblock Card")); unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN.")); actionLayout->addWidget(unblockButton); connect(unblockButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef()); }); } { auto pukButton = new QPushButton(i18n("Change Admin PIN")); pukButton->setToolTip(i18n("Change the PIN required for administrative operations.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::adminPinKeyRef()); }); } { auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN.")); actionLayout->addWidget(resetCodeButton); connect(resetCodeButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); }); } if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) { mKeyForCardKeysButton = new QPushButton(this); mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key")); mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card.")); actionLayout->addWidget(mKeyForCardKeysButton); connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys); } actionLayout->addStretch(-1); areaVLay->addLayout(actionLayout); areaVLay->addStretch(1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { const QString version = card->displayAppVersion(); mIs21 = card->appVersion() >= 0x0201; const QString manufacturer = QString::fromStdString(card->manufacturer()); const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown"); mVersionLabel->setText(manufacturerIsUnknown ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version)); mSerialNumber->setText(card->displaySerialNumber()); mRealSerial = card->serialNumber(); const auto holder = card->cardHolder(); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("%1").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); mKeysWidget->update(card); mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty(); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(card->hasSigningKey() && card->hasEncryptionKey()); } } void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode) { auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->setMode(mode); cmd->start(); } void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName); if (err) { return; } 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("This may take several minutes..."))); auto workerThread = new GenKeyThread(params, 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()))); 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 to move backup. The backup key is still stored under: %1", bkpFile)); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } void PGPCardWidget::genkeyRequested() { if (!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; } } auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this); std::vector> algos = { { "1024", QStringLiteral("RSA 1024") }, { "2048", QStringLiteral("RSA 2048") }, { "3072", QStringLiteral("RSA 3072") } }; // There is probably a better way to check for capabilities if (mIs21) { algos.push_back({"4096", QStringLiteral("RSA 4096")}); } dlg->setSupportedAlgorithms(algos, "2048"); connect(dlg, &QDialog::accepted, this, [this, dlg] () { doGenKey(dlg); dlg->deleteLater(); }); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changeNameRequested() { QString text = 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 \"<\" character may not be used.")); continue; } if (text.contains(QLatin1String(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters.")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeNameResult(err); }); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", QString::fromLatin1(err.asString()))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = 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 URL may not exceed 254 characters.")); } break; } const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeUrlResult(err); }); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", QString::fromLatin1(err.asString()))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::createCSR(const std::string &keyref) { auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "pgpcardwidget.moc"