diff options
author | saturneric <[email protected]> | 2025-04-12 23:03:57 +0000 |
---|---|---|
committer | saturneric <[email protected]> | 2025-04-12 23:03:57 +0000 |
commit | 571cfb16ccfd7ac6bc59b5acc77a94d0bdcf0990 (patch) | |
tree | a4efe7ba4d5344897c60513c03172d5de0191658 | |
parent | fix: upgrade to gpgme 1.24.2 (diff) | |
download | GpgFrontend-571cfb16ccfd7ac6bc59b5acc77a94d0bdcf0990.tar.gz GpgFrontend-571cfb16ccfd7ac6bc59b5acc77a94d0bdcf0990.zip |
feat: add openpgp smart card support
27 files changed, 897 insertions, 28 deletions
diff --git a/gpgfrontend.qrc b/gpgfrontend.qrc index 49a26dec..4f1e2275 100644 --- a/gpgfrontend.qrc +++ b/gpgfrontend.qrc @@ -111,6 +111,7 @@ <file alias="batch.png">resource/lfs/icons/batch.png</file> <file alias="encr-sign.png">resource/lfs/icons/encr-sign.png</file> <file alias="decr-verify.png">resource/lfs/icons/decr-verify.png</file> + <file alias="smart-card.png">resource/lfs/icons/smart-card.png</file> </qresource> <qresource prefix="/test/key"> <file alias="pv1.key">resource/lfs/test/data/pv1.key</file> diff --git a/resource/lfs/icons/smart-card.png b/resource/lfs/icons/smart-card.png Binary files differnew file mode 100644 index 00000000..10d23d01 --- /dev/null +++ b/resource/lfs/icons/smart-card.png diff --git a/src/core/model/GpgCardKeyPairInfo.cpp b/src/core/model/GpgCardKeyPairInfo.cpp new file mode 100644 index 00000000..481d88ea --- /dev/null +++ b/src/core/model/GpgCardKeyPairInfo.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "GpgCardKeyPairInfo.h" + +namespace GpgFrontend { + +GpgCardKeyPairInfo::GpgCardKeyPairInfo(const QString &status) { + const auto values = status.split(QLatin1Char(' ')); + if (values.size() < 2) { + return; + } + + grip = values[0]; + key_ref = values[1]; + if (values.size() >= 3) { + usage = values[2]; + } + + if (values.size() >= 4 && !values[3].isEmpty() && values[3] != "-") { + bool ok; + const qint64 seconds_since_epoch = values[3].toLongLong(&ok); + if (ok) { + time = + QDateTime::fromSecsSinceEpoch(seconds_since_epoch, QTimeZone::utc()); + } + } + + if (values.size() >= 5) { + algorithm = values[4]; + } +} + +auto GpgCardKeyPairInfo::CanAuthenticate() const -> bool { + return usage.contains('a'); +} + +auto GpgCardKeyPairInfo::CanCertify() const -> bool { + return usage.contains('c'); +} + +auto GpgCardKeyPairInfo::CanEncrypt() const -> bool { + return usage.contains('e'); +} + +auto GpgCardKeyPairInfo::CanSign() const -> bool { return usage.contains('s'); } + +} // namespace GpgFrontend diff --git a/src/core/model/GpgCardKeyPairInfo.h b/src/core/model/GpgCardKeyPairInfo.h new file mode 100644 index 00000000..132ded84 --- /dev/null +++ b/src/core/model/GpgCardKeyPairInfo.h @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +namespace GpgFrontend { + +struct GpgCardKeyPairInfo { + explicit GpgCardKeyPairInfo(const QString &status); + + [[nodiscard]] auto CanAuthenticate() const -> bool; + [[nodiscard]] auto CanCertify() const -> bool; + [[nodiscard]] auto CanEncrypt() const -> bool; + [[nodiscard]] auto CanSign() const -> bool; + + QString key_ref; + QString grip; + QString usage; + QDateTime time; + QString algorithm; +}; + +} // namespace GpgFrontend diff --git a/src/core/model/GpgKey.cpp b/src/core/model/GpgKey.cpp index 6608d885..3efb53fc 100644 --- a/src/core/model/GpgKey.cpp +++ b/src/core/model/GpgKey.cpp @@ -37,6 +37,9 @@ GpgKey::GpgKey(gpgme_key_t key) if (ptr != nullptr) gpgme_key_unref(ptr); }) {} +GpgKey::GpgKey(QSharedPointer<struct _gpgme_key> key_ref) + : key_ref_(std::move(key_ref)) {} + GpgKey::operator gpgme_key_t() const { return key_ref_.get(); } GpgKey::GpgKey(const GpgKey &) = default; @@ -218,4 +221,5 @@ auto GpgKey::PrimaryKey() const -> GpgSubKey { } auto GpgKey::IsSubKey() const -> bool { return false; } + } // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/model/GpgKey.h b/src/core/model/GpgKey.h index da92de8d..de2e8370 100644 --- a/src/core/model/GpgKey.h +++ b/src/core/model/GpgKey.h @@ -28,6 +28,8 @@ #pragma once +#include <utility> + #include "core/model/GpgAbstractKey.h" #include "core/model/GpgSubKey.h" #include "core/model/GpgUID.h" @@ -57,6 +59,13 @@ class GPGFRONTEND_CORE_EXPORT GpgKey : public GpgAbstractKey { /** * @brief Construct a new Gpg Key object * + * @param key + */ + explicit GpgKey(QSharedPointer<struct _gpgme_key> key_ref); + + /** + * @brief Construct a new Gpg Key object + * * @param k */ GpgKey(const GpgKey&); diff --git a/src/core/model/GpgKeyTreeModel.cpp b/src/core/model/GpgKeyTreeModel.cpp index cb3ee4ba..bbe5d00b 100644 --- a/src/core/model/GpgKeyTreeModel.cpp +++ b/src/core/model/GpgKeyTreeModel.cpp @@ -257,6 +257,15 @@ auto GpgKeyTreeModel::GetAllCheckedSubKey() -> QContainer<GpgSubKey> { return ret; } +auto GpgKeyTreeModel::GetKeyByIndex(QModelIndex index) -> GpgAbstractKey * { + if (!index.isValid()) return nullptr; + + const auto *item = + static_cast<const GpgKeyTreeItem *>(index.internalPointer()); + + return item->Key(); +} + GpgKeyTreeItem::GpgKeyTreeItem(QSharedPointer<GpgAbstractKey> key, QVariantList data) : data_(std::move(data)), key_(std::move(key)) {} diff --git a/src/core/model/GpgKeyTreeModel.h b/src/core/model/GpgKeyTreeModel.h index f56591ea..9e91037d 100644 --- a/src/core/model/GpgKeyTreeModel.h +++ b/src/core/model/GpgKeyTreeModel.h @@ -346,6 +346,13 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyTreeModel : public QAbstractItemModel { */ auto GetAllCheckedSubKey() -> QContainer<GpgSubKey>; + /** + * @brief Get the Key By Index object + * + * @return GpgAbstractKey* + */ + auto GetKeyByIndex(QModelIndex) -> GpgAbstractKey *; + private: int gpg_context_channel_; QVariantList column_headers_; diff --git a/src/core/model/GpgOpenPGPCard.cpp b/src/core/model/GpgOpenPGPCard.cpp new file mode 100644 index 00000000..f0d8c9cd --- /dev/null +++ b/src/core/model/GpgOpenPGPCard.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "GpgOpenPGPCard.h" + +#include "core/model/GpgCardKeyPairInfo.h" +#include "core/utils/CommonUtils.h" + +namespace GpgFrontend { + +void GpgFrontend::GpgOpenPGPCard::parse_card_info(const QString& name, + const QString& value) { + if (name == "APPVERSION") { + app_version = ParseHexEncodedVersionTuple(value); + } else if (name == "CARDTYPE") { + card_type = value; + } else if (name == "CARDVERSION") { + card_version = ParseHexEncodedVersionTuple(value); + } else if (name == "DISP-NAME") { + auto list = value.split(QStringLiteral("<<"), Qt::SkipEmptyParts); + std::reverse(list.begin(), list.end()); + card_holder = + list.join(QLatin1Char(' ')).replace(QLatin1Char('<'), QLatin1Char(' ')); + } else if (name == "KEYPAIRINFO") { + const GpgCardKeyPairInfo info = GpgCardKeyPairInfo(value); + if (info.grip.isEmpty()) { + LOG_W() << "invalid KEYPAIRINFO status line" << value; + good = false; + } + } else if (name == "KEY-FPR") { + const auto values = value.split(QLatin1Char(' ')); + if (values.size() < 2) { + LOG_W() << "invalid KEY-FPR status line" << value; + good = false; + return; + } + + const auto& key_number = values[0].toInt(); + const auto& fpr = values[1]; + fprs.insert(key_number, fpr); + + } else if (name == "MANUFACTURER") { + // the value of MANUFACTURER is the manufacturer ID as unsigned number + // optionally followed by the name of the manufacturer, e.g. + // 6 Yubico + // 65534 unmanaged S/N range + // for PKCS#15 cards the manufacturer ID is always 0, e.g. + // 0 www.atos.net/cardos [R&S] + auto space_index = value.indexOf(' '); + if (space_index != -1) { + card_infos.insert(name, value.mid(space_index + 1).trimmed()); + } + } else { + card_infos.insert(name, value); + } +} +GpgOpenPGPCard::GpgOpenPGPCard(const QStringList& status) : good(true) { + for (const QString& line : status) { + auto tokens = line.split(' ', Qt::SkipEmptyParts); + auto name = tokens.value(0); + auto value = tokens.mid(1).join(' '); + + parse_card_info(name, value); + } +} +} // namespace GpgFrontend diff --git a/src/core/model/GpgOpenPGPCard.h b/src/core/model/GpgOpenPGPCard.h new file mode 100644 index 00000000..78b7d854 --- /dev/null +++ b/src/core/model/GpgOpenPGPCard.h @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include "core/model/GpgCardKeyPairInfo.h" +#include "core/typedef/CoreTypedef.h" + +namespace GpgFrontend { + +struct GPGFRONTEND_CORE_EXPORT GpgOpenPGPCard { + public: + QString reader; + QString serial_number; + QString card_type; + int card_version; + QString app_type; + int app_version; + QString ext_capability; + QString manufacturer; + QString card_holder; + QString display_language; + QString display_sex; + QString chv_status; + int sig_counter = 0; + + QContainer<GpgCardKeyPairInfo> keys; + QMap<int, QString> fprs; + QMap<QString, QString> card_infos; + + QString kdf; + QString uif1; + QString uif2; + QString uif3; + + bool good = false; + + GpgOpenPGPCard() = default; + + explicit GpgOpenPGPCard(const QStringList& status); + + private: + void parse_card_info(const QString& name, const QString& value); +}; + +} // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/model/GpgSubKey.cpp b/src/core/model/GpgSubKey.cpp index b4bd94ec..a4e6582f 100644 --- a/src/core/model/GpgSubKey.cpp +++ b/src/core/model/GpgSubKey.cpp @@ -27,8 +27,7 @@ */ #include "GpgSubKey.h" -#include <utility> - +#include "core/model/GpgKey.h" namespace GpgFrontend { GpgSubKey::GpgSubKey() = default; @@ -92,11 +91,15 @@ auto GpgSubKey::ExpirationTime() const -> QDateTime { auto GpgSubKey::IsADSK() const -> bool { return s_key_ref_->can_renc; } -auto GpgSubKey::SmartCardSerialNumber() -> QString { - return s_key_ref_->card_number; +auto GpgSubKey::SmartCardSerialNumber() const -> QString { + return QString::fromLatin1(s_key_ref_->card_number); } auto GpgSubKey::IsSubKey() const -> bool { return true; } auto GpgSubKey::IsGood() const -> bool { return s_key_ref_ != nullptr; } + +auto GpgSubKey::Convert2GpgKey() const -> QSharedPointer<GpgKey> { + return QSharedPointer<GpgKey>::create(key_ref_); +} } // namespace GpgFrontend diff --git a/src/core/model/GpgSubKey.h b/src/core/model/GpgSubKey.h index fe925f8e..c947b8af 100644 --- a/src/core/model/GpgSubKey.h +++ b/src/core/model/GpgSubKey.h @@ -35,6 +35,8 @@ namespace GpgFrontend { +class GpgKey; + /** * @brief * @@ -232,7 +234,14 @@ class GPGFRONTEND_CORE_EXPORT GpgSubKey : public GpgAbstractKey { * * @return QString */ - [[nodiscard]] auto SmartCardSerialNumber() -> QString; + [[nodiscard]] auto SmartCardSerialNumber() const -> QString; + + /** + * @brief + * + * @return QString + */ + [[nodiscard]] auto Convert2GpgKey() const -> QSharedPointer<GpgKey>; private: QSharedPointer<struct _gpgme_key> key_ref_; diff --git a/src/core/utils/CommonUtils.cpp b/src/core/utils/CommonUtils.cpp index 9687acd4..0adc4d7f 100644 --- a/src/core/utils/CommonUtils.cpp +++ b/src/core/utils/CommonUtils.cpp @@ -98,4 +98,13 @@ auto GFUnStrDup(const char* s) -> QString { auto GPGFRONTEND_CORE_EXPORT IsFlatpakENV() -> bool { return QString::fromLocal8Bit(qgetenv("container")) == "flatpak"; } + +auto GPGFRONTEND_CORE_EXPORT ParseHexEncodedVersionTuple(const QString& s) + -> int { + // s is a hex-encoded, unsigned int-packed version tuple, + // i.e. each byte represents one part of the version tuple + bool ok; + const auto version = s.toUtf8().toUInt(&ok, 16); + return ok ? static_cast<int>(version) : -1; +} } // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/utils/CommonUtils.h b/src/core/utils/CommonUtils.h index de77114f..468d8a59 100644 --- a/src/core/utils/CommonUtils.h +++ b/src/core/utils/CommonUtils.h @@ -73,4 +73,13 @@ auto GPGFRONTEND_CORE_EXPORT GFUnStrDup(const char *) -> QString; */ auto GPGFRONTEND_CORE_EXPORT IsFlatpakENV() -> bool; +/** + * @brief + * + * @param s + * @return int + */ +auto GPGFRONTEND_CORE_EXPORT ParseHexEncodedVersionTuple(const QString &s) + -> int; + } // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/utils/GpgUtils.cpp b/src/core/utils/GpgUtils.cpp index c7040cc2..22ba856e 100644 --- a/src/core/utils/GpgUtils.cpp +++ b/src/core/utils/GpgUtils.cpp @@ -353,4 +353,21 @@ auto GPGFRONTEND_CORE_EXPORT GetUsagesBySubkey(const GpgSubKey& key) if (key.IsADSK()) usages += "R"; return usages; } + +auto GPGFRONTEND_CORE_EXPORT GetGpgKeyByGpgAbstractKey(GpgAbstractKey* ab_key) + -> GpgKey { + if (!ab_key->IsGood()) return {}; + + if (ab_key->IsSubKey()) { + auto* s_key = dynamic_cast<GpgSubKey*>(ab_key); + + assert(s_key != nullptr); + if (s_key == nullptr) return {}; + + return *s_key->Convert2GpgKey(); + } + + auto* key = dynamic_cast<GpgKey*>(ab_key); + return *key; +} } // namespace GpgFrontend diff --git a/src/core/utils/GpgUtils.h b/src/core/utils/GpgUtils.h index b453cc0a..9fcfe5cf 100644 --- a/src/core/utils/GpgUtils.h +++ b/src/core/utils/GpgUtils.h @@ -28,7 +28,7 @@ #pragma once -#include "core/function/result_analyse/GpgResultAnalyse.h" +#include "core/model/GpgAbstractKey.h" #include "core/model/KeyDatabaseInfo.h" #include "core/struct/settings_object/KeyDatabaseItemSO.h" #include "core/typedef/CoreTypedef.h" @@ -173,4 +173,12 @@ auto GPGFRONTEND_CORE_EXPORT GetUsagesByKey(const GpgKey& key) -> QString; */ auto GPGFRONTEND_CORE_EXPORT GetUsagesBySubkey(const GpgSubKey& key) -> QString; +/** + * @brief + * + * @return GpgKey + */ +auto GPGFRONTEND_CORE_EXPORT GetGpgKeyByGpgAbstractKey(GpgAbstractKey*) + -> GpgKey; + } // namespace GpgFrontend
\ No newline at end of file diff --git a/src/test/core/GpgCoreTestAssuan.cpp b/src/test/core/GpgCoreTestAssuan.cpp index cc61b27c..ef0d444f 100644 --- a/src/test/core/GpgCoreTestAssuan.cpp +++ b/src/test/core/GpgCoreTestAssuan.cpp @@ -70,6 +70,7 @@ TEST_F(GpgCoreTest, CoreAssuanConnectTestB) { helper.SendStatusCommand(GpgComponentType::kGPG_AGENT, "keyinfo --list"); ASSERT_TRUE(ret); ASSERT_TRUE(!status.isEmpty()); + ASSERT_TRUE(status.front().startsWith("KEYINFO")); LOG_D() << "status lines of command keyinfo --list: " << status; } diff --git a/src/ui/dialog/controller/SmartCardControllerDialog.cpp b/src/ui/dialog/controller/SmartCardControllerDialog.cpp new file mode 100644 index 00000000..847836b1 --- /dev/null +++ b/src/ui/dialog/controller/SmartCardControllerDialog.cpp @@ -0,0 +1,173 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "SmartCardControllerDialog.h" + +#include "core/function/gpg/GpgAssuanHelper.h" +#include "core/utils/GpgUtils.h" + +// +#include "ui_SmartCardControllerDialog.h" + +namespace GpgFrontend::UI { +SmartCardControllerDialog::SmartCardControllerDialog(QWidget* parent) + : GeneralDialog("SmartCardControllerDialog", parent), + ui_(QSharedPointer<Ui_SmartCardControllerDialog>::create()), + channel_(kGpgFrontendDefaultChannel) { + ui_->setupUi(this); + + for (const auto& key_db : GetGpgKeyDatabaseInfos()) { + ui_->keyDBIndexComboBox->insertItem( + key_db.channel, QString("%1: %2").arg(key_db.channel).arg(key_db.name)); + } + + connect(ui_->keyDBIndexComboBox, + qOverload<int>(&QComboBox::currentIndexChanged), this, + [=](int index) { + channel_ = index; + ui_->cardKeysTreeView->SetChannel(channel_); + ui_->cardKeysTreeView->expandAll(); + }); + + slot_refresh(); +} + +void SmartCardControllerDialog::get_smart_card_serial_number() { + auto [ret, status] = GpgAssuanHelper::GetInstance().SendStatusCommand( + GpgComponentType::kGPG_AGENT, "SCD SERIALNO --all openpgp"); + if (!ret || status.isEmpty()) return; + + auto line = status.front(); + auto token = line.split(' '); + + if (token.size() != 2) { + LOG_E() << "invalid response of command SERIALNO: " << line; + return; + } + + serial_number_ = token.back().trimmed(); + LOG_D() << "get smart card serial number: " << serial_number_; +} + +void SmartCardControllerDialog::fetch_smart_card_info() { + if (serial_number_.isEmpty()) return; + + auto [ret, status] = GpgAssuanHelper::GetInstance().SendStatusCommand( + GpgComponentType::kGPG_AGENT, "SCD LEARN --force " + serial_number_); + if (!ret || status.isEmpty()) return; + + LOG_D() << "fetch card raw info: " << status; + card_info_ = GpgOpenPGPCard(status); + + if (!card_info_.good) { + LOG_E() << "parse card raw info failed"; + return; + } +} + +void SmartCardControllerDialog::print_smart_card_info() { + if (!card_info_.good) return; + + QString html; + QTextStream out(&html); + auto card = card_info_; + + out << "<h2>" << tr("OpenPGP Card Information") << "</h2>"; + + out << "<h3>" << tr("Basic Information") << "</h3><ul>"; + out << "<li><b>" << tr("Reader:") << "</b> " << card.reader << "</li>"; + out << "<li><b>" << tr("Serial Number:") << "</b> " << card.serial_number + << "</li>"; + out << "<li><b>" << tr("Card Type:") << "</b> " << card.card_type << "</li>"; + out << "<li><b>" << tr("Card Version:") << "</b> " << card.card_version + << "</li>"; + out << "<li><b>" << tr("App Type:") << "</b> " << card.app_type << "</li>"; + out << "<li><b>" << tr("App Version:") << "</b> " << card.app_version + << "</li>"; + out << "<li><b>" << tr("Manufacturer:") << "</b> " << card.manufacturer + << "</li>"; + out << "<li><b>" << tr("Card Holder:") << "</b> " << card.card_holder + << "</li>"; + out << "<li><b>" << tr("Language:") << "</b> " << card.display_language + << "</li>"; + out << "<li><b>" << tr("Sex:") << "</b> " << card.display_sex << "</li>"; + out << "</ul>"; + + out << "<h3>" << tr("Status") << "</h3><ul>"; + out << "<li><b>" << tr("CHV Status:") << "</b> " << card.chv_status + << "</li>"; + out << "<li><b>" << tr("Signature Counter:") << "</b> " << card.sig_counter + << "</li>"; + out << "<li><b>" << tr("KDF:") << "</b> " << card.kdf << "</li>"; + out << "<li><b>" << tr("UIF1:") << "</b> " << card.uif1 << "</li>"; + out << "<li><b>" << tr("UIF2:") << "</b> " << card.uif2 << "</li>"; + out << "<li><b>" << tr("UIF3:") << "</b> " << card.uif3 << "</li>"; + out << "</ul>"; + + out << "<h3>" << tr("Fingerprints") << "</h3><ul>"; + for (auto it = card.fprs.begin(); it != card.fprs.end(); ++it) { + out << "<li><b>" << tr("Key %1:").arg(it.key()) << "</b> " << it.value() + << "</li>"; + } + out << "</ul>"; + + if (!card.card_infos.isEmpty()) { + out << "<h3>" << tr("Additional Info") << "</h3><ul>"; + for (auto it = card.card_infos.begin(); it != card.card_infos.end(); ++it) { + out << "<li><b>" << tr("%1:").arg(it.key()) << "</b> " << it.value() + << "</li>"; + } + out << "</ul>"; + } + + ui_->cardInfoEdit->setText(html); +} + +void SmartCardControllerDialog::slot_refresh() { + ui_->cardInfoEdit->clear(); + + get_smart_card_serial_number(); + fetch_smart_card_info(); + + print_smart_card_info(); + refresh_key_tree_view(); +} + +void SmartCardControllerDialog::refresh_key_tree_view() { + if (card_info_.fprs.isEmpty()) { + ui_->cardKeysTreeView->SetKeyFilter([](auto) { return false; }); + return; + } + + QStringList card_fprs(card_info_.fprs.begin(), card_info_.fprs.end()); + ui_->cardKeysTreeView->SetKeyFilter([=](const GpgAbstractKey* k) { + return card_fprs.contains(k->Fingerprint()); + }); +} + +} // namespace GpgFrontend::UI diff --git a/src/ui/dialog/controller/SmartCardControllerDialog.h b/src/ui/dialog/controller/SmartCardControllerDialog.h new file mode 100644 index 00000000..9b71bba7 --- /dev/null +++ b/src/ui/dialog/controller/SmartCardControllerDialog.h @@ -0,0 +1,85 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend 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 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include "core/model/GpgOpenPGPCard.h" +#include "ui/dialog/GeneralDialog.h" + +class Ui_SmartCardControllerDialog; + +namespace GpgFrontend::UI { +class SmartCardControllerDialog : public GeneralDialog { + Q_OBJECT + public: + /** + * @brief Construct a new Smart Card Controller Dialog object + * + * @param parent + */ + explicit SmartCardControllerDialog(QWidget* parent = nullptr); + + private slots: + + /** + * @brief + * + */ + void slot_refresh(); + + private: + QSharedPointer<Ui_SmartCardControllerDialog> ui_; ///< + int channel_; + QString serial_number_; + GpgOpenPGPCard card_info_; + + /** + * @brief Get the smart card serial number object + * + */ + void get_smart_card_serial_number(); + + /** + * @brief + * + */ + void fetch_smart_card_info(); + + /** + * @brief + * + */ + void print_smart_card_info(); + + /** + * @brief + * + */ + void refresh_key_tree_view(); +}; +} // namespace GpgFrontend::UI diff --git a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp index 79a28176..c4e93b62 100644 --- a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp +++ b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp @@ -389,7 +389,12 @@ void KeyPairSubkeyTab::slot_refresh_subkey_detail() { : tr("Not Exists")); // Show the situation if key in a smart card. - card_key_label_->setText(s_key.IsCardKey() ? tr("Yes") : tr("No")); + auto smart_card_info = s_key.IsCardKey() ? tr("Yes") : tr("No"); + if (s_key.IsCardKey() && !s_key.SmartCardSerialNumber().isEmpty()) { + smart_card_info += " "; + smart_card_info += "(" + s_key.SmartCardSerialNumber() + ")"; + } + card_key_label_->setText(smart_card_info); if (!s_key.IsSecretKey()) { auto palette_expired = master_key_exist_var_label_->palette(); diff --git a/src/ui/main_window/MainWindow.h b/src/ui/main_window/MainWindow.h index 9e5a289c..58aa406d 100644 --- a/src/ui/main_window/MainWindow.h +++ b/src/ui/main_window/MainWindow.h @@ -711,8 +711,10 @@ class MainWindow : public GeneralMainWindow { QAction* clean_double_line_breaks_act_{}; ///< Action to remove double ///< line breaks - QAction* gnupg_controller_open_act_{}; ///< - QAction* module_controller_open_act_{}; ///< + QAction* gnupg_controller_open_act_{}; ///< + QAction* module_controller_open_act_{}; ///< + QAction* smart_card_controller_open_act_{}; ///< + QAction* clean_gpg_password_cache_act_{}; ///< QAction* reload_components_act_{}; ///< QAction* restart_components_act_{}; ///< diff --git a/src/ui/main_window/MainWindowUI.cpp b/src/ui/main_window/MainWindowUI.cpp index a586a43b..6598aede 100644 --- a/src/ui/main_window/MainWindowUI.cpp +++ b/src/ui/main_window/MainWindowUI.cpp @@ -29,9 +29,10 @@ #include "MainWindow.h" #include "core/function/GlobalSettingStation.h" #include "core/module/ModuleManager.h" -#include "dialog/controller/ModuleControllerDialog.h" #include "ui/UserInterfaceUtils.h" #include "ui/dialog/controller/GnuPGControllerDialog.h" +#include "ui/dialog/controller/ModuleControllerDialog.h" +#include "ui/dialog/controller/SmartCardControllerDialog.h" #include "ui/dialog/help/AboutDialog.h" #include "ui/widgets/KeyList.h" #include "ui/widgets/TextEdit.h" @@ -255,6 +256,12 @@ void MainWindow::create_actions() { connect(module_controller_open_act_, &QAction::triggered, this, [this]() { (new ModuleControllerDialog(this))->exec(); }); + smart_card_controller_open_act_ = create_action( + "smart_card_controller_open", tr("Open Smart Card Controller"), + ":/icons/smart-card.png", tr("Open Smart Card Controller Dialog")); + connect(smart_card_controller_open_act_, &QAction::triggered, this, + [this]() { (new SmartCardControllerDialog(this))->exec(); }); + /** * E-Mail Menu */ @@ -462,6 +469,7 @@ void MainWindow::create_menus() { advance_menu_->addSeparator(); advance_menu_->addAction(gnupg_controller_open_act_); advance_menu_->addAction(module_controller_open_act_); + advance_menu_->addAction(smart_card_controller_open_act_); view_menu_ = menuBar()->addMenu(tr("View")); diff --git a/src/ui/model/GpgKeyTreeProxyModel.cpp b/src/ui/model/GpgKeyTreeProxyModel.cpp index e58cee88..2f46f548 100644 --- a/src/ui/model/GpgKeyTreeProxyModel.cpp +++ b/src/ui/model/GpgKeyTreeProxyModel.cpp @@ -125,4 +125,9 @@ void GpgKeyTreeProxyModel::slot_update_favorites_cache() { } } +void GpgKeyTreeProxyModel::SetKeyFilter(const KeyFilter &filter) { + custom_filter_ = filter; + invalidateFilter(); +} + } // namespace GpgFrontend::UI
\ No newline at end of file diff --git a/src/ui/model/GpgKeyTreeProxyModel.h b/src/ui/model/GpgKeyTreeProxyModel.h index 5e75dd84..f41afd4f 100644 --- a/src/ui/model/GpgKeyTreeProxyModel.h +++ b/src/ui/model/GpgKeyTreeProxyModel.h @@ -40,14 +40,39 @@ class GpgKeyTreeProxyModel : public QSortFilterProxyModel { public: using KeyFilter = std::function<bool(const GpgAbstractKey *)>; + /** + * @brief Construct a new Gpg Key Tree Proxy Model object + * + * @param model + * @param display_mode + * @param filter + * @param parent + */ explicit GpgKeyTreeProxyModel(QSharedPointer<GpgKeyTreeModel> model, GpgKeyTreeDisplayMode display_mode, KeyFilter filter, QObject *parent); + /** + * @brief Set the Search Keywords object + * + * @param keywords + */ void SetSearchKeywords(const QString &keywords); + /** + * @brief + * + * @param model + */ void ResetGpgKeyTableModel(QSharedPointer<GpgKeyTreeModel> model); + /** + * @brief Set the Key Filter object + * + * @param filter + */ + void SetKeyFilter(const KeyFilter &filter); + protected: [[nodiscard]] auto filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent) const -> bool override; diff --git a/src/ui/widgets/KeyTreeView.cpp b/src/ui/widgets/KeyTreeView.cpp index 3dde069a..3df5a440 100644 --- a/src/ui/widgets/KeyTreeView.cpp +++ b/src/ui/widgets/KeyTreeView.cpp @@ -31,10 +31,24 @@ #include <utility> #include "core/function/gpg/GpgKeyGetter.h" -#include "model/GpgKeyTreeProxyModel.h" +#include "core/utils/GpgUtils.h" +#include "ui/dialog/keypair_details/KeyDetailsDialog.h" +#include "ui/model/GpgKeyTreeProxyModel.h" namespace GpgFrontend::UI { +KeyTreeView::KeyTreeView(QWidget* parent) + : QTreeView(parent), + channel_(kGpgFrontendDefaultChannel), + model_(QSharedPointer<GpgKeyTreeModel>::create( + channel_, GpgKeyGetter::GetInstance(channel_).FetchKey(), + [](auto) { return false; }, this)), + proxy_model_( + model_, GpgKeyTreeDisplayMode::kALL, [](auto) { return false; }, + this) { + init(); +} + KeyTreeView::KeyTreeView(int channel, GpgKeyTreeModel::Detector checkable_detector, GpgKeyTreeProxyModel::KeyFilter filter, @@ -42,10 +56,37 @@ KeyTreeView::KeyTreeView(int channel, : QTreeView(parent), channel_(channel), model_(QSharedPointer<GpgKeyTreeModel>::create( - channel, GpgKeyGetter::GetInstance(channel_).FetchKey(), + channel_, GpgKeyGetter::GetInstance(channel_).FetchKey(), checkable_detector, this)), proxy_model_(model_, GpgKeyTreeDisplayMode::kALL, std::move(filter), this) { + init(); +} + +void KeyTreeView::paintEvent(QPaintEvent* event) { + QTreeView::paintEvent(event); + + if (!init_) { + slot_adjust_column_widths(); + init_ = true; + } +} + +void KeyTreeView::slot_adjust_column_widths() { + for (int i = 1; i < model_->columnCount({}); ++i) { + this->resizeColumnToContents(i); + } +} + +auto KeyTreeView::GetAllCheckedKeyIds() -> KeyIdArgsList { + return model_->GetAllCheckedKeyIds(); +} + +auto KeyTreeView::GetAllCheckedSubKey() -> QContainer<GpgSubKey> { + return model_->GetAllCheckedSubKey(); +} + +void KeyTreeView::init() { setModel(&proxy_model_); sortByColumn(2, Qt::AscendingOrder); @@ -64,29 +105,39 @@ KeyTreeView::KeyTreeView(int channel, setFocusPolicy(Qt::NoFocus); setAlternatingRowColors(true); setSortingEnabled(true); -} -void KeyTreeView::paintEvent(QPaintEvent* event) { - QTreeView::paintEvent(event); + connect(this, &QTableView::doubleClicked, this, + [this](const QModelIndex& index) { + if (!index.isValid() || index.column() == 0) return; - if (!init_) { - slot_adjust_column_widths(); - init_ = true; - } -} + QModelIndex source_index = proxy_model_.mapToSource(index); + auto key = + GetGpgKeyByGpgAbstractKey(model_->GetKeyByIndex(source_index)); -void KeyTreeView::slot_adjust_column_widths() { - for (int i = 1; i < model_->columnCount({}); ++i) { - this->resizeColumnToContents(i); - } + if (!key.IsGood()) { + QMessageBox::critical(this, tr("Error"), tr("Key Not Found.")); + return; + } + + new KeyDetailsDialog(model_->GetGpgContextChannel(), key, this); + }); } -auto KeyTreeView::GetAllCheckedKeyIds() -> KeyIdArgsList { - return model_->GetAllCheckedKeyIds(); +void KeyTreeView::SetKeyFilter(const GpgKeyTreeProxyModel::KeyFilter& filter) { + proxy_model_.SetKeyFilter(filter); } -auto KeyTreeView::GetAllCheckedSubKey() -> QContainer<GpgSubKey> { - return model_->GetAllCheckedSubKey(); +void KeyTreeView::SetChannel(int channel) { + if (channel_ == channel) return; + LOG_D() << "new channel for key tree view: " << channel; + + channel_ = channel; + init_ = false; + model_ = QSharedPointer<GpgKeyTreeModel>::create( + channel_, GpgKeyGetter::GetInstance(channel_).FetchKey(), + [](auto) { return false; }, this); + proxy_model_.setSourceModel(model_.get()); + proxy_model_.invalidate(); } } // namespace GpgFrontend::UI diff --git a/src/ui/widgets/KeyTreeView.h b/src/ui/widgets/KeyTreeView.h index a362a8a1..0e12ad0b 100644 --- a/src/ui/widgets/KeyTreeView.h +++ b/src/ui/widgets/KeyTreeView.h @@ -48,6 +48,16 @@ class KeyTreeView : public QTreeView { * @param _info_type * @param _filter */ + explicit KeyTreeView(QWidget* parent = nullptr); + + /** + * @brief Construct a new Key Table object + * + * @param _key_list + * @param _select_type + * @param _info_type + * @param _filter + */ explicit KeyTreeView(int channel, GpgKeyTreeModel::Detector checkable_detector, GpgKeyTreeProxyModel::KeyFilter filter, @@ -67,6 +77,20 @@ class KeyTreeView : public QTreeView { */ auto GetAllCheckedSubKey() -> QContainer<GpgSubKey>; + /** + * @brief Set the Key Filter object + * + * @param filter + */ + void SetKeyFilter(const GpgKeyTreeProxyModel::KeyFilter& filter); + + /** + * @brief + * + * @param channel + */ + void SetChannel(int channel); + protected: /** * @brief @@ -81,6 +105,8 @@ class KeyTreeView : public QTreeView { GpgKeyTreeProxyModel proxy_model_; void slot_adjust_column_widths(); + + void init(); }; } // namespace GpgFrontend::UI
\ No newline at end of file diff --git a/ui/SmartCardControllerDialog.ui b/ui/SmartCardControllerDialog.ui new file mode 100644 index 00000000..537862fc --- /dev/null +++ b/ui/SmartCardControllerDialog.ui @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SmartCardControllerDialog</class> + <widget class="QDialog" name="SmartCardControllerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>858</width> + <height>571</height> + </rect> + </property> + <property name="windowTitle"> + <string>SmartCardController</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QTextEdit" name="cardInfoEdit"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="GpgFrontend::UI::KeyTreeView" name="cardKeysTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Operations</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>Change Name of Card Holder</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_3"> + <property name="text"> + <string>Move Key to Card</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Orientation::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Orientation::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QComboBox" name="keyDBIndexComboBox"/> + </item> + <item> + <widget class="QPushButton" name="refreshButton"> + <property name="text"> + <string>Refresh</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>GpgFrontend::UI::KeyTreeView</class> + <extends>QTreeView</extends> + <header>ui/widgets/KeyTreeView.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> |