aboutsummaryrefslogtreecommitdiffstats
path: root/src/core/function/gpg
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2025-04-13 20:35:24 +0000
committersaturneric <[email protected]>2025-04-13 20:35:24 +0000
commit1009c988f93f3c470ed65d2603c3089655bb270a (patch)
tree68bc1b8d033537d8564cf65492fb5f34195a222f /src/core/function/gpg
parentfeat: add SmartCardController (diff)
downloadGpgFrontend-1009c988f93f3c470ed65d2603c3089655bb270a.tar.gz
GpgFrontend-1009c988f93f3c470ed65d2603c3089655bb270a.zip
refactor: code cleanup
Diffstat (limited to 'src/core/function/gpg')
-rw-r--r--src/core/function/gpg/GpgSmartCardManager.cpp171
-rw-r--r--src/core/function/gpg/GpgSmartCardManager.h39
2 files changed, 209 insertions, 1 deletions
diff --git a/src/core/function/gpg/GpgSmartCardManager.cpp b/src/core/function/gpg/GpgSmartCardManager.cpp
index 06010756..b4b660cf 100644
--- a/src/core/function/gpg/GpgSmartCardManager.cpp
+++ b/src/core/function/gpg/GpgSmartCardManager.cpp
@@ -84,4 +84,175 @@ auto GpgSmartCardManager::Fetch(const QString& serial_number) -> bool {
.DoCardInteract(next_state_handler, action_handler);
}
+auto GpgSmartCardManager::GetSerialNumbers() -> QStringList {
+ auto [r, s] = assuan_.SendStatusCommand(GpgComponentType::kGPG_AGENT,
+ "SCD SERIALNO --all");
+ if (!r) {
+ cached_scd_serialno_status_hash_.clear();
+ cache_scd_card_serial_numbers_.clear();
+ return {};
+ }
+
+ auto hash =
+ QCryptographicHash::hash(s.join(' ').toUtf8(), QCryptographicHash::Sha1)
+ .toHex();
+ // check and skip
+ if (cached_scd_serialno_status_hash_ == hash) {
+ return cache_scd_card_serial_numbers_;
+ }
+
+ cached_scd_serialno_status_hash_.clear();
+ cache_scd_card_serial_numbers_.clear();
+ auto [ret, status] = assuan_.SendStatusCommand(GpgComponentType::kGPG_AGENT,
+ "SCD GETINFO all_active_apps");
+ if (!ret || status.empty()) {
+ LOG_D() << "command SCD GETINFO all_active_apps failed, resetting...";
+ return {};
+ }
+
+ for (const auto& line : status) {
+ auto tokens = line.split(' ');
+
+ if (tokens.size() < 2 || tokens[0] != "SERIALNO") {
+ LOG_E() << "invalid response of command GETINFO all_active_apps: "
+ << line;
+ continue;
+ }
+
+ auto serial_number = tokens[1];
+ if (!line.contains("openpgp")) {
+ LOG_W() << "smart card: " << serial_number << "doesn't support openpgp.";
+ continue;
+ }
+
+ cache_scd_card_serial_numbers_.append(serial_number);
+ }
+
+ cached_scd_serialno_status_hash_ = hash;
+ return cache_scd_card_serial_numbers_;
+}
+
+auto GpgSmartCardManager::SelectCardBySerialNumber(const QString& serial_number)
+ -> std::tuple<bool, QString> {
+ if (serial_number.isEmpty()) return {false, "Serial Number is empty."};
+
+ auto [ret, status] = assuan_.SendStatusCommand(
+ GpgComponentType::kGPG_AGENT,
+ QString("SCD SERIALNO --demand=%1 openpgp").arg(serial_number));
+ if (!ret || status.isEmpty()) {
+ return {false, status.join(' ')};
+ }
+
+ auto line = status.front();
+ auto token = line.split(' ');
+
+ if (token.size() != 2) {
+ LOG_E() << "invalid response of command SERIALNO: " << line;
+ return {false, line};
+ }
+
+ LOG_D() << "selected smart card by serial number: " << serial_number;
+
+ return {true, {}};
+}
+
+auto GpgSmartCardManager::FetchCardInfoBySerialNumber(
+ const QString& serial_number) -> QSharedPointer<GpgOpenPGPCard> {
+ if (serial_number.trimmed().isEmpty()) return nullptr;
+
+ auto [ret, status] = assuan_.SendStatusCommand(
+ GpgComponentType::kGPG_AGENT, "SCD LEARN --force " + serial_number);
+ if (!ret || status.isEmpty()) {
+ LOG_E() << "scd learn failed: " << status;
+ return nullptr;
+ }
+
+ auto card_info = GpgOpenPGPCard(status);
+ if (!card_info.good) {
+ return nullptr;
+ }
+
+ return QSharedPointer<GpgOpenPGPCard>::create(card_info);
+}
+
+auto PercentDataEscape(const QByteArray& data, bool plus_escape = false,
+ const QString& prefix = QString()) -> QString {
+ QString result;
+
+ if (!prefix.isEmpty()) {
+ for (QChar ch : prefix) {
+ if (ch == '%' || ch.unicode() < 0x20) {
+ result += QString("%%%1")
+ .arg(ch.unicode(), 2, 16, QLatin1Char('0'))
+ .toUpper();
+ } else {
+ result += ch;
+ }
+ }
+ }
+
+ for (unsigned char ch : data) {
+ if (ch == '\0') {
+ result += "%00";
+ } else if (ch == '%') {
+ result += "%25";
+ } else if (plus_escape && ch == ' ') {
+ result += '+';
+ } else if (plus_escape && (ch < 0x20 || ch == '+')) {
+ result += QString("%%%1").arg(ch, 2, 16, QLatin1Char('0')).toUpper();
+ } else {
+ result += QLatin1Char(ch);
+ }
+ }
+
+ return result;
+}
+
+auto GpgSmartCardManager::ModifyAttr(const QString& attr, const QString& value)
+ -> std::tuple<bool, QString> {
+ if (attr.trimmed().isEmpty() || value.trimmed().isEmpty()) {
+ return {false, "ATTR or Value is empty"};
+ }
+
+ const auto command = QString("SCD SETATTR %1 ").arg(attr);
+ const auto escaped_command =
+ PercentDataEscape(value.trimmed().toUtf8(), true, command);
+
+ auto [r, s] =
+ assuan_.SendStatusCommand(GpgComponentType::kGPG_AGENT, escaped_command);
+
+ if (!r) {
+ LOG_E() << "SCD SETATTR command failed for attr" << attr;
+ return {false, s.join(' ')};
+ }
+
+ return {true, {}};
+}
+
+auto GpgSmartCardManager::ModifyPin(const QString& pin_ref)
+ -> std::tuple<bool, QString> {
+ if (pin_ref.trimmed().isEmpty()) return {false, "PIN Reference is empty"};
+
+ QString command;
+ if (pin_ref == "OPENPGP.1") {
+ command = "SCD PASSWD OPENPGP.1";
+ } else if (pin_ref == "OPENPGP.3") {
+ command = "SCD PASSWD OPENPGP.3";
+ } else if (pin_ref == "OPENPGP.2") {
+ command = "SCD PASSWD --reset OPENPGP.2";
+ } else {
+ command = QString("SCD PASSWD %1").arg(pin_ref);
+ }
+
+ auto [success, status] =
+ assuan_.SendStatusCommand(GpgComponentType::kGPG_AGENT, command);
+
+ if (!success) {
+ LOG_E() << "modify pin of smart failed: " << status;
+ return {false, status.join(' ')};
+ }
+
+ return {true, {}};
+}
+
} // namespace GpgFrontend \ No newline at end of file
diff --git a/src/core/function/gpg/GpgSmartCardManager.h b/src/core/function/gpg/GpgSmartCardManager.h
index 9c8cc8bb..2d3038b4 100644
--- a/src/core/function/gpg/GpgSmartCardManager.h
+++ b/src/core/function/gpg/GpgSmartCardManager.h
@@ -29,7 +29,9 @@
#pragma once
#include "core/function/basic/GpgFunctionObject.h"
+#include "core/function/gpg/GpgAssuanHelper.h"
#include "core/function/gpg/GpgContext.h"
+#include "core/model/GpgOpenPGPCard.h"
#include "core/typedef/GpgTypedef.h"
namespace GpgFrontend {
@@ -50,6 +52,28 @@ class GPGFRONTEND_CORE_EXPORT GpgSmartCardManager
int channel = SingletonFunctionObject::GetDefaultChannel());
/**
+ * @brief Get the Serial Numbers object
+ *
+ * @return QStringList
+ */
+ auto GetSerialNumbers() -> QStringList;
+
+ /**
+ * @brief
+ *
+ * @return std::tuple<bool, QString>
+ */
+ auto SelectCardBySerialNumber(const QString&) -> std::tuple<bool, QString>;
+
+ /**
+ * @brief
+ *
+ * @return QSharedPointer<GpgOpenPGPCard>
+ */
+ auto FetchCardInfoBySerialNumber(const QString&)
+ -> QSharedPointer<GpgOpenPGPCard>;
+
+ /**
* @brief
*
* @param key
@@ -64,11 +88,24 @@ class GPGFRONTEND_CORE_EXPORT GpgSmartCardManager
*
* @return std::tuple<bool, QString>
*/
- auto ModifyAttr() -> std::tuple<bool, QString>;
+ auto ModifyAttr(const QString& attr,
+ const QString& value) -> std::tuple<bool, QString>;
+
+ /**
+ * @brief
+ *
+ * @param pin_ref
+ * @return std::tuple<bool, QString>
+ */
+ auto ModifyPin(const QString& pin_ref) -> std::tuple<bool, QString>;
private:
GpgContext& ctx_ =
GpgContext::GetInstance(SingletonFunctionObject::GetChannel()); ///<
+ GpgAssuanHelper& assuan_ =
+ GpgAssuanHelper::GetInstance(SingletonFunctionObject::GetChannel()); ///<
+ QString cached_scd_serialno_status_hash_;
+ QContainer<QString> cache_scd_card_serial_numbers_;
};
} // namespace GpgFrontend