dbhub: Add client certificate management

Add a list of all configured client certificates to the preferences
dialog and show some information on them.

Add two button to the preferences dialog to add and remove client
certificates.

Copy configured client certificates to some safe place where they aren't
deleted by accident.

Change the remote code to expect certificate and private key in one
file. The path to this file is still hardcoded, now to client.cert.pem.

Remove the example client certificate as it's not up-to-date anymore
anyway.

Still missing: Option to use a configured client certificate from the
preferences dialog to authenticate.
This commit is contained in:
Martin Kleusberg
2016-11-01 22:54:50 +01:00
parent 02019e391b
commit 60e4cb758e
7 changed files with 312 additions and 115 deletions

View File

@@ -11,6 +11,7 @@
#include <QColorDialog>
#include <QMessageBox>
#include <QKeyEvent>
#include <QStandardPaths>
PreferencesDialog::PreferencesDialog(QWidget* parent)
: QDialog(parent),
@@ -20,6 +21,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent)
ui->treeSyntaxHighlighting->setColumnHidden(0, true);
ui->labelDatabaseDefaultSqlText->setVisible(false);
ui->editDatabaseDefaultSqlText->setVisible(false);
ui->tableClientCerts->setColumnHidden(0, true);
ui->fr_bin_bg->installEventFilter(this);
ui->fr_bin_fg->installEventFilter(this);
@@ -112,31 +114,42 @@ void PreferencesDialog::loadSettings()
// Remote settings
ui->checkUseRemotes->setChecked(Settings::getSettingsValue("remote", "active").toBool());
auto ca_certs = static_cast<Application*>(qApp)->mainWindow()->getRemote().caCertificates();
ui->tableCaCerts->setRowCount(ca_certs.size());
for(int i=0;i<ca_certs.size();i++)
{
QSslCertificate cert = ca_certs.at(i);
auto ca_certs = static_cast<Application*>(qApp)->mainWindow()->getRemote().caCertificates();
ui->tableCaCerts->setRowCount(ca_certs.size());
for(int i=0;i<ca_certs.size();i++)
{
QSslCertificate cert = ca_certs.at(i);
QTableWidgetItem* cert_cn = new QTableWidgetItem(cert.subjectInfo(QSslCertificate::CommonName).at(0));
cert_cn->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 0, cert_cn);
QTableWidgetItem* cert_cn = new QTableWidgetItem(cert.subjectInfo(QSslCertificate::CommonName).at(0));
cert_cn->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 0, cert_cn);
QTableWidgetItem* cert_o = new QTableWidgetItem(cert.subjectInfo(QSslCertificate::Organization).at(0));
cert_o->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 1, cert_o);
QTableWidgetItem* cert_o = new QTableWidgetItem(cert.subjectInfo(QSslCertificate::Organization).at(0));
cert_o->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 1, cert_o);
QTableWidgetItem* cert_from = new QTableWidgetItem(cert.effectiveDate().toString());
cert_from->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 2, cert_from);
QTableWidgetItem* cert_from = new QTableWidgetItem(cert.effectiveDate().toString());
cert_from->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 2, cert_from);
QTableWidgetItem* cert_to = new QTableWidgetItem(cert.expiryDate().toString());
cert_to->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 3, cert_to);
QTableWidgetItem* cert_to = new QTableWidgetItem(cert.expiryDate().toString());
cert_to->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 3, cert_to);
QTableWidgetItem* cert_serialno = new QTableWidgetItem(QString(cert.serialNumber()));
cert_serialno->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 4, cert_serialno);
QTableWidgetItem* cert_serialno = new QTableWidgetItem(QString(cert.serialNumber()));
cert_serialno->setFlags(Qt::ItemIsSelectable);
ui->tableCaCerts->setItem(i, 4, cert_serialno);
}
}
{
QStringList client_certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
foreach(const QString& file, client_certs)
{
auto certs = QSslCertificate::fromPath(file);
foreach(const QSslCertificate& cert, certs)
addClientCertToTable(file, cert);
}
}
// Gracefully handle the preferred Editor font not being available
@@ -206,7 +219,47 @@ void PreferencesDialog::saveSettings()
Settings::setSettingsValue("extensions", "list", extList);
Settings::setSettingsValue("extensions", "disableregex", ui->checkRegexDisabled->isChecked());
// Save remote settings
Settings::setSettingsValue("remote", "active", ui->checkUseRemotes->isChecked());
QStringList old_client_certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
QStringList new_client_certs;
for(int i=0;i<ui->tableClientCerts->rowCount();i++)
{
// Loop through the new list of client certs
// If this certificate was already imported, remove it from the list of old certificates. All remaining certificates on this
// list will be deleted later on.
QString path = ui->tableClientCerts->item(i, 0)->text();
if(old_client_certs.contains(path))
{
// This is a cert that is already imported
old_client_certs.removeAll(path);
new_client_certs.push_back(path);
} else {
// This is a new certificate. Copy file to a safe place.
// Generate unique destination file name
QString copy_to = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).append("/").append(QFileInfo(path).fileName());
int suffix = 0;
do
{
suffix++;
} while(QFile::exists(copy_to + QString::number(suffix)));
// Copy file
copy_to.append(QString::number(suffix));
QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
QFile::copy(path, copy_to);
new_client_certs.push_back(copy_to);
}
}
foreach(const QString& file, old_client_certs)
{
// Now only the deleted client certs are still in the old list. Delete the cert files associated with them.
QFile::remove(file);
}
Settings::setSettingsValue("remote", "client_certificates", new_client_certs);
// Warn about restarting to change language
QVariant newLanguage = ui->languageComboBox->itemData(ui->languageComboBox->currentIndex());
@@ -373,3 +426,76 @@ void PreferencesDialog::activateRemoteTab(bool active)
{
ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(ui->tabRemote), active);
}
void PreferencesDialog::addClientCertificate()
{
// Get certificate file to import and abort here if no file gets selected
// NOTE: We assume here that this file contains both, certificate and private key!
QString path = FileDialog::getOpenFileName(this, tr("Import certificate file"), "*.pem");
if(path.isEmpty())
return;
// Open file and check if any certificates were imported
auto certs = QSslCertificate::fromPath(path);
if(certs.size() == 0)
{
QMessageBox::warning(this, qApp->applicationName(), tr("No certificates found in this file."));
return;
}
// Add certificates to list
for(int i=0;i<certs.size();i++)
addClientCertToTable(path, certs.at(i));
}
void PreferencesDialog::removeClientCertificate()
{
// Any row selected?
int row = ui->tableClientCerts->currentRow();
if(row == -1)
return;
// Double check
if(QMessageBox::question(this, qApp->applicationName(), tr("Are you sure you want do remove this certificate? All certificate "
"data will be deleted from the application settings!"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)
{
ui->tableClientCerts->removeRow(row);
}
}
void PreferencesDialog::addClientCertToTable(const QString& path, const QSslCertificate& cert)
{
// Do nothing if the file doesn't even exist
if(!QFile::exists(path))
return;
// Add new row
int row = ui->tableClientCerts->rowCount();
ui->tableClientCerts->setRowCount(row + 1);
// Fill row with data
QTableWidgetItem* cert_file = new QTableWidgetItem(path);
cert_file->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 0, cert_file);
QTableWidgetItem* cert_subject_cn = new QTableWidgetItem(cert.subjectInfo(QSslCertificate::CommonName).at(0));
cert_subject_cn->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 1, cert_subject_cn);
QTableWidgetItem* cert_issuer_cn = new QTableWidgetItem(cert.issuerInfo(QSslCertificate::CommonName).at(0));
cert_issuer_cn->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 2, cert_issuer_cn);
QTableWidgetItem* cert_from = new QTableWidgetItem(cert.effectiveDate().toString());
cert_from->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 3, cert_from);
QTableWidgetItem* cert_to = new QTableWidgetItem(cert.expiryDate().toString());
cert_to->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 4, cert_to);
QTableWidgetItem* cert_serialno = new QTableWidgetItem(QString(cert.serialNumber()));
cert_serialno->setFlags(Qt::ItemIsSelectable);
ui->tableClientCerts->setItem(row, 5, cert_serialno);
}

