/**
* Copyright (C) 2021 Saturneric
*
* This file is part of GpgFrontend.
*
* GpgFrontend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GpgFrontend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GpgFrontend. If not, see .
*
* The initial version of the source code is inherited from
* the gpg4usb project, which is under GPL-3.0-or-later.
*
* The source code version of this software was modified and released
* by Saturneric starting on May 12, 2021.
*
*/
#include "KeyPairOperaTab.h"
#include "KeySetExpireDateDialog.h"
#include "core/function/GlobalSettingStation.h"
#include "core/function/gpg/GpgKeyImportExporter.h"
#include "core/function/gpg/GpgKeyOpera.h"
#include "ui/SignalStation.h"
#include "ui/UserInterfaceUtils.h"
#include "ui/dialog/import_export/KeyUploadDialog.h"
namespace GpgFrontend::UI {
KeyPairOperaTab::KeyPairOperaTab(const std::string& key_id, QWidget* parent)
: QWidget(parent), m_key_(GpgKeyGetter::GetInstance().GetKey(key_id)) {
// Set Menu
CreateOperaMenu();
auto m_vbox = new QVBoxLayout(this);
auto* opera_key_box = new QGroupBox(_("General Operations"));
auto* vbox_p_k = new QVBoxLayout();
auto export_h_box_layout = new QHBoxLayout();
vbox_p_k->addLayout(export_h_box_layout);
auto* export_public_button = new QPushButton(_("Export Public Key"));
export_h_box_layout->addWidget(export_public_button);
connect(export_public_button, &QPushButton::clicked, this,
&KeyPairOperaTab::slot_export_public_key);
if (m_key_.IsPrivateKey()) {
auto* export_private_button = new QPushButton(_("Export Private Key"));
export_private_button->setStyleSheet("text-align:center;");
export_private_button->setMenu(secret_key_export_opera_menu_);
export_h_box_layout->addWidget(export_private_button);
if (m_key_.IsHasMasterKey()) {
auto* edit_expires_button =
new QPushButton(_("Modify Expiration Datetime (Primary Key)"));
connect(edit_expires_button, &QPushButton::clicked, this,
&KeyPairOperaTab::slot_modify_edit_datetime);
auto* edit_password_button = new QPushButton(_("Modify Password"));
connect(edit_password_button, &QPushButton::clicked, this,
&KeyPairOperaTab::slot_modify_password);
vbox_p_k->addWidget(edit_expires_button);
vbox_p_k->addWidget(edit_password_button);
}
}
auto advance_h_box_layout = new QHBoxLayout();
// get settings
auto& settings = GlobalSettingStation::GetInstance().GetUISettings();
// read settings
bool forbid_all_gnupg_connection = false;
try {
forbid_all_gnupg_connection =
settings.lookup("network.forbid_all_gnupg_connection");
} catch (...) {
SPDLOG_ERROR("setting operation error: forbid_all_gnupg_connection");
}
auto* key_server_opera_button =
new QPushButton(_("Key Server Operation (Pubkey)"));
key_server_opera_button->setStyleSheet("text-align:center;");
key_server_opera_button->setMenu(key_server_opera_menu_);
key_server_opera_button->setDisabled(forbid_all_gnupg_connection);
advance_h_box_layout->addWidget(key_server_opera_button);
if (m_key_.IsPrivateKey() && m_key_.IsHasMasterKey()) {
auto* revoke_cert_gen_button =
new QPushButton(_("Generate Revoke Certificate"));
connect(revoke_cert_gen_button, &QPushButton::clicked, this,
&KeyPairOperaTab::slot_gen_revoke_cert);
advance_h_box_layout->addWidget(revoke_cert_gen_button);
}
auto* modify_tofu_button = new QPushButton(_("Modify TOFU Policy"));
connect(modify_tofu_button, &QPushButton::clicked, this,
&KeyPairOperaTab::slot_modify_tofu_policy);
vbox_p_k->addLayout(advance_h_box_layout);
opera_key_box->setLayout(vbox_p_k);
m_vbox->addWidget(opera_key_box);
vbox_p_k->addWidget(modify_tofu_button);
m_vbox->addStretch(0);
setLayout(m_vbox);
}
void KeyPairOperaTab::CreateOperaMenu() {
key_server_opera_menu_ = new QMenu(this);
auto* uploadKeyPair = new QAction(_("Upload Key Pair to Key Server"), this);
connect(uploadKeyPair, &QAction::triggered, this,
&KeyPairOperaTab::slot_upload_key_to_server);
if (!(m_key_.IsPrivateKey() && m_key_.IsHasMasterKey()))
uploadKeyPair->setDisabled(true);
auto* updateKeyPair = new QAction(_("Sync Key Pair From Key Server"), this);
connect(updateKeyPair, &QAction::triggered, this,
&KeyPairOperaTab::slot_update_key_from_server);
// when a key has primary key, it should always upload to keyserver.
if (m_key_.IsHasMasterKey()) {
updateKeyPair->setDisabled(true);
}
key_server_opera_menu_->addAction(uploadKeyPair);
key_server_opera_menu_->addAction(updateKeyPair);
secret_key_export_opera_menu_ = new QMenu(this);
auto* exportFullSecretKey = new QAction(_("Export Full Secret Key"), this);
connect(exportFullSecretKey, &QAction::triggered, this,
&KeyPairOperaTab::slot_export_private_key);
if (!m_key_.IsPrivateKey()) exportFullSecretKey->setDisabled(true);
auto* exportShortestSecretKey =
new QAction(_("Export Shortest Secret Key"), this);
connect(exportShortestSecretKey, &QAction::triggered, this,
&KeyPairOperaTab::slot_export_short_private_key);
secret_key_export_opera_menu_->addAction(exportFullSecretKey);
secret_key_export_opera_menu_->addAction(exportShortestSecretKey);
}
void KeyPairOperaTab::slot_export_public_key() {
ByteArrayPtr keyArray = nullptr;
if (!GpgKeyImportExporter::GetInstance().ExportKey(m_key_, keyArray)) {
QMessageBox::critical(this, _("Error"),
_("An error occurred during the export operation."));
return;
}
// generate a file name
#ifndef WINDOWS
auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" +
m_key_.GetId() + ")_pub.asc";
#else
auto file_string = m_key_.GetName() + "[" + m_key_.GetEmail() + "](" +
m_key_.GetId() + ")_pub.asc";
#endif
std::replace(file_string.begin(), file_string.end(), ' ', '_');
auto file_name =
QFileDialog::getSaveFileName(
this, _("Export Key To File"), QString::fromStdString(file_string),
QString(_("Key Files")) + " (*.asc *.txt);;All Files (*)")
.toStdString();
if (file_name.empty()) return;
if (!write_buffer_to_file(file_name, *keyArray)) {
QMessageBox::critical(
this, _("Export Error"),
QString(_("Couldn't open %1 for writing")).arg(file_name.c_str()));
return;
}
}
void KeyPairOperaTab::slot_export_short_private_key() {
// Show a information box with explanation about private key
int ret = QMessageBox::information(
this, _("Exporting short private Key"),
"" + QString(_("You are about to export your")) +
"" + _(" PRIVATE KEY ") + "!
\n" +
_("This is NOT your Public Key, so DON'T give it away.") + "
" +
_("Do you REALLY want to export your PRIVATE KEY in a Minimum "
"Size?") +
"
" +
_("For OpenPGP keys it removes all signatures except for the latest "
"self-signatures."),
QMessageBox::Cancel | QMessageBox::Ok);
// export key, if ok was clicked
if (ret == QMessageBox::Ok) {
ByteArrayPtr keyArray = nullptr;
if (!GpgKeyImportExporter::GetInstance().ExportSecretKeyShortest(
m_key_, keyArray)) {
QMessageBox::critical(
this, _("Error"),
_("An error occurred during the export operation."));
return;
}
// generate a file name
#ifndef WINDOWS
auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" +
m_key_.GetId() + ")_short_secret.asc";
#else
auto file_string = m_key_.GetName() + "[" + m_key_.GetEmail() + "](" +
m_key_.GetId() + ")_short_secret.asc";
#endif
std::replace(file_string.begin(), file_string.end(), ' ', '_');
auto file_name =
QFileDialog::getSaveFileName(
this, _("Export Key To File"), QString::fromStdString(file_string),
QString(_("Key Files")) + " (*.asc *.txt);;All Files (*)")
.toStdString();
if (file_name.empty()) return;
if (!write_buffer_to_file(file_name, *keyArray)) {
QMessageBox::critical(
this, _("Export Error"),
QString(_("Couldn't open %1 for writing")).arg(file_name.c_str()));
return;
}
}
}
void KeyPairOperaTab::slot_export_private_key() {
// Show a information box with explanation about private key
int ret = QMessageBox::information(
this, _("Exporting private Key"),
"" + QString(_("You are about to export your")) +
"" + _(" PRIVATE KEY ") + "!
\n" +
_("This is NOT your Public Key, so DON'T give it away.") + "
" +
_("Do you REALLY want to export your PRIVATE KEY?"),
QMessageBox::Cancel | QMessageBox::Ok);
// export key, if ok was clicked
if (ret == QMessageBox::Ok) {
ByteArrayPtr keyArray = nullptr;
if (!GpgKeyImportExporter::GetInstance().ExportSecretKey(m_key_,
keyArray)) {
QMessageBox::critical(
this, _("Error"),
_("An error occurred during the export operation."));
return;
}
// generate a file name
#ifndef WINDOWS
auto file_string = m_key_.GetName() + "<" + m_key_.GetEmail() + ">(" +
m_key_.GetId() + ")_full_secret.asc";
#else
auto file_string = m_key_.GetName() + "[" + m_key_.GetEmail() + "](" +
m_key_.GetId() + ")_full_secret.asc";
#endif
std::replace(file_string.begin(), file_string.end(), ' ', '_');
auto file_name =
QFileDialog::getSaveFileName(
this, _("Export Key To File"), QString::fromStdString(file_string),
QString(_("Key Files")) + " (*.asc *.txt);;All Files (*)")
.toStdString();
if (file_name.empty()) return;
if (!write_buffer_to_file(file_name, *keyArray)) {
QMessageBox::critical(
this, _("Export Error"),
QString(_("Couldn't open %1 for writing")).arg(file_name.c_str()));
return;
}
}
}
void KeyPairOperaTab::slot_modify_edit_datetime() {
auto dialog = new KeySetExpireDateDialog(m_key_.GetId(), this);
dialog->show();
}
void KeyPairOperaTab::slot_upload_key_to_server() {
auto keys = std::make_unique();
keys->push_back(m_key_.GetId());
auto* dialog = new KeyUploadDialog(keys, this);
dialog->show();
dialog->SlotUpload();
}
void KeyPairOperaTab::slot_update_key_from_server() {
auto keys = std::make_unique();
keys->push_back(m_key_.GetId());
auto* dialog = new KeyServerImportDialog(this);
dialog->show();
dialog->SlotImport(keys);
}
void KeyPairOperaTab::slot_gen_revoke_cert() {
auto literal = QString("%1 (*.rev)").arg(_("Revocation Certificates"));
QString m_output_file_name;
QFileDialog dialog(this, "Generate revocation certificate", QString(),
literal);
dialog.setDefaultSuffix(".rev");
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec()) m_output_file_name = dialog.selectedFiles().front();
if (!m_output_file_name.isEmpty())
CommonUtils::GetInstance()->SlotExecuteGpgCommand(
{"--command-fd", "0", "--status-fd", "1", "--no-tty", "-o",
m_output_file_name, "--gen-revoke", m_key_.GetFingerprint().c_str()},
[](QProcess* proc) -> void {
// Code From Gpg4Win
while (proc->canReadLine()) {
const QString line = QString::fromUtf8(proc->readLine()).trimmed();
SPDLOG_DEBUG("line: {}", line.toStdString());
if (line == QLatin1String("[GNUPG:] GET_BOOL gen_revoke.okay")) {
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE "
"ask_revocation_reason.code")) {
proc->write("0\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE "
"ask_revocation_reason.text")) {
proc->write("\n");
} else if (line ==
QLatin1String(
"[GNUPG:] GET_BOOL openfile.overwrite.okay")) {
// We asked before
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_BOOL "
"ask_revocation_reason.okay")) {
proc->write("y\n");
}
}
});
}
void KeyPairOperaTab::slot_modify_password() {
auto err = GpgKeyOpera::GetInstance().ModifyPassword(m_key_);
if (check_gpg_error_2_err_code(err) != GPG_ERR_NO_ERROR) {
QMessageBox::critical(this, _("Not Successful"),
QString(_("Modify password not successfully.")));
}
}
void KeyPairOperaTab::slot_modify_tofu_policy() {
QStringList items;
items << _("Policy Auto") << _("Policy Good") << _("Policy Bad")
<< _("Policy Ask") << _("Policy Unknown");
bool ok;
QString item = QInputDialog::getItem(
this, _("Modify TOFU Policy(Default is Auto)"),
_("Policy for the Key Pair:"), items, 0, false, &ok);
if (ok && !item.isEmpty()) {
SPDLOG_DEBUG("selected policy: {}", item.toStdString());
gpgme_tofu_policy_t tofu_policy = GPGME_TOFU_POLICY_AUTO;
if (item == _("Policy Auto")) {
tofu_policy = GPGME_TOFU_POLICY_AUTO;
} else if (item == _("Policy Good")) {
tofu_policy = GPGME_TOFU_POLICY_GOOD;
} else if (item == _("Policy Bad")) {
tofu_policy = GPGME_TOFU_POLICY_BAD;
} else if (item == _("Policy Ask")) {
tofu_policy = GPGME_TOFU_POLICY_ASK;
} else if (item == _("Policy Unknown")) {
tofu_policy = GPGME_TOFU_POLICY_UNKNOWN;
}
auto err = GpgKeyOpera::GetInstance().ModifyTOFUPolicy(m_key_, tofu_policy);
if (check_gpg_error_2_err_code(err) != GPG_ERR_NO_ERROR) {
QMessageBox::critical(this, _("Not Successful"),
QString(_("Modify TOFU policy not successfully.")));
}
}
}
} // namespace GpgFrontend::UI