aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2024-11-18 14:31:25 +0000
committersaturneric <[email protected]>2024-11-18 14:31:25 +0000
commit91b3a5950f0d3243ad514f0832faf1549f222d3a (patch)
treefce00a530f6b66d30855d6ce4dcd2087e56f4de4 /src
parentfeat: use keys.openpgp.org as default for key publishing and refreshing (diff)
downloadGpgFrontend-91b3a5950f0d3243ad514f0832faf1549f222d3a.tar.gz
GpgFrontend-91b3a5950f0d3243ad514f0832faf1549f222d3a.zip
feat: support export a single subkey
Diffstat (limited to 'src')
-rw-r--r--src/core/function/gpg/GpgKeyImportExporter.cpp16
-rw-r--r--src/core/function/gpg/GpgKeyImportExporter.h10
-rw-r--r--src/test/core/GpgCoreTestImportExport.cpp60
-rw-r--r--src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp76
-rw-r--r--src/ui/dialog/keypair_details/KeyPairSubkeyTab.h6
5 files changed, 157 insertions, 11 deletions
diff --git a/src/core/function/gpg/GpgKeyImportExporter.cpp b/src/core/function/gpg/GpgKeyImportExporter.cpp
index d0494339..fa1f908f 100644
--- a/src/core/function/gpg/GpgKeyImportExporter.cpp
+++ b/src/core/function/gpg/GpgKeyImportExporter.cpp
@@ -174,4 +174,20 @@ void GpgKeyImportExporter::ExportAllKeys(const KeyArgsList& keys, bool secret,
cb, "gpgme_op_export_keys", "2.1.0");
}
+auto GpgKeyImportExporter::ExportSubkey(const QString& fpr, bool ascii) const
+ -> std::tuple<GpgError, GFBuffer> {
+ int mode = 0;
+ mode |= GPGME_EXPORT_MODE_SECRET_SUBKEY;
+
+ auto pattern = fpr;
+ if (!fpr.endsWith("!")) pattern += "!";
+
+ GpgData data_out;
+ auto* ctx = ascii ? ctx_.DefaultContext() : ctx_.BinaryContext();
+ auto err =
+ gpgme_op_export(ctx, pattern.toLatin1().constData(), mode, data_out);
+ if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) return {err, {}};
+
+ return {err, data_out.Read2GFBuffer()};
+}
} // namespace GpgFrontend
diff --git a/src/core/function/gpg/GpgKeyImportExporter.h b/src/core/function/gpg/GpgKeyImportExporter.h
index b52189f1..cc7b3f6f 100644
--- a/src/core/function/gpg/GpgKeyImportExporter.h
+++ b/src/core/function/gpg/GpgKeyImportExporter.h
@@ -75,6 +75,16 @@ class GPGFRONTEND_CORE_EXPORT GpgKeyImportExporter
/**
* @brief
*
+ * @param fpr
+ * @param ascii
+ * @return std::tuple<GpgError, GFBuffer>
+ */
+ [[nodiscard]] auto ExportSubkey(const QString& fpr, bool ascii) const
+ -> std::tuple<GpgError, GFBuffer>;
+
+ /**
+ * @brief
+ *
* @param keys
* @param outBuffer
* @param secret
diff --git a/src/test/core/GpgCoreTestImportExport.cpp b/src/test/core/GpgCoreTestImportExport.cpp
index e67abe78..87e13252 100644
--- a/src/test/core/GpgCoreTestImportExport.cpp
+++ b/src/test/core/GpgCoreTestImportExport.cpp
@@ -26,13 +26,67 @@
*
*/
-#include <vector>
-
#include "GpgCoreTest.h"
#include "core/GpgConstants.h"
+#include "core/function/gpg/GpgKeyImportExporter.h"
+#include "core/utils/GpgUtils.h"
namespace GpgFrontend::Test {
-// TEST_F(GpgCoreTest, CoreExportSecretTest) {}
+TEST_F(GpgCoreTest, CoreExportSubkeyTestA) {
+ auto [err, gf_buffer] = GpgKeyImportExporter::GetInstance().ExportSubkey(
+ "F89C95A05088CC93", true);
+
+ ASSERT_EQ(CheckGpgError(err), GPG_ERR_NO_ERROR);
+ ASSERT_FALSE(gf_buffer.Empty());
+ ASSERT_EQ(
+ QCryptographicHash::hash(gf_buffer.ConvertToQByteArray(),
+ QCryptographicHash::Sha256)
+ .toHex(),
+ QByteArray(
+ "6e3375060aa889d9eb61e2966eabb31eb6b5359a7742ee7adeedec09e6afa36a"));
+}
+
+TEST_F(GpgCoreTest, CoreExportSubkeyTestB) {
+ auto [err, gf_buffer] = GpgKeyImportExporter::GetInstance().ExportSubkey(
+ "F89C95A05088CC93!", true);
+
+ ASSERT_EQ(CheckGpgError(err), GPG_ERR_NO_ERROR);
+ ASSERT_FALSE(gf_buffer.Empty());
+ ASSERT_EQ(
+ QCryptographicHash::hash(gf_buffer.ConvertToQByteArray(),
+ QCryptographicHash::Sha256)
+ .toHex(),
+ QByteArray(
+ "6e3375060aa889d9eb61e2966eabb31eb6b5359a7742ee7adeedec09e6afa36a"));
+}
+
+TEST_F(GpgCoreTest, CoreExportSubkeyTestC) {
+ auto [err, gf_buffer] = GpgKeyImportExporter::GetInstance().ExportSubkey(
+ "CFF986E51BBC2F46064C2136F89C95A05088CC93", true);
+
+ ASSERT_EQ(CheckGpgError(err), GPG_ERR_NO_ERROR);
+ ASSERT_FALSE(gf_buffer.Empty());
+ ASSERT_EQ(
+ QCryptographicHash::hash(gf_buffer.ConvertToQByteArray(),
+ QCryptographicHash::Sha256)
+ .toHex(),
+ QByteArray(
+ "6e3375060aa889d9eb61e2966eabb31eb6b5359a7742ee7adeedec09e6afa36a"));
+}
+
+TEST_F(GpgCoreTest, CoreExportSubkeyTestD) {
+ auto [err, gf_buffer] = GpgKeyImportExporter::GetInstance().ExportSubkey(
+ "CFF986E51BBC2F46064C2136F89C95A05088CC93!", true);
+
+ ASSERT_EQ(CheckGpgError(err), GPG_ERR_NO_ERROR);
+ ASSERT_FALSE(gf_buffer.Empty());
+ ASSERT_EQ(
+ QCryptographicHash::hash(gf_buffer.ConvertToQByteArray(),
+ QCryptographicHash::Sha256)
+ .toHex(),
+ QByteArray(
+ "6e3375060aa889d9eb61e2966eabb31eb6b5359a7742ee7adeedec09e6afa36a"));
+}
} // 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 a5b38d1f..8066ccfb 100644
--- a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp
+++ b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp
@@ -30,8 +30,12 @@
#include "core/GpgModel.h"
#include "core/function/gpg/GpgKeyGetter.h"
+#include "core/function/gpg/GpgKeyImportExporter.h"
#include "core/utils/CommonUtils.h"
+#include "core/utils/GpgUtils.h"
+#include "core/utils/IOUtils.h"
#include "ui/UISignalStation.h"
+#include "ui/UserInterfaceUtils.h"
namespace GpgFrontend::UI {
@@ -94,6 +98,10 @@ KeyPairSubkeyTab::KeyPairSubkeyTab(int channel, const QString& key_id,
fingerprint_var_label_ = new QLabel(this);
card_key_label_ = new QLabel(this);
+ // make keyid & fingerprint selectable for copy
+ key_id_var_label_->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ fingerprint_var_label_->setTextInteractionFlags(Qt::TextSelectableByMouse);
+
subkey_detail_layout->addWidget(key_id_var_label_, 0, 1, 1, 1);
subkey_detail_layout->addWidget(algorithm_var_label_, 1, 1, 1, 2);
subkey_detail_layout->addWidget(algorithm_detail_var_label_, 2, 1, 1, 2);
@@ -105,14 +113,11 @@ KeyPairSubkeyTab::KeyPairSubkeyTab(int channel, const QString& key_id,
subkey_detail_layout->addWidget(card_key_label_, 8, 1, 1, 2);
subkey_detail_layout->addWidget(fingerprint_var_label_, 9, 1, 1, 2);
- auto* copy_key_id_button = new QPushButton(tr("Copy"));
- copy_key_id_button->setFlat(true);
- subkey_detail_layout->addWidget(copy_key_id_button, 0, 2);
- connect(copy_key_id_button, &QPushButton::clicked, this, [=]() {
- QString fpr = key_id_var_label_->text().trimmed();
- QClipboard* cb = QApplication::clipboard();
- cb->setText(fpr);
- });
+ auto* export_subkey_button = new QPushButton(tr("Export Subkey"));
+ export_subkey_button->setFlat(true);
+ subkey_detail_layout->addWidget(export_subkey_button, 0, 2);
+ connect(export_subkey_button, &QPushButton::clicked, this,
+ &KeyPairSubkeyTab::slot_export_subkey);
list_box_->setLayout(subkey_list_layout);
list_box_->setContentsMargins(0, 12, 0, 0);
@@ -312,6 +317,10 @@ void KeyPairSubkeyTab::create_subkey_opera_menu() {
connect(edit_subkey_act, &QAction::triggered, this,
&KeyPairSubkeyTab::slot_edit_subkey);
+ auto* export_subkey_act = new QAction(tr("Export"));
+ connect(export_subkey_act, &QAction::triggered, this,
+ &KeyPairSubkeyTab::slot_export_subkey);
+
subkey_opera_menu_->addAction(edit_subkey_act);
}
@@ -340,10 +349,61 @@ auto KeyPairSubkeyTab::get_selected_subkey() -> const GpgSubKey& {
return buffered_subkeys_[row];
}
+
void KeyPairSubkeyTab::slot_refresh_key_info() {
key_ = GpgKeyGetter::GetInstance(current_gpg_context_channel_)
.GetKey(key_.GetId());
assert(key_.IsGood());
}
+void KeyPairSubkeyTab::slot_export_subkey() {
+ int ret = QMessageBox::question(
+ this, tr("Exporting Subkey"),
+ "<h3>" + tr("You are about to export a private subkey.") + "</h3>\n" +
+ tr("While subkeys are less critical than the primary key, "
+ "they should still be handled with care.") +
+ "<br /><br />" +
+ tr("Do you want to proceed with exporting this subkey?"),
+ QMessageBox::Cancel | QMessageBox::Yes, QMessageBox::Cancel);
+
+ if (ret != QMessageBox::Yes) {
+ QMessageBox::information(this, tr("Export Cancelled"),
+ tr("The subkey export has been cancelled."));
+ return;
+ }
+
+ const auto& subkey = get_selected_subkey();
+
+ auto [err, gf_buffer] =
+ GpgKeyImportExporter::GetInstance(current_gpg_context_channel_)
+ .ExportSubkey(subkey.GetFingerprint(), true);
+
+ if (CheckGpgError(err) != GPG_ERR_NO_ERROR) {
+ CommonUtils::RaiseMessageBox(this, err);
+ return;
+ }
+
+ // generate a file name
+#if defined(_WIN32) || defined(WIN32)
+ auto file_string = key_.GetName() + "[" + key_.GetEmail() + "](" +
+ subkey.GetID() + ")_subkey.asc";
+#else
+ auto file_string = key_.GetName() + "<" + key_.GetEmail() + ">(" +
+ subkey.GetID() + ")_subkey.asc";
+#endif
+ std::replace(file_string.begin(), file_string.end(), ' ', '_');
+
+ auto file_name = QFileDialog::getSaveFileName(
+ this, tr("Export Key To File"), file_string,
+ tr("Key Files") + " (*.asc *.txt);;All Files (*)");
+
+ if (file_name.isEmpty()) return;
+
+ if (!WriteFileGFBuffer(file_name, gf_buffer)) {
+ QMessageBox::critical(this, tr("Export Error"),
+ tr("Couldn't open %1 for writing").arg(file_name));
+ return;
+ }
+}
+
} // namespace GpgFrontend::UI
diff --git a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
index 85423745..e7b97eb5 100644
--- a/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
+++ b/src/ui/dialog/keypair_details/KeyPairSubkeyTab.h
@@ -126,6 +126,12 @@ class KeyPairSubkeyTab : public QWidget {
*/
void slot_refresh_key_info();
+ /**
+ * @brief
+ *
+ */
+ void slot_export_subkey();
+
protected:
/**
* @brief