View File

@@ -7,6 +7,8 @@
class QTreeWidgetItem;
class QFrame;
class QTableWidget;
class QSslCertificate;
namespace Ui {
class PreferencesDialog;
@@ -29,6 +31,8 @@ private slots:
virtual void addExtension();
virtual void removeExtension();
virtual void activateRemoteTab(bool active);
virtual void addClientCertificate();
virtual void removeClientCertificate();
private:
Ui::PreferencesDialog *ui;
@@ -36,6 +40,7 @@ private:
void fillLanguageBox();
void loadColorSetting(QFrame *frame, const QString &name);
void saveColorSetting(QFrame *frame, const QString &name);
void addClientCertToTable(const QString& path, const QSslCertificate& cert);
protected:
bool eventFilter(QObject *obj, QEvent *event);

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>590</width>
<width>597</width>
<height>614</height>
</rect>
</property>
@@ -282,7 +282,7 @@
<item row="7" column="0">
<widget class="QLabel" name="labelDatabaseDefaultSqlText">
<property name="text">
<string>SQL to execute after opening database</string>
<string>SQ&amp;L to execute after opening database</string>
</property>
<property name="buddy">
<cstring>editDatabaseDefaultSqlText</cstring>
@@ -1020,6 +1020,15 @@
</item>
<item row="0" column="1">
<widget class="QTableWidget" name="tableCaCerts">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<column>
<property name="text">
<string>Subject CN</string>
@@ -1053,6 +1062,102 @@
</column>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Your certificates</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QTableWidget" name="tableClientCerts">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<column>
<property name="text">
<string>File</string>
</property>
</column>
<column>
<property name="text">
<string>Subject CN</string>
</property>
<property name="toolTip">
<string>Subject Common Name</string>
</property>
</column>
<column>
<property name="text">
<string>Issuer CN</string>
</property>
<property name="toolTip">
<string>Issuer Common Name</string>
</property>
</column>
<column>
<property name="text">
<string>Valid from</string>
</property>
</column>
<column>
<property name="text">
<string>Valid to</string>
</property>
</column>
<column>
<property name="text">
<string>Serial number</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QToolButton" name="buttonClientCertAdd">
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/trigger_create</normaloff>:/icons/trigger_create</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonClientCertRemove">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/trigger_delete</normaloff>:/icons/trigger_delete</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
@@ -1224,8 +1329,8 @@
<y>207</y>
</hint>
<hint type="destinationlabel">
<x>55</x>
<y>252</y>
<x>108</x>
<y>280</y>
</hint>
</hints>
</connection>
@@ -1261,6 +1366,38 @@
</hint>
</hints>
</connection>
<connection>
<sender>buttonClientCertAdd</sender>
<signal>clicked()</signal>
<receiver>PreferencesDialog</receiver>
<slot>addClientCertificate()</slot>
<hints>
<hint type="sourcelabel">
<x>563</x>
<y>257</y>
</hint>
<hint type="destinationlabel">
<x>596</x>
<y>243</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonClientCertRemove</sender>
<signal>clicked()</signal>
<receiver>PreferencesDialog</receiver>
<slot>removeClientCertificate()</slot>
<hints>
<hint type="sourcelabel">
<x>561</x>
<y>289</y>
</hint>
<hint type="destinationlabel">
<x>597</x>
<y>295</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>saveSettings()</slot>
@@ -1269,5 +1406,7 @@
<slot>addExtension()</slot>
<slot>removeExtension()</slot>
<slot>activateRemoteTab(bool)</slot>
<slot>addClientCertificate()</slot>
<slot>removeClientCertificate()</slot>
</slots>
</ui>

View File

@@ -9,6 +9,7 @@
#include "RemoteDatabase.h"
#include "version.h"
#include "FileDialog.h"
#include "Settings.h"
RemoteDatabase::RemoteDatabase() :
m_manager(new QNetworkAccessManager),
@@ -27,18 +28,14 @@ RemoteDatabase::RemoteDatabase() :
caCerts += QSslCertificate::fromPath(":/certs/" + caCertName);
m_sslConfiguration.setCaCertificates(caCerts);
// Load client cert
QFile fileClientCert("client.crt");
// Load client cert and private key
QFile fileClientCert("client.cert.pem");
fileClientCert.open(QFile::ReadOnly);
QSslCertificate clientCert(&fileClientCert);
fileClientCert.seek(0);
QSslKey clientKey(&fileClientCert, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
fileClientCert.close();
m_sslConfiguration.setLocalCertificate(clientCert);
// Load private key
QFile fileClientKey("client.key");
fileClientKey.open(QFile::ReadOnly);
QSslKey clientKey(&fileClientKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "password");
fileClientKey.close();
m_sslConfiguration.setPrivateKey(clientKey);
// Load settings and set up some more stuff while doing so
@@ -58,6 +55,18 @@ RemoteDatabase::~RemoteDatabase()
void RemoteDatabase::reloadSettings()
{
// Load all configured client certificates
m_clientCertFiles.clear();
auto client_certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
foreach(const QString& path, client_certs)
{
QFile file(path);
file.open(QFile::ReadOnly);
QSslCertificate cert(&file);
file.close();
m_clientCertFiles.insert(path, cert);
}
// TODO Add support for proxies here
}

View File

@@ -21,6 +21,7 @@ public:
void reloadSettings();
const QList<QSslCertificate>& caCertificates() const;
const QMap<QString, QSslCertificate>& clientCertificates() const { return m_clientCertFiles; }
void fetchDatabase(const QString& url);
@@ -37,6 +38,7 @@ private:
QProgressDialog* m_progress;
QNetworkReply* m_currentReply;
QSslConfiguration m_sslConfiguration;
QMap<QString, QSslCertificate> m_clientCertFiles;
};
#endif

View File

@@ -1,30 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFFTCCAv0CAQEwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV
BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
ZDAeFw0xNjEwMTcxNDU4NTNaFw0xNzEwMTcxNDU4NTNaMFwxCzAJBgNVBAYTAkFV
MRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz
IFB0eSBMdGQxFTATBgNVBAMMDFRlc3QgY2VydCAjMTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBALU9e88zfZ52lZZXK4PpNEitP9xOf3o7prZsFnRLTGqT
HUB69ZKeJcXrEoMi6Etl6Uek9Q246J2VKNfrORxMoCaukBh4WE2aj9YPFSdQPjEs
y9+YM3wbGIFfeuhAMuoNWm+opulfDEbYoZkPGIN0ouos9BYdjygkZoPBauGcWOky
PO7Peh5gMyuq23idtiHuLxtbh4K8WGNAgUQBrL5jRrZcblTBErH+G6XoQqvX4SF/
TyPqMiD+aj8oVVLBLhWN9C5qYeMltj8CIno8ko1P8PP5KH1rsUaj3UeisGh1rJPw
Tvss9LeEwgcoZcIV2aeRrqHn/XX9VgzCVzIcOMcCUqc9LHsamJjqCs1c0s+ZsHgm
NlR/xzEQNf817fNOBhpDO+ujf2z/qHmubL3pQVQyo+pmDJm7EZ4BcuwG8CnulptR
yJZTWio8pOC9J/R8dci3cyrVLoxgayQdENFrqoKBkRVWGwKbu1OfRAo4hTSjSCIS
UqfAib0Qj/YZrM7flH3U3S1+FXoLjcch1S/btvxVaXG7DhsoH/X3ql7Mnjw0tXJp
Fjlqlf9uD12FJH8Bc3OiSnJnE0sH3VGrW5Zb53SuCKM+r18WrtzWyv9k9EE0gICo
uCwL4AVmSAdU1C8pEymW0RBVqzAEvuEE0ZkhxCXm8ZJBgcuATj0KAjj4r7OMdb+Z
AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAEmM9OAIhUsqk/EI3x4qPDRq16VAARnA
bYebzQ44z4eGgOSIbLGXn+aLCwgYjpsjFCeDjKmy1SRWNFuUCT3afcQGkHXM9g4n
qAVWSEnCDSSwghdPJokPw3peUNzb/cx46kqz7gAUpTW1q3rMAxvTF4igPBsls9fQ
JM1U6daHi8EYl2NXWE4BDQMEDeq92pzEI9rFxka0l7ZkjbZETtz9BktvpSGGhQmx
KryO0g4+4WoJO4RmXY087rWZD3B9cZGJI363legq7TC1qr69076i+cd0q1qQnj73
E05KBtgfHQJtEL3+pQwYU4XU210FoJ/uFA8crE2LgHcPjb6NwZchyNETjXeqm9mv
7U9hGQC8Cq9rllwFtJ4Y2XU6iXYIXcK0Z6Qk82fkAgegDejmft8XqObOyS+pr6Zu
JlhOjUSxr6D/TJOkxp1DtfTFqrvtq0iFMtsagGl7weLlfv0t0QXy1vbVNFvaWfgC
qejq/JrHzsjbamuKAjpmoFv34MwGDt05GVVx7+Qavk/bo/jYXyBlWJpV8GJN9vMi
0Zg3RyX05s2KJWX4PQs2ZncYAKOIZEhKT3LuhqE+eak+++GPt1ul/yNeEDGUIzQW
6B1/tAaQZhoY2plZ1grrnyFr9BhOXLylL/X/Rr2Pw1huexa/K/Gbp+w2rr/3/zop
ZoUymJ60DOFt
-----END CERTIFICATE-----

View File

@@ -1,54 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,FAC500C30C704082
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
-----END RSA PRIVATE KEY-----