aboutsummaryrefslogtreecommitdiffstats
path: root/src/core/function/gpg/GpgSmartCardManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/function/gpg/GpgSmartCardManager.cpp')
-rw-r--r--src/core/function/gpg/GpgSmartCardManager.cpp171
1 files changed, 171 insertions, 0 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