diff options
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); } |