diff options
author | saturneric <[email protected]> | 2024-11-18 14:31:25 +0000 |
---|---|---|
committer | saturneric <[email protected]> | 2024-11-18 14:31:25 +0000 |
commit | 91b3a5950f0d3243ad514f0832faf1549f222d3a (patch) | |
tree | fce00a530f6b66d30855d6ce4dcd2087e56f4de4 /src | |
parent | feat: use keys.openpgp.org as default for key publishing and refreshing (diff) | |
download | GpgFrontend-91b3a5950f0d3243ad514f0832faf1549f222d3a.tar.gz GpgFrontend-91b3a5950f0d3243ad514f0832faf1549f222d3a.zip |
feat: support export a single subkey
Diffstat (limited to 'src')
-rw-r--r-- | src/core/function/gpg/GpgKeyImportExporter.cpp | 16 | ||||
-rw-r--r-- | src/core/function/gpg/GpgKeyImportExporter.h | 10 | ||||
-rw-r--r-- | src/test/core/GpgCoreTestImportExport.cpp | 60 | ||||
-rw-r--r-- | src/ui/dialog/keypair_details/KeyPairSubkeyTab.cpp | 76 | ||||
-rw-r--r-- | src/ui/dialog/keypair_details/KeyPairSubkeyTab.h | 6 |
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 |