aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2024-11-19 15:00:56 +0000
committersaturneric <[email protected]>2024-11-19 15:00:56 +0000
commit5b883eebb6992e00a8979000a64e72ff1aae9432 (patch)
tree4a52c8620e3cb02d2ca3d4b2112c553239ada5bb /src
parentfeat: add delete subkey function (diff)
downloadGpgFrontend-5b883eebb6992e00a8979000a64e72ff1aae9432.tar.gz
GpgFrontend-5b883eebb6992e00a8979000a64e72ff1aae9432.zip
feat: add revoke subkey function
Diffstat (limited to 'src')
-rw-r--r--src/core/function/gpg/GpgKeyManager.cpp237
-rw-r--r--src/core/function/gpg/GpgKeyManager.h13
-rw-r--r--src/test/core/GpgCoreTestKeyManagement.cpp38
-rw-r--r--src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp103
-rw-r--r--src/ui/dialog/keypair_details/KeyPairSubkeyTab.h6
5 files changed, 330 insertions, 67 deletions
diff --git a/src/core/function/gpg/GpgKeyManager.cpp b/src/core/function/gpg/GpgKeyManager.cpp
index bd52c341..753b668f 100644
--- a/src/core/function/gpg/GpgKeyManager.cpp
+++ b/src/core/function/gpg/GpgKeyManager.cpp
@@ -36,6 +36,64 @@
GpgFrontend::GpgKeyManager::GpgKeyManager(int channel)
: SingletonFunctionObject<GpgKeyManager>(channel) {}
+auto GpgFrontend::GpgKeyManager::interactor_cb_fnc(void* handle,
+ const char* status,
+ const char* args,
+ int fd) -> gpgme_error_t {
+ auto* handle_struct = static_cast<AutomatonHandelStruct*>(handle);
+ QString status_s = status;
+ QString args_s = args;
+
+ if (status_s == "KEY_CONSIDERED") {
+ auto tokens = QString(args).split(' ');
+
+ if (tokens.empty() || tokens[0] != handle_struct->KeyFpr()) {
+ LOG_W() << "handle struct key fpr " << handle_struct->KeyFpr()
+ << "mismatch token: " << tokens[0] << ", exit...";
+
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (status_s == "GOT_IT" || status_s.isEmpty()) {
+ FLOG_D("gpg reply is GOT_IT, continue...");
+ return 0;
+ }
+
+ LOG_D() << "current state" << handle_struct->CurrentStatus()
+ << "gpg status: " << status_s << ", args: " << args_s;
+
+ AutomatonState next_state = handle_struct->NextState(status_s, args_s);
+ if (next_state == AS_ERROR) {
+ FLOG_D("handle struct next state caught error, abort...");
+ return -1;
+ }
+ LOG_D() << "next state" << next_state;
+
+ if (next_state == AS_SAVE) {
+ handle_struct->SetSuccess(true);
+ }
+
+ // set state and preform action
+ handle_struct->SetStatus(next_state);
+ Command cmd = handle_struct->Action();
+
+ LOG_D() << "next action, cmd:" << cmd;
+
+ if (!cmd.isEmpty()) {
+ auto btye_array = cmd.toUtf8();
+ gpgme_io_write(fd, btye_array, btye_array.size());
+ gpgme_io_write(fd, "\n", 1);
+ } else if (status_s == "GET_LINE") {
+ // avoid trapping in this state
+ return GPG_ERR_FALSE;
+ }
+
+ return 0;
+}
+
auto GpgFrontend::GpgKeyManager::SignKey(
const GpgFrontend::GpgKey& target, GpgFrontend::KeyArgsList& keys,
const QString& uid, const std::unique_ptr<QDateTime>& expires) -> bool {
@@ -179,69 +237,110 @@ auto GpgFrontend::GpgKeyManager::SetOwnerTrustLevel(const GpgKey& key,
return CheckGpgError(err) == GPG_ERR_NO_ERROR && handel_struct.Success();
}
-auto GpgFrontend::GpgKeyManager::interactor_cb_fnc(void* handle,
- const char* status,
- const char* args,
- int fd) -> gpgme_error_t {
- auto* handle_struct = static_cast<AutomatonHandelStruct*>(handle);
- QString status_s = status;
- QString args_s = args;
-
- if (status_s == "KEY_CONSIDERED") {
- auto tokens = QString(args).split(' ');
-
- if (tokens.empty() || tokens[0] != handle_struct->KeyFpr()) {
- LOG_W() << "handle struct key fpr " << handle_struct->KeyFpr()
- << "mismatch token: " << tokens[0] << ", exit...";
-
- return -1;
- }
-
- return 0;
- }
-
- if (status_s == "GOT_IT" || status_s.isEmpty()) {
- FLOG_D("status GOT_IT, continue...");
- return 0;
+auto GpgFrontend::GpgKeyManager::DeleteSubkey(const GpgKey& key,
+ int subkey_index) -> bool {
+ if (subkey_index < 0 || subkey_index >= key.GetSubKeys()->size()) {
+ LOG_W() << "illegal subkey index: " << subkey_index;
}
- LOG_D() << "current state" << handle_struct->CurrentStatus()
- << "gpg status: " << status_s << ", args: " << args_s;
-
- AutomatonState next_state = handle_struct->NextState(status_s, args_s);
- if (next_state == AS_ERROR) {
- FLOG_D("handle struct next state caught error, abort...");
- return -1;
- }
- LOG_D() << "next state" << next_state;
+ AutomatonNextStateHandler next_state_handler =
+ [](AutomatonState state, QString status, QString args) {
+ auto tokens = args.split(' ');
- if (next_state == AS_SAVE) {
- handle_struct->SetSuccess(true);
- }
+ switch (state) {
+ case AS_START:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_SELECT;
+ }
+ return AS_ERROR;
+ case AS_SELECT:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_COMMAND;
+ }
+ return AS_ERROR;
+ case AS_COMMAND:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_QUIT;
+ } else if (status == "GET_BOOL" &&
+ args == "keyedit.remove.subkey.okay") {
+ return AS_REALLY_ULTIMATE;
+ }
+ return AS_ERROR;
+ case AS_REALLY_ULTIMATE:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_QUIT;
+ }
+ return AS_ERROR;
+ case AS_QUIT:
+ if (status == "GET_BOOL" && args == "keyedit.save.okay") {
+ return AS_SAVE;
+ }
+ return AS_ERROR;
+ case AS_ERROR:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_QUIT;
+ }
+ return AS_ERROR;
+ default:
+ return AS_ERROR;
+ };
+ };
- // set state and preform action
- handle_struct->SetStatus(next_state);
- Command cmd = handle_struct->Action();
+ AutomatonActionHandler action_handler =
+ [subkey_index](AutomatonHandelStruct& handler, AutomatonState state) {
+ switch (state) {
+ case AS_SELECT:
+ return QString("key %1").arg(subkey_index);
+ case AS_COMMAND:
+ return QString("delkey");
+ case AS_REALLY_ULTIMATE:
+ handler.SetSuccess(true);
+ return QString("Y");
+ case AS_QUIT:
+ return QString("quit");
+ case AS_SAVE:
+ handler.SetSuccess(true);
+ return QString("Y");
+ case AS_START:
+ case AS_ERROR:
+ return QString("");
+ default:
+ return QString("");
+ }
+ return QString("");
+ };
- LOG_D() << "next action, cmd:" << cmd;
+ auto key_fpr = key.GetFingerprint();
+ AutomatonHandelStruct handel_struct(key_fpr);
+ handel_struct.SetHandler(next_state_handler, action_handler);
- if (!cmd.isEmpty()) {
- auto btye_array = cmd.toUtf8();
- gpgme_io_write(fd, btye_array, btye_array.size());
- gpgme_io_write(fd, "\n", 1);
- } else if (status_s == "GET_LINE") {
- // avoid trapping in this state
- return GPG_ERR_FALSE;
- }
+ GpgData data_out;
- return 0;
+ auto err =
+ gpgme_op_interact(ctx_.DefaultContext(), static_cast<gpgme_key_t>(key), 0,
+ GpgKeyManager::interactor_cb_fnc,
+ static_cast<void*>(&handel_struct), data_out);
+ return CheckGpgError(err) == GPG_ERR_NO_ERROR && handel_struct.Success();
}
-auto GpgFrontend::GpgKeyManager::DeleteSubkey(const GpgKey& key,
- int subkey_index) -> bool {
+
+auto GpgFrontend::GpgKeyManager::RevokeSubkey(
+ const GpgKey& key, int subkey_index, int reason_code,
+ const QString& reason_text) -> bool {
if (subkey_index < 0 || subkey_index >= key.GetSubKeys()->size()) {
LOG_W() << "illegal subkey index: " << subkey_index;
}
+ // dealing with reason text
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 4)
+ auto reason_text_lines =
+ GpgFrontend::SecureCreateSharedObject<QList<QString>>(
+ reason_text.split('\n', Qt::SkipEmptyParts).toVector());
+#else
+ auto reason_text_lines =
+ GpgFrontend::SecureCreateSharedObject<QVector<QString>>(
+ reason_text.split('\n', Qt::SkipEmptyParts).toVector());
+#endif
+
AutomatonNextStateHandler next_state_handler =
[](AutomatonState state, QString status, QString args) {
auto tokens = args.split(' ');
@@ -261,13 +360,35 @@ auto GpgFrontend::GpgKeyManager::DeleteSubkey(const GpgKey& key,
if (status == "GET_LINE" && args == "keyedit.prompt") {
return AS_QUIT;
} else if (status == "GET_BOOL" &&
- args == "keyedit.remove.subkey.okay") {
+ args == "keyedit.revoke.subkey.okay") {
+ return AS_REALLY_ULTIMATE;
+ }
+ return AS_ERROR;
+ case AS_REASON_CODE:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_QUIT;
+ } else if (status == "GET_LINE" &&
+ args == "ask_revocation_reason.text") {
+ return AS_REASON_TEXT;
+ }
+ return AS_ERROR;
+ case AS_REASON_TEXT:
+ if (status == "GET_LINE" && args == "keyedit.prompt") {
+ return AS_QUIT;
+ } else if (status == "GET_LINE" &&
+ args == "ask_revocation_reason.text") {
+ return AS_REASON_TEXT;
+ } else if (status == "GET_BOOL" &&
+ args == "ask_revocation_reason.okay") {
return AS_REALLY_ULTIMATE;
}
return AS_ERROR;
case AS_REALLY_ULTIMATE:
if (status == "GET_LINE" && args == "keyedit.prompt") {
return AS_QUIT;
+ } else if (status == "GET_LINE" &&
+ args == "ask_revocation_reason.code") {
+ return AS_REASON_CODE;
}
return AS_ERROR;
case AS_QUIT:
@@ -286,14 +407,20 @@ auto GpgFrontend::GpgKeyManager::DeleteSubkey(const GpgKey& key,
};
AutomatonActionHandler action_handler =
- [subkey_index](AutomatonHandelStruct& handler, AutomatonState state) {
+ [subkey_index, reason_code, reason_text_lines](
+ AutomatonHandelStruct& handler, AutomatonState state) {
switch (state) {
case AS_SELECT:
return QString("key %1").arg(subkey_index);
case AS_COMMAND:
- return QString("delkey");
+ return QString("revkey");
+ case AS_REASON_CODE:
+ return QString::number(reason_code);
+ case AS_REASON_TEXT:
+ return reason_text_lines->isEmpty()
+ ? QString("")
+ : QString(reason_text_lines->takeFirst().toUtf8());
case AS_REALLY_ULTIMATE:
- handler.SetSuccess(true);
return QString("Y");
case AS_QUIT:
return QString("quit");
diff --git a/src/core/function/gpg/GpgKeyManager.h b/src/core/function/gpg/GpgKeyManager.h
index 83a38d05..986e835f 100644
--- a/src/core/function/gpg/GpgKeyManager.h
+++ b/src/core/function/gpg/GpgKeyManager.h
@@ -101,6 +101,17 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyManager
*/
auto DeleteSubkey(const GpgKey& key, int subkey_index) -> bool;
+ /**
+ * @brief
+ *
+ * @param key
+ * @param subkey_index
+ * @return true
+ * @return false
+ */
+ auto RevokeSubkey(const GpgKey& key, int subkey_index, int reason_code,
+ const QString& reason_text) -> bool;
+
private:
static auto interactor_cb_fnc(void* handle, const char* status,
const char* args, int fd) -> gpgme_error_t;
@@ -111,6 +122,8 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyManager
AS_SELECT,
AS_COMMAND,
AS_VALUE,
+ AS_REASON_CODE,
+ AS_REASON_TEXT,
AS_REALLY_ULTIMATE,
AS_SAVE,
AS_ERROR,
diff --git a/src/test/core/GpgCoreTestKeyManagement.cpp b/src/test/core/GpgCoreTestKeyManagement.cpp
index 114c237a..135a7e51 100644
--- a/src/test/core/GpgCoreTestKeyManagement.cpp
+++ b/src/test/core/GpgCoreTestKeyManagement.cpp
@@ -107,6 +107,7 @@ cBEIUb80jrN959lF8eobqrVouY5GyvZXVZFGoXS4OTkFAwlEZxWBxJw=
)";
namespace GpgFrontend::Test {
+
TEST_F(GpgCoreTest, CoreDeleteSubkeyTestA) {
auto info = GpgKeyImportExporter::GetInstance().ImportKey(
GFBuffer(QString::fromLatin1(TEST_PRIVATE_KEY_DATA)));
@@ -127,6 +128,7 @@ TEST_F(GpgCoreTest, CoreDeleteSubkeyTestA) {
ASSERT_TRUE(res);
+ GpgKeyGetter::GetInstance().FlushKeyCache();
key = GpgKeyGetter::GetInstance(kGpgFrontendDefaultChannel)
.GetKey("822D7E13F5B85D7D");
ASSERT_TRUE(key.IsGood());
@@ -213,4 +215,40 @@ TEST_F(GpgCoreTest, CoreSetOwnerTrustA) {
GpgKeyOpera::GetInstance().DeleteKey(key.GetId());
}
+TEST_F(GpgCoreTest, CoreRevokeSubkeyTestA) {
+ auto info = GpgKeyImportExporter::GetInstance().ImportKey(
+ GFBuffer(QString::fromLatin1(TEST_PRIVATE_KEY_DATA)));
+
+ ASSERT_EQ(info->not_imported, 0);
+ ASSERT_EQ(info->imported, 1);
+
+ auto key = GpgKeyGetter::GetInstance(kGpgFrontendDefaultChannel)
+ .GetKey("822D7E13F5B85D7D");
+ ASSERT_TRUE(key.IsGood());
+
+ auto subkeys = key.GetSubKeys();
+
+ ASSERT_EQ(subkeys->size(), 5);
+ ASSERT_EQ((*subkeys)[2].GetID(), "2D1F9FC59B568A8C");
+
+ auto res = GpgKeyManager::GetInstance().RevokeSubkey(
+ key, 2, 2, QString("H\nE\nLL\nO\n\n"));
+
+ ASSERT_TRUE(res);
+
+ GpgKeyGetter::GetInstance().FlushKeyCache();
+ key = GpgKeyGetter::GetInstance(kGpgFrontendDefaultChannel)
+ .GetKey("822D7E13F5B85D7D");
+ ASSERT_TRUE(key.IsGood());
+
+ subkeys = key.GetSubKeys();
+
+ ASSERT_EQ(subkeys->size(), 5);
+ ASSERT_EQ((*subkeys)[2].GetID(), "2D1F9FC59B568A8C");
+
+ ASSERT_TRUE((*subkeys)[2].IsRevoked());
+
+ GpgKeyOpera::GetInstance().DeleteKey(key.GetId());
+}
+
} // namespace GpgFrontend::Test \ No newline at end of file
diff --git a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp
index 335f9c53..ded630e7 100644
--- a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp
+++ b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp
@@ -37,6 +37,7 @@
#include "core/utils/IOUtils.h"
#include "ui/UISignalStation.h"
#include "ui/UserInterfaceUtils.h"
+#include "ui/dialog/RevocationOptionsDialog.h"
namespace GpgFrontend::UI {
@@ -85,14 +86,16 @@ KeyPairSubkeyTab::KeyPairSubkeyTab(int channel, const QString& key_id,
subkey_detail_layout->addWidget(
new QLabel(tr("Create Date (Local Time)") + ": "), 7, 0);
subkey_detail_layout->addWidget(new QLabel(tr("Existence") + ": "), 8, 0);
- subkey_detail_layout->addWidget(new QLabel(tr("Key in Smart Card") + ": "), 9,
- 0);
- subkey_detail_layout->addWidget(new QLabel(tr("Fingerprint") + ": "), 10, 0);
+ subkey_detail_layout->addWidget(new QLabel(tr("Revoked") + ": "), 9, 0);
+ subkey_detail_layout->addWidget(new QLabel(tr("Key in Smart Card") + ": "),
+ 10, 0);
+ subkey_detail_layout->addWidget(new QLabel(tr("Fingerprint") + ": "), 11, 0);
key_type_var_label_ = new QLabel(this);
key_id_var_label_ = new QLabel(this);
key_size_var_label_ = new QLabel(this);
expire_var_label_ = new QLabel(this);
+ revoke_var_label_ = new QLabel(this);
algorithm_var_label_ = new QLabel(this);
algorithm_detail_var_label_ = new QLabel(this);
created_var_label_ = new QLabel(this);
@@ -114,8 +117,9 @@ KeyPairSubkeyTab::KeyPairSubkeyTab(int channel, const QString& key_id,
subkey_detail_layout->addWidget(expire_var_label_, 6, 1, 1, 2);
subkey_detail_layout->addWidget(created_var_label_, 7, 1, 1, 2);
subkey_detail_layout->addWidget(master_key_exist_var_label_, 8, 1, 1, 2);
- subkey_detail_layout->addWidget(card_key_label_, 9, 1, 1, 2);
- subkey_detail_layout->addWidget(fingerprint_var_label_, 10, 1, 1, 2);
+ subkey_detail_layout->addWidget(revoke_var_label_, 9, 1, 1, 2);
+ subkey_detail_layout->addWidget(card_key_label_, 10, 1, 1, 2);
+ subkey_detail_layout->addWidget(fingerprint_var_label_, 11, 1, 1, 2);
export_subkey_button_ = new QPushButton(tr("Export Subkey"));
export_subkey_button_->setFlat(true);
@@ -191,7 +195,6 @@ void KeyPairSubkeyTab::slot_refresh_subkey_list() {
this->buffered_subkeys_.clear();
auto sub_keys = key_.GetSubKeys();
for (auto& sub_key : *sub_keys) {
- if (sub_key.IsDisabled() || sub_key.IsRevoked()) continue;
this->buffered_subkeys_.push_back(std::move(sub_key));
}
@@ -232,12 +235,20 @@ void KeyPairSubkeyTab::slot_refresh_subkey_list() {
tmp6->setTextAlignment(Qt::AlignCenter);
subkey_list_->setItem(row, 6, tmp6);
- if (!row) {
+ if (row == 0) {
for (auto i = 0; i < subkey_list_->columnCount(); i++) {
subkey_list_->item(row, i)->setForeground(QColor(65, 105, 255));
}
}
+ if (subkey.IsExpired() || subkey.IsRevoked()) {
+ for (auto i = 0; i < subkey_list_->columnCount(); i++) {
+ auto font = subkey_list_->item(row, i)->font();
+ font.setStrikeOut(true);
+ subkey_list_->item(row, i)->setFont(font);
+ }
+ }
+
row++;
}
@@ -332,6 +343,17 @@ void KeyPairSubkeyTab::slot_refresh_subkey_detail() {
key_type_var_label_->setText(
subkey.IsHasCertificationCapability() ? tr("Primary Key") : tr("Subkey"));
+
+ revoke_var_label_->setText(subkey.IsRevoked() ? tr("Yes") : tr("No"));
+ if (!subkey.IsRevoked()) {
+ auto palette_expired = revoke_var_label_->palette();
+ palette_expired.setColor(revoke_var_label_->foregroundRole(), Qt::red);
+ revoke_var_label_->setPalette(palette_expired);
+ } else {
+ auto palette_valid = revoke_var_label_->palette();
+ palette_valid.setColor(revoke_var_label_->foregroundRole(), Qt::darkGreen);
+ revoke_var_label_->setPalette(palette_valid);
+ }
}
void KeyPairSubkeyTab::create_subkey_opera_menu() {
@@ -348,8 +370,13 @@ void KeyPairSubkeyTab::create_subkey_opera_menu() {
connect(delete_subkey_act_, &QAction::triggered, this,
&KeyPairSubkeyTab::slot_delete_subkey);
+ revoke_subkey_act_ = new QAction(tr("Revoke"));
+ connect(revoke_subkey_act_, &QAction::triggered, this,
+ &KeyPairSubkeyTab::slot_revoke_subkey);
+
subkey_opera_menu_->addAction(export_subkey_act_);
subkey_opera_menu_->addAction(edit_subkey_act_);
+ subkey_opera_menu_->addAction(revoke_subkey_act_);
subkey_opera_menu_->addAction(delete_subkey_act_);
}
@@ -360,10 +387,9 @@ void KeyPairSubkeyTab::slot_edit_subkey() {
dialog->show();
}
-void KeyPairSubkeyTab::slot_revoke_subkey() {}
-
void KeyPairSubkeyTab::contextMenuEvent(QContextMenuEvent* event) {
- if (key_.IsPrivateKey() && !subkey_list_->selectedItems().isEmpty()) {
+ // must have primary key before do any actions on subkey
+ if (key_.IsHasMasterKey() && !subkey_list_->selectedItems().isEmpty()) {
const auto& subkey = get_selected_subkey();
if (subkey.IsHasCertificationCapability()) return;
@@ -371,6 +397,8 @@ void KeyPairSubkeyTab::contextMenuEvent(QContextMenuEvent* event) {
export_subkey_act_->setDisabled(!subkey.IsSecretKey());
edit_subkey_act_->setDisabled(!subkey.IsSecretKey());
delete_subkey_act_->setDisabled(!subkey.IsSecretKey());
+ revoke_subkey_act_->setDisabled(!subkey.IsSecretKey() ||
+ subkey.IsRevoked());
subkey_opera_menu_->exec(event->globalPos());
}
@@ -486,4 +514,59 @@ void KeyPairSubkeyTab::slot_delete_subkey() {
emit SignalKeyDatabaseRefresh();
}
+
+void KeyPairSubkeyTab::slot_revoke_subkey() {
+ const auto& subkey = get_selected_subkey();
+ const auto subkeys = key_.GetSubKeys();
+
+ QString message = tr("<h3>Revoke Subkey Confirmation</h3><br />"
+ "<b>KeyID:</b> %1<br />2<br />"
+ "Revoking a subkey will make it permanently unusable. "
+ "This action is <b>irreversible</b>.<br />"
+ "Are you sure you want to revoke this subkey?")
+ .arg(subkey.GetID());
+
+ int ret = QMessageBox::warning(this, tr("Revoke Subkey"), message,
+ QMessageBox::Cancel | QMessageBox::Yes,
+ QMessageBox::Cancel);
+
+ if (ret != QMessageBox::Yes) return;
+
+ int index = 0;
+ for (const auto& sk : *subkeys) {
+ if (sk.GetFingerprint() == subkey.GetFingerprint()) {
+ break;
+ }
+ index++;
+ }
+
+ if (index == 0) {
+ QMessageBox::critical(
+ this, tr("Illegal Operation"),
+ tr("Cannot revoke the primary key or an invalid subkey."));
+ return;
+ }
+
+ auto* revocation_options_dialog = new RevocationOptionsDialog(this);
+
+ connect(revocation_options_dialog,
+ &RevocationOptionsDialog::SignalRevokeOptionAccepted, this,
+ [key = key_, index, channel = current_gpg_context_channel_, this](
+ int code, const QString& text) {
+ auto res = GpgKeyManager::GetInstance(channel).RevokeSubkey(
+ key, index, code, text);
+ if (!res) {
+ QMessageBox::critical(
+ nullptr, tr("Revocation Failed"),
+ tr("Failed to revoke the subkey. Please try again."));
+ } else {
+ QMessageBox::information(
+ nullptr, tr("Revocation Successful"),
+ tr("The subkey has been successfully revoked."));
+ emit SignalKeyDatabaseRefresh();
+ }
+ });
+
+ revocation_options_dialog->show();
+}
} // namespace GpgFrontend::UI
diff --git a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
index 1222b77d..9c8daeb9 100644
--- a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
+++ b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
@@ -78,8 +78,9 @@ class KeyPairSubkeyTab : public QWidget {
QMenu* subkey_opera_menu_{}; ///<
QLabel* key_type_var_label_;
- QLabel* key_size_var_label_; ///< Label containing the keys key size
- QLabel* expire_var_label_; ///< Label containing the keys expiration date
+ QLabel* key_size_var_label_; ///< Label containing the keys key size
+ QLabel* expire_var_label_; ///< Label containing the keys expiration date
+ QLabel* revoke_var_label_;
QLabel* created_var_label_; ///< Label containing the keys creation date
QLabel* algorithm_var_label_; ///< Label containing the keys algorithm
QLabel* algorithm_detail_var_label_; ///<
@@ -94,6 +95,7 @@ class KeyPairSubkeyTab : public QWidget {
QAction* edit_subkey_act_;
QAction* delete_subkey_act_;
+ QAction* revoke_subkey_act_;
private slots: