diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-11 02:17:32 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-11 02:17:32 +0200 |
| commit | 2779c61e83491d1d798f3f494aa890e3f586245a (patch) | |
| tree | c09e6e77efd85465a331a65f5f8d73dd450d6b4f /src | |
| parent | ab3e5ad5f25862985c17ba557163a1902b54747f (diff) | |
| download | kristall-2779c61e83491d1d798f3f494aa890e3f586245a.tar.gz | |
Adds first draft of client certificate management.
Diffstat (limited to 'src')
| -rw-r--r-- | src/certificateselectiondialog.cpp | 46 | ||||
| -rw-r--r-- | src/certificateselectiondialog.hpp | 6 | ||||
| -rw-r--r-- | src/certificateselectiondialog.ui | 2 | ||||
| -rw-r--r-- | src/documentoutlinemodel.cpp | 3 | ||||
| -rw-r--r-- | src/identitycollection.cpp | 263 | ||||
| -rw-r--r-- | src/identitycollection.hpp | 78 | ||||
| -rw-r--r-- | src/kristall.hpp | 3 | ||||
| -rw-r--r-- | src/kristall.pro | 5 | ||||
| -rw-r--r-- | src/main.cpp | 5 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 7 | ||||
| -rw-r--r-- | src/newidentitiydialog.cpp | 74 | ||||
| -rw-r--r-- | src/newidentitiydialog.hpp | 40 | ||||
| -rw-r--r-- | src/newidentitiydialog.ui | 112 |
13 files changed, 641 insertions, 3 deletions
diff --git a/src/certificateselectiondialog.cpp b/src/certificateselectiondialog.cpp index 70dac42..e3aef35 100644 --- a/src/certificateselectiondialog.cpp +++ b/src/certificateselectiondialog.cpp @@ -2,6 +2,12 @@ #include "ui_certificateselectiondialog.h" #include "certificatehelper.hpp" +#include "kristall.hpp" +#include "newidentitiydialog.hpp" + + +#include <QDebug> +#include <QItemSelectionModel> CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : QDialog(parent), @@ -9,6 +15,11 @@ CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : { ui->setupUi(this); this->ui->server_request->setVisible(false); + + this->ui->certificates->setModel(&global_identities); + this->ui->certificates->expandAll(); + + connect(this->ui->certificates->selectionModel(), &QItemSelectionModel::currentChanged, this, &CertificateSelectionDialog::on_currentChanged); } CertificateSelectionDialog::~CertificateSelectionDialog() @@ -69,3 +80,38 @@ void CertificateSelectionDialog::acceptTemporaryWithTimeout(QDateTime timeout) this->accept(); } + +void CertificateSelectionDialog::on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + auto id = global_identities.getIdentity(current); + + this->ui->use_selected_cert->setEnabled(id.isValid()); +} + +void CertificateSelectionDialog::on_create_new_cert_clicked() +{ + NewIdentitiyDialog dialog { this }; + + if(dialog.exec() != QDialog::Accepted) + return; + + auto id = dialog.createIdentity(); + if(not id.isValid()) + return; + id.is_persistent = true; + + global_identities.addCertificate( + dialog.groupName(), + id); +} + +void CertificateSelectionDialog::on_use_selected_cert_clicked() +{ + auto sel = this->ui->certificates->selectionModel()->currentIndex(); + this->cryto_identity = global_identities.getIdentity(sel); + if(this->cryto_identity.isValid()) { + this->accept(); + } else { + qDebug() << "Tried to use an invalid identity when the button should not be enabled. This is a bug!"; + } +} diff --git a/src/certificateselectiondialog.hpp b/src/certificateselectiondialog.hpp index 55fe909..da43b0e 100644 --- a/src/certificateselectiondialog.hpp +++ b/src/certificateselectiondialog.hpp @@ -32,11 +32,17 @@ private slots: void on_use_temp_cert_48h_clicked(); + void on_create_new_cert_clicked(); + + void on_use_selected_cert_clicked(); + private: //! Creates an anonymous identity with a randomly chosen name that //! will time out on `timeout`, then accepts the dialog. void acceptTemporaryWithTimeout(QDateTime timeout); + + void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); private: Ui::CertificateSelectionDialog *ui; diff --git a/src/certificateselectiondialog.ui b/src/certificateselectiondialog.ui index d7179ae..0199982 100644 --- a/src/certificateselectiondialog.ui +++ b/src/certificateselectiondialog.ui @@ -35,7 +35,7 @@ </widget> </item> <item> - <widget class="QTreeView" name="treeView"/> + <widget class="QTreeView" name="certificates"/> </item> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> diff --git a/src/documentoutlinemodel.cpp b/src/documentoutlinemodel.cpp index 604ca6f..0f12233 100644 --- a/src/documentoutlinemodel.cpp +++ b/src/documentoutlinemodel.cpp @@ -115,7 +115,6 @@ QModelIndex DocumentOutlineModel::index(int row, int column, const QModelIndex & if (childItem) return createIndex(row, column, reinterpret_cast<quintptr>(childItem)); return QModelIndex(); - } QModelIndex DocumentOutlineModel::parent(const QModelIndex &child) const @@ -137,7 +136,7 @@ QModelIndex DocumentOutlineModel::parent(const QModelIndex &child) const int DocumentOutlineModel::rowCount(const QModelIndex &parent) const { - Node const *parentItem; + Node const * parentItem; if (parent.column() > 0) return 0; diff --git a/src/identitycollection.cpp b/src/identitycollection.cpp new file mode 100644 index 0000000..9955d3f --- /dev/null +++ b/src/identitycollection.cpp @@ -0,0 +1,263 @@ +#include "identitycollection.hpp" + +#include <cassert> +#include <QDebug> + +IdentityCollection::IdentityCollection(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +void IdentityCollection::load(QSettings &settings) +{ + this->beginResetModel(); + + this->root.children.clear(); + + int group_cnt = settings.beginReadArray("groups"); + for(int i = 0; i < group_cnt; i++) + { + settings.setArrayIndex(i); + auto group = std::make_unique<GroupNode>(); + + group->title = settings.value("name").toString(); + + int id_cnt = settings.beginReadArray("identities"); + + for(int j = 0; j < id_cnt; j++) + { + settings.setArrayIndex(j); + auto id = std::make_unique<IdentityNode>(); + + id->identity.is_persistent = true; + id->identity.display_name = settings.value("display_name").toString(); + + id->identity.certificate = QSslCertificate::fromData( + settings.value("certificate").toByteArray(), + QSsl::Der + ).first(); + + id->identity.private_key = QSslKey( + settings.value("private_key").toByteArray(), + QSsl::Rsa, + QSsl::Der + ); + + group->children.emplace_back(std::move(id)); + } + + settings.endArray(); + + this->root.children.emplace_back(std::move(group)); + } + settings.endArray(); + + relayout(); + + this->endResetModel(); +} + +void IdentityCollection::save(QSettings &settings) const +{ + settings.beginWriteArray("groups", int(root.children.size())); + + int grp_index = 0; + for(auto const & grp : root.children) + { + settings.setArrayIndex(grp_index); + grp_index += 1; + + auto & group = grp->as<GroupNode>(); + settings.setValue("name", group.title); + + settings.beginWriteArray("identities", int(group.children.size())); + + int id_index = 0; + for(auto const & _id : group.children) + { + settings.setArrayIndex(id_index); + id_index += 1; + + auto & id = _id->as<IdentityNode>(); + + settings.setValue("display_name", id.identity.display_name); + settings.setValue("certificate", id.identity.certificate.toDer()); + settings.setValue("private_key", id.identity.private_key.toDer()); + } + + + settings.endArray(); + } + + settings.endArray(); +} + +bool IdentityCollection::addCertificate(const QString &group_name, const CryptoIdentity &crypto_id) +{ + // Don't allow saving transient certificates + if(not crypto_id.is_persistent) + return false; + + this->beginResetModel(); + + GroupNode * group = nullptr; + for(auto const & grp : root.children) + { + auto * g = static_cast<GroupNode*>(grp.get()); + if(g->title == group_name) { + group = g; + break; + } + } + if(group == nullptr) { + group = new GroupNode(); + group->title = group_name; + this->root.children.emplace_back(group); + } + + auto id = std::make_unique<IdentityNode>(); + id->identity = crypto_id; + group->children.emplace_back(std::move(id)); + + this->relayout(); + + this->endResetModel(); + + return true; +} + +CryptoIdentity IdentityCollection::getIdentity(const QModelIndex &index) const +{ + if (!index.isValid()) + return CryptoIdentity(); + + if (index.column() != 0) + return CryptoIdentity(); + + Node const *item = static_cast<Node const*>(index.internalPointer()); + switch(item->type) { + case Node::Identity: return static_cast<IdentityNode const *>(item)->identity; + default: + return CryptoIdentity(); + } +} + +QStringList IdentityCollection::groups() const +{ + QStringList result; + for(auto const & grp : root.children) + { + result.append(grp->as<GroupNode>().title); + } + return result; +} + +//QVariant IdentityCollection::headerData(int section, Qt::Orientation orientation, int role) const +//{ +// return QVariant { }; +//} + +QModelIndex IdentityCollection::index(int row, int column, const QModelIndex &parent) const +{ + qDebug() << "index" << row << column << parent; + if (not hasIndex(row, column, parent)) + return QModelIndex(); + + Node const * parentItem; + + if(!parent.isValid()) + parentItem = &this->root; + else + parentItem = static_cast<Node*>(parent.internalPointer()); + + auto & children = parentItem->children; + if(row < 0 or size_t(row) >= children.size()) + return QModelIndex { }; + return createIndex( + row, + column, + reinterpret_cast<quintptr>(children[row].get()) + ); +} + +QModelIndex IdentityCollection::parent(const QModelIndex &index) const +{ + qDebug() << "parent" << index; + if (!index.isValid()) + return QModelIndex(); + + Node const *childItem = static_cast<Node const *>(index.internalPointer()); + Node const * parent = childItem->parent; + + if (parent == &root) + return QModelIndex(); + + return createIndex( + parent->index, + 0, + reinterpret_cast<quintptr>(parent)); +} + +int IdentityCollection::rowCount(const QModelIndex &parent) const +{ + Node const * parentItem; + + if (!parent.isValid()) + parentItem = &root; + else + parentItem = static_cast<Node const *>(parent.internalPointer()); + + int rc = parentItem->children.size(); + qDebug() << "row count for " << parent << rc; + return rc; +} + +int IdentityCollection::columnCount(const QModelIndex &parent) const +{ + qDebug() << "column count" << parent; + return 1; +} + +QVariant IdentityCollection::data(const QModelIndex &index, int role) const +{ + qDebug() << "data" << index << role; + if (!index.isValid()) + return QVariant(); + + if (index.column() != 0) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + Node const *item = static_cast<Node const*>(index.internalPointer()); + switch(item->type) { + case Node::Root: return "root"; + case Node::Group: return static_cast<GroupNode const *>(item)->title; + case Node::Identity: return static_cast<IdentityNode const *>(item)->identity.display_name; + default: + return "Unknown"; + } +} + +void IdentityCollection::relayout() +{ + for(size_t i = 0; i < root.children.size(); i++) + { + auto & group = *root.children[i]; + group.parent = &root; + group.index = i; + + qDebug() << "group[" << group.index << "]" << group.as<GroupNode>().title; + + for(size_t j = 0; j < group.children.size(); j++) + { + auto & id = *group.children[j]; + id.parent = &group; + id.index = j; + assert(id.children.size() == 0); + + qDebug() << "id[" << id.index << "]" << id.as<IdentityNode>().identity.display_name; + } + } +} diff --git a/src/identitycollection.hpp b/src/identitycollection.hpp new file mode 100644 index 0000000..38463ab --- /dev/null +++ b/src/identitycollection.hpp @@ -0,0 +1,78 @@ +#ifndef IDENTITYCOLLECTION_HPP +#define IDENTITYCOLLECTION_HPP + +#include "cryptoidentity.hpp" + +#include <QAbstractItemModel> +#include <memory> +#include <QSettings> + +class IdentityCollection : public QAbstractItemModel +{ + Q_OBJECT + struct Node { + enum Type { Root, Group, Identity }; + Node * parent = nullptr; + int index = 0; + std::vector<std::unique_ptr<Node>> children; + Type type; + explicit Node(Type t) : type(t) { } + virtual ~Node() = default; + + template<typename T> + T & as() { return *static_cast<T*>(this); } + }; + + struct IdentityNode : Node { + CryptoIdentity identity; + IdentityNode() : Node(Identity) { } + ~IdentityNode() override = default; + }; + + struct GroupNode : Node { + QString title; + GroupNode() : Node(Group) { } + ~GroupNode() override = default; + }; + + struct RootNode : Node { + RootNode() : Node(Root) { } + ~RootNode() override = default; + }; + +public: + explicit IdentityCollection(QObject *parent = nullptr); + +public: + void load(QSettings & settings); + + void save(QSettings & settings) const; + + bool addCertificate(QString const & group, CryptoIdentity const & id); + + CryptoIdentity getIdentity(QModelIndex const & index) const; + + QStringList groups() const; + +public: + // Header: + // QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + void relayout(); + +private: + RootNode root; +}; + +#endif // IDENTITYCOLLECTION_HPP diff --git a/src/kristall.hpp b/src/kristall.hpp index 73c0bc5..a0a4b49 100644 --- a/src/kristall.hpp +++ b/src/kristall.hpp @@ -4,7 +4,10 @@ #include <QSettings> #include <QClipboard> +#include "identitycollection.hpp" + extern QSettings global_settings; +extern IdentityCollection global_identities; extern QClipboard * global_clipboard; #endif // KRISTALL_HPP diff --git a/src/kristall.pro b/src/kristall.pro index 91aabf2..73f2892 100644 --- a/src/kristall.pro +++ b/src/kristall.pro @@ -37,10 +37,12 @@ SOURCES += \ geminirenderer.cpp \ gopherclient.cpp \ gophermaprenderer.cpp \ + identitycollection.cpp \ ioutil.cpp \ main.cpp \ mainwindow.cpp \ mediaplayer.cpp \ + newidentitiydialog.cpp \ plaintextrenderer.cpp \ protocolsetup.cpp \ settingsdialog.cpp \ @@ -61,10 +63,12 @@ HEADERS += \ geminirenderer.hpp \ gopherclient.hpp \ gophermaprenderer.hpp \ + identitycollection.hpp \ ioutil.hpp \ kristall.hpp \ mainwindow.hpp \ mediaplayer.hpp \ + newidentitiydialog.hpp \ plaintextrenderer.hpp \ protocolsetup.hpp \ settingsdialog.hpp \ @@ -76,6 +80,7 @@ FORMS += \ certificateselectiondialog.ui \ mainwindow.ui \ mediaplayer.ui \ + newidentitiydialog.ui \ settingsdialog.ui TRANSLATIONS += \ diff --git a/src/main.cpp b/src/main.cpp index b7974de..c280425 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include <QCommandLineParser> #include <QDebug> +IdentityCollection global_identities; QSettings global_settings { "xqTechnologies", "Kristall" }; QClipboard * global_clipboard; @@ -23,6 +24,10 @@ int main(int argc, char *argv[]) global_settings.setValue("start_page", "about:favourites"); } + global_settings.beginGroup("Client Identities"); + global_identities.load(global_settings); + global_settings.endGroup(); + MainWindow w(&app); auto urls = cli_parser.positionalArguments(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4cd1919..9a56133 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,6 +39,9 @@ MainWindow::MainWindow(QApplication * app, QWidget *parent) : ui->favourites_view->setModel(&favourites); + ui->clientcert_view->setModel(&global_identities); + ui->clientcert_view->expandAll(); + this->ui->outline_window->setVisible(false); this->ui->history_window->setVisible(false); this->ui->clientcert_window->setVisible(false); @@ -143,6 +146,10 @@ void MainWindow::saveSettings() this->favourites.save(global_settings); this->protocols.save(global_settings); + global_settings.beginGroup("Client Identities"); + global_identities.save(global_settings); + global_settings.endGroup(); + global_settings.beginGroup("Theme"); this->current_style.save(global_settings); global_settings.endGroup(); diff --git a/src/newidentitiydialog.cpp b/src/newidentitiydialog.cpp new file mode 100644 index 0000000..5419d74 --- /dev/null +++ b/src/newidentitiydialog.cpp @@ -0,0 +1,74 @@ +#include "newidentitiydialog.hpp" +#include "ui_newidentitiydialog.h" + +#include "certificatehelper.hpp" +#include "kristall.hpp" + +#include <QPushButton> +#include <QDebug> + +NewIdentitiyDialog::NewIdentitiyDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewIdentitiyDialog) +{ + ui->setupUi(this); + + ui->display_name->setText("Unnamed"); + ui->common_name->setText("Unnamed"); + ui->expiration_date->setDate(QDate::currentDate().addYears(1)); + ui->expiration_date->setTime(QTime(12, 00)); + + ui->group->clear(); + for(auto group_name : global_identities.groups()) + { + ui->group->addItem(group_name); + } +} + +NewIdentitiyDialog::~NewIdentitiyDialog() +{ + delete ui; +} + +CryptoIdentity NewIdentitiyDialog::createIdentity() const +{ + auto id = CertificateHelper::createNewIdentity( + this->ui->common_name->text(), + this->ui->expiration_date->dateTime() + ); + id.display_name = this->ui->display_name->text(); + return id; +} + +QString NewIdentitiyDialog::groupName() const +{ + return this->ui->group->currentText(); +} + +void NewIdentitiyDialog::updateUI() +{ + bool is_ok = true; + + is_ok &= (not this->ui->group->currentText().isEmpty()); + is_ok &= (not this->ui->common_name->text().isEmpty()); + is_ok &= (not this->ui->display_name->text().isEmpty()); + is_ok &= (this->ui->expiration_date->dateTime() > QDateTime::currentDateTime()); + + this->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(is_ok); +} + +void NewIdentitiyDialog::on_group_editTextChanged(const QString &arg1) +{ + qDebug() << arg1; + this->updateUI(); +} + +void NewIdentitiyDialog::on_display_name_textChanged(const QString &arg1) +{ + this->updateUI(); +} + +void NewIdentitiyDialog::on_common_name_textChanged(const QString &arg1) +{ + this->updateUI(); +} diff --git a/src/newidentitiydialog.hpp b/src/newidentitiydialog.hpp new file mode 100644 index 0000000..bc8f90e --- /dev/null +++ b/src/newidentitiydialog.hpp @@ -0,0 +1,40 @@ +#ifndef NEWIDENTITIYDIALOG_HPP +#define NEWIDENTITIYDIALOG_HPP + +#include <QDialog> + +#include "cryptoidentity.hpp" + +namespace Ui { +class NewIdentitiyDialog; +} + +class NewIdentitiyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewIdentitiyDialog(QWidget *parent = nullptr); + ~NewIdentitiyDialog(); + + //! Creates a new identity from the currently set + //! user settings. + CryptoIdentity createIdentity() const; + + QString groupName() const; + +private slots: + void on_group_editTextChanged(const QString &arg1); + + void on_display_name_textChanged(const QString &arg1); + + void on_common_name_textChanged(const QString &arg1); + +private: + void updateUI(); + +private: + Ui::NewIdentitiyDialog *ui; +}; + +#endif // NEWIDENTITIYDIALOG_HPP diff --git a/src/newidentitiydialog.ui b/src/newidentitiydialog.ui new file mode 100644 index 0000000..05c8155 --- /dev/null +++ b/src/newidentitiydialog.ui @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NewIdentitiyDialog</class> + <widget class="QDialog" name="NewIdentitiyDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>328</width> + <height>191</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QFormLayout" name="formLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Display Name</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Expiration Date</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_name"/> + </item> + <item row="3" column="1"> + <widget class="QDateTimeEdit" name="expiration_date"/> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="common_name"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Common Name</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Group</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="group"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>NewIdentitiyDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>NewIdentitiyDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> |
