diff options
Diffstat (limited to 'src/core/function/gpg/GpgSmartCardManager.cpp')
-rw-r--r-- | src/core/function/gpg/GpgSmartCardManager.cpp | 171 |
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 |