diff options
author | Saturneric <[email protected]> | 2021-05-29 19:28:40 +0000 |
---|---|---|
committer | Saturneric <[email protected]> | 2021-05-29 19:28:40 +0000 |
commit | fe67bfb777a79b25149efa3e045dc043dc144ad0 (patch) | |
tree | 4d75a10f210b86d7e116d188569bc08e4e72adb7 | |
parent | Make eligible keys enter the signature candidate list. (diff) | |
download | GpgFrontend-fe67bfb777a79b25149efa3e045dc043dc144ad0.tar.gz GpgFrontend-fe67bfb777a79b25149efa3e045dc043dc144ad0.zip |
New page and function for generating subkeys.
Define the interface and functions of the subkey management tab.
When there is an item in the UID list, the first item is selected by default.
Compile the API for generating sub-keys and the corresponding calling thread.
Set GpgGenKeyInfo to apply to the subkey
Generate a subkey for the selected key pair of the management key pair interface.
Adjust the project structure and add a new classification key generation category.
Double-click the item in KeyList in the key pair management interface to enter the key details page.
Adjust the title of the key pair management interface.
Optimize part of the code of KeyGenThread.
Signed-off-by: Saturneric <[email protected]>
21 files changed, 719 insertions, 50 deletions
diff --git a/include/gpg/GpgContext.h b/include/gpg/GpgContext.h index a68c6490..af390ff1 100644 --- a/include/gpg/GpgContext.h +++ b/include/gpg/GpgContext.h @@ -93,6 +93,8 @@ namespace GpgME { bool generateKey(GenKeyInfo *params); + bool generateSubkey(const GpgKey &key, GenKeyInfo *params); + void deleteKeys(QStringList *uidList); bool encrypt(QStringList *uidList, const QByteArray &inBuffer, diff --git a/include/gpg/GpgGenKeyInfo.h b/include/gpg/GpgGenKeyInfo.h index ee2b0262..a5936207 100644 --- a/include/gpg/GpgGenKeyInfo.h +++ b/include/gpg/GpgGenKeyInfo.h @@ -49,6 +49,8 @@ public: static const QVector<QString> SupportedKeyAlgo; + static const QVector<QString> SupportedSubkeyAlgo; + [[nodiscard]] bool isSubKey() const { return subKey; } diff --git a/include/ui/KeyMgmt.h b/include/ui/KeyMgmt.h index c709fa86..c11280af 100755 --- a/include/ui/KeyMgmt.h +++ b/include/ui/KeyMgmt.h @@ -26,11 +26,11 @@ #define __KEYMGMT_H__ #include "ui/widgets/KeyList.h" -#include "KeygenThread.h" +#include "ui/keygen/KeygenThread.h" #include "ui/keypair_details/KeyDetailsDialog.h" #include "KeyImportDetailDialog.h" #include "KeyServerImportDialog.h" -#include "KeygenDialog.h" +#include "ui/keygen/KeygenDialog.h" class KeyMgmt : public QMainWindow { diff --git a/include/ui/Wizard.h b/include/ui/Wizard.h index 1003bc5d..da6fdf05 100644 --- a/include/ui/Wizard.h +++ b/include/ui/Wizard.h @@ -25,7 +25,7 @@ #ifndef WIZARD_H #define WIZARD_H -#include "ui/KeygenDialog.h" +#include "ui/keygen/KeygenDialog.h" #include "ui/KeyMgmt.h" #include "gpg/GpgConstants.h" #include "SettingsDialog.h" diff --git a/include/ui/KeygenDialog.h b/include/ui/keygen/KeygenDialog.h index b1feaa4a..a2718a1d 100644 --- a/include/ui/KeygenDialog.h +++ b/include/ui/keygen/KeygenDialog.h @@ -76,7 +76,6 @@ private: void generateKeyDialog(); - /** * @details Refresh widgets state by GenKeyInfo */ diff --git a/include/ui/KeygenThread.h b/include/ui/keygen/KeygenThread.h index 079409e5..e04e00d2 100644 --- a/include/ui/KeygenThread.h +++ b/include/ui/keygen/KeygenThread.h @@ -26,12 +26,6 @@ #include "gpg/GpgContext.h" - -QT_BEGIN_NAMESPACE -class QMessageBox; - -QT_END_NAMESPACE - class KeyGenThread : public QThread { Q_OBJECT @@ -39,7 +33,6 @@ public: KeyGenThread(GenKeyInfo *keyGenParams, GpgME::GpgContext *ctx); signals: - void signalKeyGenerated(bool success); private: @@ -49,7 +42,8 @@ private: QMutex mutex; protected: - void run(); + + void run() override; }; diff --git a/include/ui/keygen/SubkeyGenerateDialog.h b/include/ui/keygen/SubkeyGenerateDialog.h new file mode 100644 index 00000000..874b2451 --- /dev/null +++ b/include/ui/keygen/SubkeyGenerateDialog.h @@ -0,0 +1,79 @@ +// +// Created by eric on 2021/5/30. +// + +#ifndef GPGFRONTEND_SUBKEYGENERATEDIALOG_H +#define GPGFRONTEND_SUBKEYGENERATEDIALOG_H + +#include "GpgFrontend.h" +#include "gpg/GpgKey.h" +#include "gpg/GpgSubKey.h" +#include "gpg/GpgGenKeyInfo.h" + +#include "SubkeyGenerateThread.h" + +class SubkeyGenerateDialog : public QDialog { +Q_OBJECT + +public: + + explicit SubkeyGenerateDialog(GpgME::GpgContext *ctx, const GpgKey &key, QWidget *parent = nullptr); + +private: + + GpgME::GpgContext *mCtx; /** The current gpg context */ + const GpgKey &mKey; + + GenKeyInfo genKeyInfo{}; + SubkeyGenerateThread *kg{}; /** Thread for key generation */ + + QGroupBox *keyUsageGroupBox{}; + QDialogButtonBox *buttonBox; /** Box for standardbuttons */ + QLabel *errorLabel{}; /** Label containing error message */ + QSpinBox *keySizeSpinBox{}; /** Spinbox for the keys size (in bit) */ + QComboBox *keyTypeComboBox{}; /** Combobox for Keytpe */ + QDateTimeEdit *dateEdit{}; /** Dateedit for expiration date */ + QCheckBox *expireCheckBox{}; /** Checkbox, if key should expire */ + + // ENCR, SIGN, CERT, AUTH + std::vector<QCheckBox *> keyUsageCheckBoxes; + + QGroupBox *create_key_usage_group_box(); + + QGroupBox *create_basic_info_group_box(); + + void set_signal_slot(); + + /** + * @details Refresh widgets state by GenKeyInfo + */ + void refresh_widgets_state(); + +private slots: + + /** + * @details when expirebox was checked/unchecked, enable/disable the expiration date box + */ + void slotExpireBoxChanged(); + + /** + * @details check all lineedits for false entries. Show error, when there is one, otherwise generate the key + */ + void slotKeyGenAccept(); + + void slotEncryptionBoxChanged(int state); + + void slotSigningBoxChanged(int state); + + void slotCertificationBoxChanged(int state); + + void slotAuthenticationBoxChanged(int state); + + void slotActivatedKeyType(int index); + + void slotKeyGenResult(bool success); + +}; + + +#endif //GPGFRONTEND_SUBKEYGENERATEDIALOG_H diff --git a/include/ui/keygen/SubkeyGenerateThread.h b/include/ui/keygen/SubkeyGenerateThread.h new file mode 100644 index 00000000..1f3a1cb5 --- /dev/null +++ b/include/ui/keygen/SubkeyGenerateThread.h @@ -0,0 +1,33 @@ +// +// Created by eric on 2021/5/30. +// + +#ifndef GPGFRONTEND_SUBKEYGENERATETHREAD_H +#define GPGFRONTEND_SUBKEYGENERATETHREAD_H + +#include "gpg/GpgContext.h" + +class SubkeyGenerateThread : public QThread { + Q_OBJECT + +public: + SubkeyGenerateThread(GpgKey key, GenKeyInfo *keyGenParams, GpgME::GpgContext *ctx); + +signals: + + void signalKeyGenerated(bool success); + +private: + const GpgKey mKey; + GenKeyInfo *keyGenParams; + GpgME::GpgContext *mCtx; + [[maybe_unused]] bool abort; + QMutex mutex; + +protected: + + void run() override; +}; + + +#endif //GPGFRONTEND_SUBKEYGENERATETHREAD_H diff --git a/include/ui/keypair_details/KeyPairSubkeyTab.h b/include/ui/keypair_details/KeyPairSubkeyTab.h index 11a9ab41..45abe7a1 100644 --- a/include/ui/keypair_details/KeyPairSubkeyTab.h +++ b/include/ui/keypair_details/KeyPairSubkeyTab.h @@ -28,6 +28,7 @@ #include "GpgFrontend.h" #include "gpg/GpgContext.h" +#include "ui/keygen/SubkeyGenerateDialog.h" class KeyPairSubkeyTab : public QWidget { Q_OBJECT @@ -41,14 +42,30 @@ private: void creatSubkeyList(); GpgME::GpgContext *mCtx; + const GpgKey &mKey; + QTableWidget *subkeyList; + QVector<const GpgSubKey *> buffered_subkeys; - const GpgKey &key; + QGroupBox *listBox; + QGroupBox *detailBox; - QTableWidget *subkeyList; + + QLabel *keySizeVarLabel; /** Label containng the keys keysize */ + QLabel *expireVarLabel; /** Label containng the keys expiration date */ + QLabel *createdVarLabel; /** Label containng the keys creation date */ + QLabel *algorithmVarLabel; /** Label containng the keys algorithm */ + QLabel *keyidVarLabel; /** Label containng the keys keyid */ + QLabel *fingerPrintVarLabel; /** Label containng the keys fingerprint */ + QLabel *usageVarLabel; private slots: + void slotAddSubkey(); + + void slotRefreshSubkeyList(); + + void slotRefreshSubkeyDetail(); }; diff --git a/include/ui/widgets/KeyList.h b/include/ui/widgets/KeyList.h index 0d9b07dc..06e80910 100644 --- a/include/ui/widgets/KeyList.h +++ b/include/ui/widgets/KeyList.h @@ -67,6 +67,8 @@ public: void setFilter(std::function<bool(const GpgKey&)> filter); + void setDoubleClickedAction(std::function<void(const GpgKey&, QWidget *)> action); + void setColumnWidth(int row, int size); void addMenuAction(QAction *act); @@ -108,12 +110,15 @@ private: QVector<QString> excluded_key_ids; std::function<bool(const GpgKey &)> mFilter = nullptr; + std::function<void(const GpgKey &, QWidget *)> mAction = nullptr; private slots: void uploadFinished(); + void slotDoubleClicked(const QModelIndex &index); + protected: diff --git a/src/gpg/GpgContext.cpp b/src/gpg/GpgContext.cpp index 51ee07b5..d4736249 100644 --- a/src/gpg/GpgContext.cpp +++ b/src/gpg/GpgContext.cpp @@ -23,7 +23,7 @@ */ #include "gpg/GpgContext.h" -#include "ui/KeygenThread.h" +#include "ui/keygen/KeygenThread.h" #include <unistd.h> /* contains read/write */ @@ -321,28 +321,6 @@ namespace GpgME { return; } -// list only private keys ( the 1 does ) -// gpgmeError = gpgme_op_keylist_start(mCtx, nullptr, 1); -// if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { -// checkErr(gpgmeError); -// return; -// } -// -// while ((gpgmeError = gpgme_op_keylist_next(mCtx, &key)) == GPG_ERR_NO_ERROR) { -// if (key->subkeys) { -// auto it = keys_map.find(key->subkeys->keyid); -// if (it != keys_map.end()) { -// it->is_private_key = true; -// } -// } -// gpgme_key_unref(key); -// } -// -// if (gpg_err_code(gpgmeError) != GPG_ERR_EOF) { -// checkErr(gpgmeError); -// return; -// } - gpgmeError = gpgme_op_keylist_end(mCtx); if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { checkErr(gpgmeError); @@ -1022,7 +1000,52 @@ namespace GpgME { checkErr(gpgmeError); return false; } - return false; + } + + bool GpgContext::generateSubkey(const GpgKey &key, GenKeyInfo *params) { + + if(!params->isSubKey()) { + return false; + } + + auto algo_utf8 = (params->getAlgo() + params->getKeySizeStr()).toUtf8(); + const char *algo = algo_utf8.constData(); + unsigned long expires = QDateTime::currentDateTime().secsTo(params->getExpired()); + unsigned int flags = 0; + + if (!params->isSubKey()) { + flags |= GPGME_CREATE_CERT; + } + + if (params->isAllowEncryption()) { + flags |= GPGME_CREATE_ENCR; + } + + if (params->isAllowSigning()) { + flags |= GPGME_CREATE_SIGN; + } + + if (params->isAllowAuthentication()) { + flags |= GPGME_CREATE_AUTH; + } + + if (params->isNonExpired()) { + flags |= GPGME_CREATE_NOEXPIRE; + } + + flags |= GPGME_CREATE_NOPASSWD; + + + auto gpgmeError = gpgme_op_createsubkey(mCtx, key.key_refer, + algo, 0, expires, flags); + if(gpgmeError == GPG_ERR_NO_ERROR) { + emit signalKeyUpdated(key.id); + return true; + } + else { + checkErr(gpgmeError); + return false; + } } } diff --git a/src/gpg/GpgGenKeyInfo.cpp b/src/gpg/GpgGenKeyInfo.cpp index 9532e876..69da27e3 100644 --- a/src/gpg/GpgGenKeyInfo.cpp +++ b/src/gpg/GpgGenKeyInfo.cpp @@ -30,6 +30,13 @@ const QVector<QString> GenKeyInfo::SupportedKeyAlgo = { "ED25519" }; +const QVector<QString> GenKeyInfo::SupportedSubkeyAlgo = { + "RSA", + "DSA", + "ED25519", + "ELG" +}; + void GenKeyInfo::setAlgo(const QString &m_algo) { qDebug() << "set algo " << m_algo; @@ -38,9 +45,12 @@ void GenKeyInfo::setAlgo(const QString &m_algo) { if (!this->subKey) { this->setAllowCertification(true); - this->allowChangeCertification = false; + } else { + this->setAllowCertification(false); } + this->allowChangeCertification = false; + auto lower_algo = m_algo.toLower(); if(lower_algo == "rsa") { @@ -81,6 +91,21 @@ void GenKeyInfo::setAlgo(const QString &m_algo) { suggestMaxKeySize = -1; suggestSizeAdditionStep = -1; setKeySize(-1); + } else if (lower_algo == "elg") { + /** + * GnuPG supports the Elgamal asymmetric encryption algorithm in key lengths ranging from 1024 to 4096 bits. + */ + + setAllowAuthentication(false); + allowChangeAuthentication = false; + + setAllowSigning(false); + allowChangeSigning = false; + + suggestMinKeySize = 1024; + suggestMaxKeySize = 4096; + suggestSizeAdditionStep = 1024; + setKeySize(2048); } GenKeyInfo::algo = lower_algo; } diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 5456c2a4..2527da46 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -1,6 +1,7 @@ aux_source_directory(. QTUI_SOURCE) aux_source_directory(./keypair_details QTUI_SOURCE) aux_source_directory(./widgets QTUI_SOURCE) +aux_source_directory(./keygen QTUI_SOURCE) add_library(qtui STATIC ${QTUI_SOURCE} ${QTUI_KEYPAIR_DETAILS_SOURCE}) diff --git a/src/ui/KeyMgmt.cpp b/src/ui/KeyMgmt.cpp index d40928ee..3b04a22f 100755 --- a/src/ui/KeyMgmt.cpp +++ b/src/ui/KeyMgmt.cpp @@ -35,6 +35,9 @@ KeyMgmt::KeyMgmt(GpgME::GpgContext *ctx, QWidget *parent ) : QMainWindow(parent mKeyList->setColumnWidth(2, 250); mKeyList->setColumnWidth(3, 250); setCentralWidget(mKeyList); + mKeyList->setDoubleClickedAction([this] (const GpgKey &key, QWidget *parent) { + new KeyDetailsDialog(mCtx, key, parent); + }); createActions(); createMenus(); @@ -62,7 +65,7 @@ KeyMgmt::KeyMgmt(GpgME::GpgContext *ctx, QWidget *parent ) : QMainWindow(parent this->resize(QSize(800, 400)); } - setWindowTitle(tr("Keymanagement")); + setWindowTitle(tr("KeyPairs Management")); mKeyList->addMenuAction(deleteSelectedKeysAct); mKeyList->addMenuAction(showKeyDetailsAct); } @@ -80,9 +83,9 @@ void KeyMgmt::createActions() generateKeyPairAct->setToolTip(tr("Generate KeyPair")); connect(generateKeyPairAct, SIGNAL(triggered()), this, SLOT(slotGenerateKeyDialog())); - generateSubKeyAct = new QAction(tr("Generate SubKey"), this); + generateSubKeyAct = new QAction(tr("Generate Subkey For Selected"), this); generateSubKeyAct->setIcon(QIcon(":key_generate.png")); - generateSubKeyAct->setToolTip(tr("Generate SubKey Of KeyPair")); + generateSubKeyAct->setToolTip(tr("Generate Subkey For Selected KeyPair")); connect(generateSubKeyAct, SIGNAL(triggered()), this, SLOT(slotGenerateSubKey())); importKeyFromFileAct = new QAction(tr("&File"), this); @@ -311,5 +314,21 @@ void KeyMgmt::closeEvent(QCloseEvent *event) } void KeyMgmt::slotGenerateSubKey() { + auto selectedList = mKeyList->getSelected(); + if(selectedList->empty()) { + QMessageBox::information(nullptr, + tr("Invalid Operation"), + tr("Please select one KeyPair before doing this operation.")); + return; + } + const auto &key = mCtx->getKeyById(selectedList->first()); + if(!key.is_private_key) { + QMessageBox::critical(nullptr, + tr("Invalid Operation"), + tr("If a key pair does not have a private key then it will not be able to generate sub-keys.")); + return; + } + auto dialog = new SubkeyGenerateDialog(mCtx, key, this); + dialog->show(); } diff --git a/src/ui/KeygenDialog.cpp b/src/ui/keygen/KeygenDialog.cpp index d930fffe..347ebedc 100644 --- a/src/ui/KeygenDialog.cpp +++ b/src/ui/keygen/KeygenDialog.cpp @@ -22,7 +22,7 @@ * */ -#include "ui/KeygenDialog.h" +#include "ui/keygen/KeygenDialog.h" KeyGenDialog::KeyGenDialog(GpgME::GpgContext *ctx, QWidget *parent) : QDialog(parent), mCtx(ctx) { diff --git a/src/ui/KeygenThread.cpp b/src/ui/keygen/KeygenThread.cpp index defc20bb..5922138d 100644 --- a/src/ui/KeygenThread.cpp +++ b/src/ui/keygen/KeygenThread.cpp @@ -22,12 +22,10 @@ * */ -#include "ui/KeygenThread.h" +#include "ui/keygen/KeygenThread.h" -KeyGenThread::KeyGenThread(GenKeyInfo* keyGenParams, GpgME::GpgContext *ctx): QThread(nullptr) { - this->keyGenParams = keyGenParams; - this->mCtx = ctx; - abort = false; +KeyGenThread::KeyGenThread(GenKeyInfo* keyGenParams, GpgME::GpgContext *ctx): +mCtx(ctx), keyGenParams(keyGenParams), abort(false), QThread(nullptr) { connect(this, &KeyGenThread::finished, this, &KeyGenThread::deleteLater); } diff --git a/src/ui/keygen/SubkeyGenerateDialog.cpp b/src/ui/keygen/SubkeyGenerateDialog.cpp new file mode 100644 index 00000000..fd3b9b9e --- /dev/null +++ b/src/ui/keygen/SubkeyGenerateDialog.cpp @@ -0,0 +1,292 @@ +// +// Created by eric on 2021/5/30. +// + +#include "ui/keygen/SubkeyGenerateDialog.h" + +SubkeyGenerateDialog::SubkeyGenerateDialog(GpgME::GpgContext *ctx, const GpgKey &key, QWidget *parent) + : genKeyInfo(true), mCtx(ctx), mKey(key), QDialog(parent) { + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + keyUsageGroupBox = create_key_usage_group_box(); + + auto *groupGrid = new QGridLayout(this); + groupGrid->addWidget(create_basic_info_group_box(), 0, 0); + groupGrid->addWidget(keyUsageGroupBox, 1, 0); + + auto *nameList = new QWidget(this); + nameList->setLayout(groupGrid); + + auto *vbox2 = new QVBoxLayout(); + vbox2->addWidget(nameList); + vbox2->addWidget(errorLabel); + vbox2->addWidget(buttonBox); + + this->setWindowTitle(tr("Generate Subkey")); + + this->setLayout(vbox2); + this->setModal(true); + + set_signal_slot(); + refresh_widgets_state(); +} + +QGroupBox *SubkeyGenerateDialog::create_key_usage_group_box() { + auto *groupBox = new QGroupBox(this); + auto *grid = new QGridLayout(this); + + groupBox->setTitle("Key Usage"); + + auto* encrypt = new QCheckBox(tr("Encryption"), groupBox); + encrypt->setTristate(false); + + auto* sign = new QCheckBox(tr("Signing"),groupBox); + sign->setTristate(false); + + auto* cert = new QCheckBox(tr("Certification"),groupBox); + cert->setTristate(false); + + auto* auth = new QCheckBox(tr("Authentication"), groupBox); + auth->setTristate(false); + + keyUsageCheckBoxes.push_back(encrypt); + keyUsageCheckBoxes.push_back(sign); + keyUsageCheckBoxes.push_back(cert); + keyUsageCheckBoxes.push_back(auth); + + grid->addWidget(encrypt, 0, 0); + grid->addWidget(sign, 0, 1); + grid->addWidget(cert, 1, 0); + grid->addWidget(auth, 1, 1); + + groupBox->setLayout(grid); + + return groupBox; +} + +QGroupBox *SubkeyGenerateDialog::create_basic_info_group_box() { + errorLabel = new QLabel(tr("")); + keySizeSpinBox = new QSpinBox(this); + keyTypeComboBox = new QComboBox(this); + + for(auto &algo : GenKeyInfo::SupportedSubkeyAlgo) { + keyTypeComboBox->addItem(algo); + } + if(!GenKeyInfo::SupportedKeyAlgo.isEmpty()) { + keyTypeComboBox->setCurrentIndex(0); + } + + QDateTime maxDateTime = QDateTime::currentDateTime().addYears(2); + + dateEdit = new QDateTimeEdit(maxDateTime, this); + dateEdit->setMinimumDateTime(QDateTime::currentDateTime()); + dateEdit->setMaximumDateTime(maxDateTime); + dateEdit->setDisplayFormat("dd/MM/yyyy hh:mm:ss"); + dateEdit->setCalendarPopup(true); + dateEdit->setEnabled(true); + + expireCheckBox = new QCheckBox(this); + expireCheckBox->setCheckState(Qt::Unchecked); + + auto *vbox1 = new QGridLayout; + + vbox1->addWidget(new QLabel(tr("Expiration Date:")), 2, 0); + vbox1->addWidget(new QLabel(tr("Never Expire")), 2, 3); + vbox1->addWidget(new QLabel(tr("KeySize (in Bit):")), 1, 0); + vbox1->addWidget(new QLabel(tr("Key Type:")), 0, 0); + + vbox1->addWidget(dateEdit, 2, 1); + vbox1->addWidget(expireCheckBox, 2, 2); + vbox1->addWidget(keySizeSpinBox, 1, 1); + vbox1->addWidget(keyTypeComboBox, 0, 1); + + auto basicInfoGroupBox = new QGroupBox(); + basicInfoGroupBox->setLayout(vbox1); + basicInfoGroupBox->setTitle(tr("Basic Information")); + + return basicInfoGroupBox; +} + +void SubkeyGenerateDialog::set_signal_slot() { + connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotKeyGenAccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + connect(expireCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotExpireBoxChanged())); + + connect(keyUsageCheckBoxes[0], SIGNAL(stateChanged(int)), this, SLOT(slotEncryptionBoxChanged(int))); + connect(keyUsageCheckBoxes[1], SIGNAL(stateChanged(int)), this, SLOT(slotSigningBoxChanged(int))); + connect(keyUsageCheckBoxes[2], SIGNAL(stateChanged(int)), this, SLOT(slotCertificationBoxChanged(int))); + connect(keyUsageCheckBoxes[3], SIGNAL(stateChanged(int)), this, SLOT(slotAuthenticationBoxChanged(int))); + + connect(keyTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotActivatedKeyType(int))); +} + +void SubkeyGenerateDialog::slotExpireBoxChanged() { + if (expireCheckBox->checkState()) { + dateEdit->setEnabled(false); + } else { + dateEdit->setEnabled(true); + } +} + +void SubkeyGenerateDialog::refresh_widgets_state() { + qDebug() << "refresh_widgets_state called"; + + if(genKeyInfo.isAllowEncryption()) + keyUsageCheckBoxes[0]->setCheckState(Qt::CheckState::Checked); + else + keyUsageCheckBoxes[0]->setCheckState(Qt::CheckState::Unchecked); + + if(genKeyInfo.isAllowChangeEncryption()) + keyUsageCheckBoxes[0]->setDisabled(false); + else + keyUsageCheckBoxes[0]->setDisabled(true); + + + if(genKeyInfo.isAllowSigning()) + keyUsageCheckBoxes[1]->setCheckState(Qt::CheckState::Checked); + else + keyUsageCheckBoxes[1]->setCheckState(Qt::CheckState::Unchecked); + + if(genKeyInfo.isAllowChangeSigning()) + keyUsageCheckBoxes[1]->setDisabled(false); + else + keyUsageCheckBoxes[1]->setDisabled(true); + + + if(genKeyInfo.isAllowCertification()) + keyUsageCheckBoxes[2]->setCheckState(Qt::CheckState::Checked); + else + keyUsageCheckBoxes[2]->setCheckState(Qt::CheckState::Unchecked); + + if(genKeyInfo.isAllowChangeCertification()) + keyUsageCheckBoxes[2]->setDisabled(false); + else + keyUsageCheckBoxes[2]->setDisabled(true); + + + if(genKeyInfo.isAllowAuthentication()) + keyUsageCheckBoxes[3]->setCheckState(Qt::CheckState::Checked); + else + keyUsageCheckBoxes[3]->setCheckState(Qt::CheckState::Unchecked); + + if(genKeyInfo.isAllowChangeAuthentication()) + keyUsageCheckBoxes[3]->setDisabled(false); + else + keyUsageCheckBoxes[3]->setDisabled(true); + + + keySizeSpinBox->setRange(genKeyInfo.getSuggestMinKeySize(), genKeyInfo.getSuggestMaxKeySize()); + keySizeSpinBox->setValue(genKeyInfo.getKeySize()); + keySizeSpinBox->setSingleStep(genKeyInfo.getSizeChangeStep()); + +} + +void SubkeyGenerateDialog::slotKeyGenAccept() { + QString errorString = ""; + + /** + * primary keys should have a reasonable expiration date (no more than 2 years in the future) + */ + if(dateEdit->dateTime() > QDateTime::currentDateTime().addYears(2)) { + + errorString.append(tr(" Expiration time no more than 2 years. ")); + } + + if (errorString.isEmpty()) { + + genKeyInfo.setKeySize(keySizeSpinBox->value()); + + if (expireCheckBox->checkState()) { + genKeyInfo.setNonExpired(true); + } else { + genKeyInfo.setExpired(dateEdit->dateTime()); + } + + kg = new SubkeyGenerateThread(mKey ,&genKeyInfo, mCtx); + connect(kg, SIGNAL(signalKeyGenerated(bool)), this, SLOT(slotKeyGenResult(bool))); + kg->start(); + + this->accept(); + + auto *dialog = new QDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint); + dialog->setModal(true); + dialog->setWindowTitle(tr("Generating Subkey...")); + + auto *waitMessage = new QLabel( + tr("Collecting random data for subkey generation.\n This may take a while.\n To speed up the process use your computer\n (e.g. browse the net, listen to music,...)")); + auto *pb = new QProgressBar(); + pb->setRange(0, 0); + + auto *layout = new QVBoxLayout(dialog); + layout->addWidget(waitMessage); + layout->addWidget(pb); + dialog->setLayout(layout); + + dialog->show(); + + while (!kg->isFinished() && kg->isRunning()) { + QCoreApplication::processEvents(); + } + + dialog->close(); + + } else { + /** + * create error message + */ + errorLabel->setAutoFillBackground(true); + QPalette error = errorLabel->palette(); + error.setColor(QPalette::Background, "#ff8080"); + errorLabel->setPalette(error); + errorLabel->setText(errorString); + + this->show(); + } +} + +void SubkeyGenerateDialog::slotEncryptionBoxChanged(int state) { + if(state == 0) { + genKeyInfo.setAllowEncryption(false); + } else { + genKeyInfo.setAllowEncryption(true); + } +} + +void SubkeyGenerateDialog::slotSigningBoxChanged(int state) { + if(state == 0) { + genKeyInfo.setAllowSigning(false); + } else { + genKeyInfo.setAllowSigning(true); + } +} + +void SubkeyGenerateDialog::slotCertificationBoxChanged(int state) { + if(state == 0) { + genKeyInfo.setAllowCertification(false); + } else { + genKeyInfo.setAllowCertification(true); + } +} + +void SubkeyGenerateDialog::slotAuthenticationBoxChanged(int state) { + if(state == 0) { + genKeyInfo.setAllowAuthentication(false); + } else { + genKeyInfo.setAllowAuthentication(true); + } +} + +void SubkeyGenerateDialog::slotActivatedKeyType(int index) { + qDebug() << "key type index changed " << index; + genKeyInfo.setAlgo(this->keyTypeComboBox->itemText(index)); + refresh_widgets_state(); +} + +void SubkeyGenerateDialog::slotKeyGenResult(bool success) { + if(success) + QMessageBox::information(nullptr, tr("Success"), tr("The new subkey has been generated.")); + else + QMessageBox::critical(nullptr, tr("Failure"), tr("An error occurred during subkey generation.")); +} diff --git a/src/ui/keygen/SubkeyGenerateThread.cpp b/src/ui/keygen/SubkeyGenerateThread.cpp new file mode 100644 index 00000000..8ccec66f --- /dev/null +++ b/src/ui/keygen/SubkeyGenerateThread.cpp @@ -0,0 +1,19 @@ +// +// Created by eric on 2021/5/30. +// + +#include "ui/keygen/SubkeyGenerateThread.h" + +#include <utility> + +SubkeyGenerateThread::SubkeyGenerateThread(GpgKey key, GenKeyInfo *keyGenParams, GpgME::GpgContext *ctx) + : mKey(std::move(key)), keyGenParams(keyGenParams) , mCtx(ctx), abort( + false) { + connect(this, &SubkeyGenerateThread::finished, this, &SubkeyGenerateThread::deleteLater); +} + +void SubkeyGenerateThread::run() { + bool success = mCtx->generateSubkey(mKey, keyGenParams); + emit signalKeyGenerated(success); + emit finished({}); +} diff --git a/src/ui/keypair_details/KeyPairSubkeyTab.cpp b/src/ui/keypair_details/KeyPairSubkeyTab.cpp index 2f2a49cf..a67c119e 100644 --- a/src/ui/keypair_details/KeyPairSubkeyTab.cpp +++ b/src/ui/keypair_details/KeyPairSubkeyTab.cpp @@ -24,14 +24,72 @@ #include "ui/keypair_details/KeyPairSubkeyTab.h" -KeyPairSubkeyTab::KeyPairSubkeyTab(GpgME::GpgContext *ctx, const GpgKey &key, QWidget *parent) : mCtx(ctx), key(key), QWidget(parent) { +KeyPairSubkeyTab::KeyPairSubkeyTab(GpgME::GpgContext *ctx, const GpgKey &key, QWidget *parent) : mCtx(ctx), mKey(key), QWidget(parent) { + creatSubkeyList(); + + listBox = new QGroupBox("Subkey List"); + detailBox = new QGroupBox("Detail of Selected Subkey"); + + auto uidButtonsLayout = new QGridLayout(); + + auto addSubkeyButton = new QPushButton(tr("Add New Subkey")); + if(!mKey.is_private_key) { + addSubkeyButton->setDisabled(true); + setHidden(addSubkeyButton); + } + + uidButtonsLayout->addWidget(addSubkeyButton, 0, 1); + + auto *baseLayout = new QVBoxLayout(); + + auto subkeyListLayout = new QGridLayout(); + subkeyListLayout->addWidget(subkeyList, 0, 0); + subkeyListLayout->addLayout(uidButtonsLayout, 1, 0); + + auto *subkeyDetailLayout = new QGridLayout(); + + subkeyDetailLayout->addWidget(new QLabel(tr("Key ID: ")), 0, 0); + subkeyDetailLayout->addWidget(new QLabel(tr("Algorithm: ")), 1, 0); + subkeyDetailLayout->addWidget(new QLabel(tr("Key size:")), 2, 0); + subkeyDetailLayout->addWidget(new QLabel(tr("Usage: ")), 3, 0); + subkeyDetailLayout->addWidget(new QLabel(tr("Expires on: ")), 4, 0); + subkeyDetailLayout->addWidget(new QLabel(tr("Last Update: ")), 5, 0); + + keyidVarLabel = new QLabel(); + keySizeVarLabel = new QLabel(); + expireVarLabel = new QLabel(); + algorithmVarLabel = new QLabel(); + createdVarLabel = new QLabel(); + usageVarLabel = new QLabel(); + + subkeyDetailLayout->addWidget(keyidVarLabel, 0, 1); + subkeyDetailLayout->addWidget(keySizeVarLabel, 2, 1); + subkeyDetailLayout->addWidget(expireVarLabel, 4, 1); + subkeyDetailLayout->addWidget(algorithmVarLabel, 1, 1); + subkeyDetailLayout->addWidget(createdVarLabel, 5, 1); + subkeyDetailLayout->addWidget(usageVarLabel, 3, 1); + + listBox->setLayout(subkeyListLayout); + detailBox->setLayout(subkeyDetailLayout); + + baseLayout->addWidget(listBox); + baseLayout->addWidget(detailBox); + + connect(addSubkeyButton, SIGNAL(clicked(bool)), this, SLOT(slotAddSubkey())); + connect(mCtx, SIGNAL(signalKeyInfoChanged()), this, SLOT(slotRefreshSubkeyList())); + connect(subkeyList, SIGNAL(itemSelectionChanged()), this, SLOT(slotRefreshSubkeyDetail())); + + setLayout(baseLayout); setAttribute(Qt::WA_DeleteOnClose, true); + slotRefreshSubkeyList(); + } void KeyPairSubkeyTab::creatSubkeyList() { subkeyList = new QTableWidget(this); + subkeyList->setColumnCount(5); subkeyList->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); subkeyList->verticalHeader()->hide(); @@ -52,3 +110,86 @@ void KeyPairSubkeyTab::creatSubkeyList() { subkeyList->setHorizontalHeaderLabels(labels); subkeyList->horizontalHeader()->setStretchLastSection(true); } + +void KeyPairSubkeyTab::slotRefreshSubkeyList() { + int row = 0; + + subkeyList->setSelectionMode(QAbstractItemView::SingleSelection); + + this->buffered_subkeys.clear(); + + for(const auto &subkeys : mKey.subKeys) { + if(subkeys.disabled || subkeys.revoked) { + continue; + } + this->buffered_subkeys.push_back(&subkeys); + } + + subkeyList->setRowCount(buffered_subkeys.size()); + + for(const auto& subkeys : buffered_subkeys) { + + auto *tmp0 = new QTableWidgetItem(subkeys->id); + tmp0->setTextAlignment(Qt::AlignCenter); + subkeyList->setItem(row, 0, tmp0); + + auto *tmp1 = new QTableWidgetItem(QString::number(subkeys->length)); + tmp1->setTextAlignment(Qt::AlignCenter); + subkeyList->setItem(row, 1, tmp1); + + auto *tmp2 = new QTableWidgetItem(subkeys->pubkey_algo); + tmp2->setTextAlignment(Qt::AlignCenter); + subkeyList->setItem(row, 2, tmp2); + + auto *tmp3= new QTableWidgetItem(subkeys->timestamp.toString()); + tmp3->setTextAlignment(Qt::AlignCenter); + subkeyList->setItem(row, 3, tmp3); + + auto *tmp4= new QTableWidgetItem(subkeys->expires.toString()); + tmp4->setTextAlignment(Qt::AlignCenter); + subkeyList->setItem(row, 4, tmp4); + + row++; + } + + if(subkeyList->rowCount() > 0) { + subkeyList->selectRow(0); + } +} + +void KeyPairSubkeyTab::slotAddSubkey() { + auto dialog = new SubkeyGenerateDialog(mCtx, mKey, this); + dialog->show(); +} + +void KeyPairSubkeyTab::slotRefreshSubkeyDetail() { + + int row = 0; + + for(int i = 0 ; i < subkeyList->rowCount(); i++) { + if(subkeyList->item(row, 0)->isSelected()) break; + row++; + } + + auto key = buffered_subkeys[row]; + + keyidVarLabel->setText(key->id); + keySizeVarLabel->setText(QString::number(key->length)); + expireVarLabel->setText(key->expires.toString()); + algorithmVarLabel->setText(key->pubkey_algo); + createdVarLabel->setText(key->timestamp.toString()); + + QString usage; + QTextStream usage_steam(&usage); + + if(key->can_certify) + usage_steam << "Cert "; + if(key->can_encrypt) + usage_steam << "Encr "; + if(key->can_sign) + usage_steam << "Sign "; + if(key->can_authenticate) + usage_steam << "Auth "; + + usageVarLabel->setText(usage); +} diff --git a/src/ui/keypair_details/KeyPairUIDTab.cpp b/src/ui/keypair_details/KeyPairUIDTab.cpp index 40316163..73cd0bdb 100644 --- a/src/ui/keypair_details/KeyPairUIDTab.cpp +++ b/src/ui/keypair_details/KeyPairUIDTab.cpp @@ -124,6 +124,11 @@ void KeyPairUIDTab::slotRefreshUIDList() { row++; } + + if(uidList->rowCount() > 0) { + uidList->selectRow(0); + } + slotRefreshSigList(); } diff --git a/src/ui/widgets/KeyList.cpp b/src/ui/widgets/KeyList.cpp index 2082bbc9..27e57817 100644 --- a/src/ui/widgets/KeyList.cpp +++ b/src/ui/widgets/KeyList.cpp @@ -86,6 +86,9 @@ KeyList::KeyList(GpgME::GpgContext *ctx, setLayout(layout); popupMenu = new QMenu(this); + + connect(mKeyList, SIGNAL(doubleClicked(const QModelIndex &)), + this, SLOT(slotDoubleClicked(const QModelIndex &))); connect(mCtx, SIGNAL(signalKeyInfoChanged()), this, SLOT(slotRefresh())); setAcceptDrops(true); slotRefresh(); @@ -434,5 +437,17 @@ void KeyList::setExcludeKeys(std::initializer_list<QString> key_ids) { } void KeyList::setFilter(std::function<bool(const GpgKey &)> filter) { - this->mFilter = filter; + this->mFilter = std::move(filter); +} + +void KeyList::slotDoubleClicked(const QModelIndex &index) { + if(mAction != nullptr) { + const auto &key = mCtx->getKeyById(buffered_keys[index.row()].id); + mAction(key, this); + } + +} + +void KeyList::setDoubleClickedAction(std::function<void(const GpgKey &, QWidget *)> action) { + this->mAction = std::move(action); } |