From 571cfb16ccfd7ac6bc59b5acc77a94d0bdcf0990 Mon Sep 17 00:00:00 2001 From: saturneric Date: Sun, 13 Apr 2025 01:03:57 +0200 Subject: feat: add openpgp smart card support --- .../controller/SmartCardControllerDialog.cpp | 173 +++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/ui/dialog/controller/SmartCardControllerDialog.cpp (limited to 'src/ui/dialog/controller/SmartCardControllerDialog.cpp') 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 + * + * 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 . + * + * 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 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::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(&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 << "

" << tr("OpenPGP Card Information") << "

"; + + out << "

" << tr("Basic Information") << "

    "; + out << "
  • " << tr("Reader:") << " " << card.reader << "
  • "; + out << "
  • " << tr("Serial Number:") << " " << card.serial_number + << "
  • "; + out << "
  • " << tr("Card Type:") << " " << card.card_type << "
  • "; + out << "
  • " << tr("Card Version:") << " " << card.card_version + << "
  • "; + out << "
  • " << tr("App Type:") << " " << card.app_type << "
  • "; + out << "
  • " << tr("App Version:") << " " << card.app_version + << "
  • "; + out << "
  • " << tr("Manufacturer:") << " " << card.manufacturer + << "
  • "; + out << "
  • " << tr("Card Holder:") << " " << card.card_holder + << "
  • "; + out << "
  • " << tr("Language:") << " " << card.display_language + << "
  • "; + out << "
  • " << tr("Sex:") << " " << card.display_sex << "
  • "; + out << "
"; + + out << "

" << tr("Status") << "

    "; + out << "
  • " << tr("CHV Status:") << " " << card.chv_status + << "
  • "; + out << "
  • " << tr("Signature Counter:") << " " << card.sig_counter + << "
  • "; + out << "
  • " << tr("KDF:") << " " << card.kdf << "
  • "; + out << "
  • " << tr("UIF1:") << " " << card.uif1 << "
  • "; + out << "
  • " << tr("UIF2:") << " " << card.uif2 << "
  • "; + out << "
  • " << tr("UIF3:") << " " << card.uif3 << "
  • "; + out << "
"; + + out << "

" << tr("Fingerprints") << "

    "; + for (auto it = card.fprs.begin(); it != card.fprs.end(); ++it) { + out << "
  • " << tr("Key %1:").arg(it.key()) << " " << it.value() + << "
  • "; + } + out << "
"; + + if (!card.card_infos.isEmpty()) { + out << "

" << tr("Additional Info") << "

    "; + for (auto it = card.card_infos.begin(); it != card.card_infos.end(); ++it) { + out << "
  • " << tr("%1:").arg(it.key()) << " " << it.value() + << "
  • "; + } + out << "
"; + } + + 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 -- cgit v1.2.3