Browse Source

Load WebUI certificate & key from file path

This allow users to update certificate & key more easily, i.e. without the need to import them
into qbt.

Closes #6675, #7547, #8315, #8564.
adaptive-webui-19844
Chocobo1 6 years ago
parent
commit
5cdb3b6a2d
No known key found for this signature in database
GPG Key ID: 210D9C873253A68C
  1. 96
      src/base/http/server.cpp
  2. 17
      src/base/http/server.h
  3. 16
      src/base/preferences.cpp
  4. 8
      src/base/preferences.h
  5. 29
      src/base/utils/net.cpp
  6. 8
      src/base/utils/net.h
  7. 129
      src/gui/optionsdialog.cpp
  8. 18
      src/gui/optionsdialog.h
  9. 76
      src/gui/optionsdialog.ui
  10. 25
      src/webui/api/appcontroller.cpp
  11. 19
      src/webui/webui.cpp
  12. 20
      src/webui/www/private/preferences_content.html

96
src/base/http/server.cpp

@ -30,55 +30,69 @@
#include "server.h" #include "server.h"
#include <algorithm>
#include <QMutableListIterator> #include <QMutableListIterator>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QSslCipher>
#include <QSslSocket>
#include <QStringList> #include <QStringList>
#include <QTimer> #include <QTimer>
#ifndef QT_NO_OPENSSL
#include <QSslSocket>
#else
#include <QTcpSocket>
#endif
#include "base/utils/net.h"
#include "connection.h" #include "connection.h"
static const int KEEP_ALIVE_DURATION = 7 * 1000; // milliseconds namespace
static const int CONNECTIONS_LIMIT = 500; {
static const int CONNECTIONS_SCAN_INTERVAL = 2; // seconds const int KEEP_ALIVE_DURATION = 7 * 1000; // milliseconds
const int CONNECTIONS_LIMIT = 500;
const int CONNECTIONS_SCAN_INTERVAL = 2; // seconds
QList<QSslCipher> safeCipherList()
{
const QStringList badCiphers = {"idea", "rc4"};
const QList<QSslCipher> allCiphers = QSslSocket::supportedCiphers();
QList<QSslCipher> safeCiphers;
for (const QSslCipher &cipher : allCiphers) {
bool isSafe = true;
for (const QString &badCipher : badCiphers) {
if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) {
isSafe = false;
break;
}
}
if (isSafe)
safeCiphers += cipher;
}
return safeCiphers;
}
}
using namespace Http; using namespace Http;
Server::Server(IRequestHandler *requestHandler, QObject *parent) Server::Server(IRequestHandler *requestHandler, QObject *parent)
: QTcpServer(parent) : QTcpServer(parent)
, m_requestHandler(requestHandler) , m_requestHandler(requestHandler)
#ifndef QT_NO_OPENSSL
, m_https(false) , m_https(false)
#endif
{ {
setProxy(QNetworkProxy::NoProxy); setProxy(QNetworkProxy::NoProxy);
#ifndef QT_NO_OPENSSL
QSslSocket::setDefaultCiphers(safeCipherList()); QSslSocket::setDefaultCiphers(safeCipherList());
#endif
QTimer *dropConnectionTimer = new QTimer(this); QTimer *dropConnectionTimer = new QTimer(this);
connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection); connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection);
dropConnectionTimer->start(CONNECTIONS_SCAN_INTERVAL * 1000); dropConnectionTimer->start(CONNECTIONS_SCAN_INTERVAL * 1000);
} }
Server::~Server()
{
}
void Server::incomingConnection(qintptr socketDescriptor) void Server::incomingConnection(qintptr socketDescriptor)
{ {
if (m_connections.size() >= CONNECTIONS_LIMIT) return; if (m_connections.size() >= CONNECTIONS_LIMIT) return;
QTcpSocket *serverSocket; QTcpSocket *serverSocket;
#ifndef QT_NO_OPENSSL
if (m_https) if (m_https)
serverSocket = new QSslSocket(this); serverSocket = new QSslSocket(this);
else else
#endif
serverSocket = new QTcpSocket(this); serverSocket = new QTcpSocket(this);
if (!serverSocket->setSocketDescriptor(socketDescriptor)) { if (!serverSocket->setSocketDescriptor(socketDescriptor)) {
@ -86,7 +100,6 @@ void Server::incomingConnection(qintptr socketDescriptor)
return; return;
} }
#ifndef QT_NO_OPENSSL
if (m_https) { if (m_https) {
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols); static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key); static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
@ -94,7 +107,6 @@ void Server::incomingConnection(qintptr socketDescriptor)
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone); static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
static_cast<QSslSocket *>(serverSocket)->startServerEncryption(); static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
} }
#endif
Connection *c = new Connection(serverSocket, m_requestHandler, this); Connection *c = new Connection(serverSocket, m_requestHandler, this);
m_connections.append(c); m_connections.append(c);
@ -112,27 +124,21 @@ void Server::dropTimedOutConnection()
} }
} }
#ifndef QT_NO_OPENSSL bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privateKey)
bool Server::setupHttps(const QByteArray &certificates, const QByteArray &key)
{ {
QSslKey sslKey(key, QSsl::Rsa); const QList<QSslCertificate> certs {Utils::Net::loadSSLCertificate(certificates)};
if (sslKey.isNull()) const QSslKey key {Utils::Net::loadSSLKey(privateKey)};
sslKey = QSslKey(key, QSsl::Ec);
const QList<QSslCertificate> certs = QSslCertificate::fromData(certificates); if (certs.isEmpty() || key.isNull()) {
const bool areCertsValid = !certs.empty() && std::all_of(certs.begin(), certs.end(), [](const QSslCertificate &c) { return !c.isNull(); }); disableHttps();
return false;
}
if (!sslKey.isNull() && areCertsValid) { m_key = key;
m_key = sslKey;
m_certificates = certs; m_certificates = certs;
m_https = true; m_https = true;
return true; return true;
} }
else {
disableHttps();
return false;
}
}
void Server::disableHttps() void Server::disableHttps()
{ {
@ -140,25 +146,3 @@ void Server::disableHttps()
m_certificates.clear(); m_certificates.clear();
m_key.clear(); m_key.clear();
} }
QList<QSslCipher> Server::safeCipherList() const
{
const QStringList badCiphers = {"idea", "rc4"};
const QList<QSslCipher> allCiphers = QSslSocket::supportedCiphers();
QList<QSslCipher> safeCiphers;
for (const QSslCipher &cipher : allCiphers) {
bool isSafe = true;
for (const QString &badCipher : badCiphers) {
if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) {
isSafe = false;
break;
}
}
if (isSafe)
safeCiphers += cipher;
}
return safeCiphers;
}
#endif // QT_NO_OPENSSL

17
src/base/http/server.h

@ -31,13 +31,9 @@
#ifndef HTTP_SERVER_H #ifndef HTTP_SERVER_H
#define HTTP_SERVER_H #define HTTP_SERVER_H
#include <QTcpServer>
#ifndef QT_NO_OPENSSL
#include <QSslCertificate> #include <QSslCertificate>
#include <QSslCipher>
#include <QSslKey> #include <QSslKey>
#endif #include <QTcpServer>
namespace Http namespace Http
{ {
@ -50,13 +46,10 @@ namespace Http
Q_DISABLE_COPY(Server) Q_DISABLE_COPY(Server)
public: public:
Server(IRequestHandler *requestHandler, QObject *parent = nullptr); explicit Server(IRequestHandler *requestHandler, QObject *parent = nullptr);
~Server();
#ifndef QT_NO_OPENSSL bool setupHttps(const QByteArray &certificates, const QByteArray &privateKey);
bool setupHttps(const QByteArray &certificates, const QByteArray &key);
void disableHttps(); void disableHttps();
#endif
private slots: private slots:
void dropTimedOutConnection(); void dropTimedOutConnection();
@ -67,13 +60,9 @@ namespace Http
IRequestHandler *m_requestHandler; IRequestHandler *m_requestHandler;
QList<Connection *> m_connections; // for tracking persistent connections QList<Connection *> m_connections; // for tracking persistent connections
#ifndef QT_NO_OPENSSL
QList<QSslCipher> safeCipherList() const;
bool m_https; bool m_https;
QList<QSslCertificate> m_certificates; QList<QSslCertificate> m_certificates;
QSslKey m_key; QSslKey m_key;
#endif
}; };
} }

16
src/base/preferences.cpp

@ -633,24 +633,24 @@ void Preferences::setWebUiHttpsEnabled(bool enabled)
setValue("Preferences/WebUI/HTTPS/Enabled", enabled); setValue("Preferences/WebUI/HTTPS/Enabled", enabled);
} }
QByteArray Preferences::getWebUiHttpsCertificate() const QString Preferences::getWebUIHttpsCertificatePath() const
{ {
return value("Preferences/WebUI/HTTPS/Certificate").toByteArray(); return value("Preferences/WebUI/HTTPS/CertificatePath").toString();
} }
void Preferences::setWebUiHttpsCertificate(const QByteArray &data) void Preferences::setWebUIHttpsCertificatePath(const QString &path)
{ {
setValue("Preferences/WebUI/HTTPS/Certificate", data); setValue("Preferences/WebUI/HTTPS/CertificatePath", path);
} }
QByteArray Preferences::getWebUiHttpsKey() const QString Preferences::getWebUIHttpsKeyPath() const
{ {
return value("Preferences/WebUI/HTTPS/Key").toByteArray(); return value("Preferences/WebUI/HTTPS/KeyPath").toString();
} }
void Preferences::setWebUiHttpsKey(const QByteArray &data) void Preferences::setWebUIHttpsKeyPath(const QString &path)
{ {
setValue("Preferences/WebUI/HTTPS/Key", data); setValue("Preferences/WebUI/HTTPS/KeyPath", path);
} }
bool Preferences::isAltWebUiEnabled() const bool Preferences::isAltWebUiEnabled() const

8
src/base/preferences.h

@ -203,10 +203,10 @@ public:
// HTTPS // HTTPS
bool isWebUiHttpsEnabled() const; bool isWebUiHttpsEnabled() const;
void setWebUiHttpsEnabled(bool enabled); void setWebUiHttpsEnabled(bool enabled);
QByteArray getWebUiHttpsCertificate() const; QString getWebUIHttpsCertificatePath() const;
void setWebUiHttpsCertificate(const QByteArray &data); void setWebUIHttpsCertificatePath(const QString &path);
QByteArray getWebUiHttpsKey() const; QString getWebUIHttpsKeyPath() const;
void setWebUiHttpsKey(const QByteArray &data); void setWebUIHttpsKeyPath(const QString &path);
bool isAltWebUiEnabled() const; bool isAltWebUiEnabled() const;
void setAltWebUiEnabled(bool enabled); void setAltWebUiEnabled(bool enabled);
QString getWebUiRootFolder() const; QString getWebUiRootFolder() const;

29
src/base/utils/net.cpp

@ -28,6 +28,8 @@
#include "net.h" #include "net.h"
#include <QSslCertificate>
#include <QSslKey>
#include <QString> #include <QString>
namespace Utils namespace Utils
@ -88,5 +90,32 @@ namespace Utils
{ {
return subnet.first.toString() + '/' + QString::number(subnet.second); return subnet.first.toString() + '/' + QString::number(subnet.second);
} }
QList<QSslCertificate> loadSSLCertificate(const QByteArray &data)
{
const QList<QSslCertificate> certs {QSslCertificate::fromData(data)};
if (std::any_of(certs.cbegin(), certs.cend(), [](const QSslCertificate &c) { return c.isNull(); }))
return {};
return certs;
}
bool isSSLCertificatesValid(const QByteArray &data)
{
return !loadSSLCertificate(data).isEmpty();
}
QSslKey loadSSLKey(const QByteArray &data)
{
// try different formats
QSslKey key {data, QSsl::Rsa};
if (!key.isNull())
return key;
return QSslKey(data, QSsl::Ec);
}
bool isSSLKeyValid(const QByteArray &data)
{
return !loadSSLKey(data).isNull();
}
} }
} }

8
src/base/utils/net.h

@ -33,6 +33,8 @@
#include <QList> #include <QList>
#include <QPair> #include <QPair>
class QSslCertificate;
class QSslKey;
class QString; class QString;
namespace Utils namespace Utils
@ -47,6 +49,12 @@ namespace Utils
bool isLoopbackAddress(const QHostAddress &addr); bool isLoopbackAddress(const QHostAddress &addr);
bool isIPInRange(const QHostAddress &addr, const QList<Subnet> &subnets); bool isIPInRange(const QHostAddress &addr, const QList<Subnet> &subnets);
QString subnetToString(const Subnet &subnet); QString subnetToString(const Subnet &subnet);
const int MAX_SSL_FILE_SIZE = 1024 * 1024;
QList<QSslCertificate> loadSSLCertificate(const QByteArray &data);
bool isSSLCertificatesValid(const QByteArray &data);
QSslKey loadSSLKey(const QByteArray &data);
bool isSSLKeyValid(const QByteArray &data);
} }
} }

129
src/gui/optionsdialog.cpp

@ -42,13 +42,9 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QTranslator> #include <QTranslator>
#ifndef QT_NO_OPENSSL
#include <QSslCertificate>
#include <QSslKey>
#endif
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/global.h" #include "base/global.h"
#include "base/http/server.h"
#include "base/net/dnsupdater.h" #include "base/net/dnsupdater.h"
#include "base/net/portforwarder.h" #include "base/net/portforwarder.h"
#include "base/net/proxyconfigurationmanager.h" #include "base/net/proxyconfigurationmanager.h"
@ -59,6 +55,7 @@
#include "base/torrentfileguard.h" #include "base/torrentfileguard.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/net.h"
#include "base/utils/password.h" #include "base/utils/password.h"
#include "base/utils/random.h" #include "base/utils/random.h"
#include "addnewtorrentdialog.h" #include "addnewtorrentdialog.h"
@ -196,11 +193,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
} }
#endif #endif
#if defined(QT_NO_OPENSSL)
m_ui->checkWebUiHttps->setVisible(false);
m_ui->checkSmtpSSL->setVisible(false);
#endif
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
m_ui->checkStartup->setVisible(false); m_ui->checkStartup->setVisible(false);
#endif #endif
@ -390,14 +382,23 @@ OptionsDialog::OptionsDialog(QWidget *parent)
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
// Web UI tab // Web UI tab
m_ui->textWebUIHttpsCert->setMode(FileSystemPathEdit::Mode::FileOpen);
m_ui->textWebUIHttpsCert->setFileNameFilter(tr("Certificate") + QLatin1String(" (*.cer *.crt *.pem)"));
m_ui->textWebUIHttpsCert->setDialogCaption(tr("Select certificate"));
m_ui->textWebUIHttpsKey->setMode(FileSystemPathEdit::Mode::FileOpen);
m_ui->textWebUIHttpsKey->setFileNameFilter(tr("Private key") + QLatin1String(" (*.key *.pem)"));
m_ui->textWebUIHttpsKey->setDialogCaption(tr("Select private key"));
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUi, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiAddress, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->spinWebUiPort, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUIUPnP, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkWebUiHttps, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->btnWebUiKey, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->btnWebUiCrt, &QAbstractButton::clicked, this, &ThisType::enableApplyButton); connect(m_ui->textWebUIHttpsCert, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsCertChanged(s, ShowError::Show); });
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUIHttpsKey, &FileSystemPathLineEdit::selectedPathChanged, this, [this](const QString &s) { webUIHttpsKeyChanged(s, ShowError::Show); });
connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiUsername, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->textWebUiPassword, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@ -722,11 +723,8 @@ void OptionsDialog::saveOptions()
pref->setWebUiPort(m_ui->spinWebUiPort->value()); pref->setWebUiPort(m_ui->spinWebUiPort->value());
pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked()); pref->setUPnPForWebUIPort(m_ui->checkWebUIUPnP->isChecked());
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked()); pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked());
// HTTPS pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
if (m_ui->checkWebUiHttps->isChecked()) { pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
pref->setWebUiHttpsCertificate(m_sslCert);
pref->setWebUiHttpsKey(m_sslKey);
}
// Authentication // Authentication
pref->setWebUiUsername(webUiUsername()); pref->setWebUiUsername(webUiUsername());
if (!webUiPassword().isEmpty()) if (!webUiPassword().isEmpty())
@ -1089,8 +1087,8 @@ void OptionsDialog::loadOptions()
m_ui->spinWebUiPort->setValue(pref->getWebUiPort()); m_ui->spinWebUiPort->setValue(pref->getWebUiPort());
m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort()); m_ui->checkWebUIUPnP->setChecked(pref->useUPnPForWebUIPort());
m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled()); m_ui->checkWebUiHttps->setChecked(pref->isWebUiHttpsEnabled());
setSslCertificate(pref->getWebUiHttpsCertificate()); webUIHttpsCertChanged(pref->getWebUIHttpsCertificatePath(), ShowError::NotShow);
setSslKey(pref->getWebUiHttpsKey()); webUIHttpsKeyChanged(pref->getWebUIHttpsKeyPath(), ShowError::NotShow);
m_ui->textWebUiUsername->setText(pref->getWebUiUsername()); m_ui->textWebUiUsername->setText(pref->getWebUiUsername());
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled()); m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled()); m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
@ -1559,39 +1557,57 @@ QString OptionsDialog::webUiPassword() const
return m_ui->textWebUiPassword->text(); return m_ui->textWebUiPassword->text();
} }
void OptionsDialog::showConnectionTab() void OptionsDialog::webUIHttpsCertChanged(const QString &path, const ShowError showError)
{ {
m_ui->tabSelection->setCurrentRow(TAB_CONNECTION); m_ui->textWebUIHttpsCert->setSelectedPath(path);
} m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
void OptionsDialog::on_btnWebUiCrt_clicked() if (path.isEmpty())
{
const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL certificate"), QString(), tr("SSL Certificate") + QLatin1String(" (*.crt *.pem)"));
if (filename.isEmpty())
return; return;
QFile cert(filename); QFile file(path);
if (!cert.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly)) {
if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
return; return;
}
bool success = setSslCertificate(cert.read(1024 * 1024)); if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) {
if (!success) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate.")); QMessageBox::warning(this, tr("Invalid certificate"), tr("This is not a valid SSL certificate."));
return;
} }
void OptionsDialog::on_btnWebUiKey_clicked() m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
}
void OptionsDialog::webUIHttpsKeyChanged(const QString &path, const ShowError showError)
{ {
const QString filename = QFileDialog::getOpenFileName(this, tr("Import SSL key"), QString(), tr("SSL key") + QLatin1String(" (*.key *.pem)")); m_ui->textWebUIHttpsKey->setSelectedPath(path);
if (filename.isEmpty()) m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
if (path.isEmpty())
return; return;
QFile key(filename); QFile file(path);
if (!key.open(QIODevice::ReadOnly)) if (!file.open(QIODevice::ReadOnly)) {
if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid path"), file.errorString());
return; return;
}
bool success = setSslKey(key.read(1024 * 1024)); if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE))) {
if (!success) if (showError == ShowError::Show)
QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key.")); QMessageBox::warning(this, tr("Invalid key"), tr("This is not a valid SSL key."));
return;
}
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
}
void OptionsDialog::showConnectionTab()
{
m_ui->tabSelection->setCurrentRow(TAB_CONNECTION);
} }
void OptionsDialog::on_registerDNSBtn_clicked() void OptionsDialog::on_registerDNSBtn_clicked()
@ -1698,45 +1714,6 @@ QString OptionsDialog::languageToLocalizedString(const QLocale &locale)
} }
} }
bool OptionsDialog::setSslKey(const QByteArray &key)
{
#ifndef QT_NO_OPENSSL
// try different formats
const bool isKeyValid = (!QSslKey(key, QSsl::Rsa).isNull() || !QSslKey(key, QSsl::Ec).isNull());
if (isKeyValid) {
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
m_sslKey = key;
}
else {
m_ui->lblSslKeyStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
m_sslKey.clear();
}
return isKeyValid;
#else
Q_UNUSED(key);
return false;
#endif
}
bool OptionsDialog::setSslCertificate(const QByteArray &cert)
{
#ifndef QT_NO_OPENSSL
const bool isCertValid = !QSslCertificate(cert).isNull();
if (isCertValid) {
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-high.svg", this, 24));
m_sslCert = cert;
}
else {
m_ui->lblSslCertStatus->setPixmap(Utils::Gui::scaledPixmapSvg(":/icons/qbt-theme/security-low.svg", this, 24));
m_sslCert.clear();
}
return isCertValid;
#else
Q_UNUSED(cert);
return false;
#endif
}
bool OptionsDialog::schedTimesOk() bool OptionsDialog::schedTimesOk()
{ {
if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time()) { if (m_ui->timeEditScheduleFrom->time() == m_ui->timeEditScheduleTo->time()) {

18
src/gui/optionsdialog.h

@ -60,7 +60,6 @@ class OptionsDialog : public QDialog
Q_OBJECT Q_OBJECT
using ThisType = OptionsDialog; using ThisType = OptionsDialog;
private:
enum Tabs enum Tabs
{ {
TAB_UI, TAB_UI,
@ -73,6 +72,12 @@ private:
TAB_ADVANCED TAB_ADVANCED
}; };
enum class ShowError
{
NotShow,
Show
};
public: public:
// Constructor / Destructor // Constructor / Destructor
OptionsDialog(QWidget *parent = nullptr); OptionsDialog(QWidget *parent = nullptr);
@ -102,10 +107,10 @@ private slots:
void on_randomButton_clicked(); void on_randomButton_clicked();
void on_addScanFolderButton_clicked(); void on_addScanFolderButton_clicked();
void on_removeScanFolderButton_clicked(); void on_removeScanFolderButton_clicked();
void on_btnWebUiCrt_clicked();
void on_btnWebUiKey_clicked();
void on_registerDNSBtn_clicked(); void on_registerDNSBtn_clicked();
void setLocale(const QString &localeStr); void setLocale(const QString &localeStr);
void webUIHttpsCertChanged(const QString &path, ShowError showError);
void webUIHttpsKeyChanged(const QString &path, ShowError showError);
private: private:
// Methods // Methods
@ -164,17 +169,14 @@ private:
int getMaxActiveDownloads() const; int getMaxActiveDownloads() const;
int getMaxActiveUploads() const; int getMaxActiveUploads() const;
int getMaxActiveTorrents() const; int getMaxActiveTorrents() const;
// WebUI
bool isWebUiEnabled() const; bool isWebUiEnabled() const;
QString webUiUsername() const; QString webUiUsername() const;
QString webUiPassword() const; QString webUiPassword() const;
// WebUI SSL Cert / key
bool setSslKey(const QByteArray &key);
bool setSslCertificate(const QByteArray &cert);
bool schedTimesOk();
bool webUIAuthenticationOk(); bool webUIAuthenticationOk();
bool isAlternativeWebUIPathValid(); bool isAlternativeWebUIPathValid();
QByteArray m_sslCert, m_sslKey; bool schedTimesOk();
Ui::OptionsDialog *m_ui; Ui::OptionsDialog *m_ui;
QAbstractButton *m_applyButton; QAbstractButton *m_applyButton;

76
src/gui/optionsdialog.ui

@ -2966,79 +2966,25 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout_11"> <layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="0">
<widget class="QLabel" name="lblSslCertStatus"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblWebUiCrt">
<property name="text">
<string>Certificate:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="btnWebUiCrt">
<property name="text">
<string>Import SSL Certificate</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_12">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>138</width>
<height>28</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lblSslKeyStatus"/>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLabel" name="lblWebUiKey"> <widget class="QLabel" name="lblWebUiKey">
<property name="text"> <property name="text">
<string>Key:</string> <string>Key:</string>
</property> </property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5"> <widget class="QLabel" name="lblWebUiCrt">
<item>
<widget class="QPushButton" name="btnWebUiKey">
<property name="text"> <property name="text">
<string>Import SSL Key</string> <string>Certificate:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="0">
<spacer name="horizontalSpacer_13"> <widget class="QLabel" name="lblSslCertStatus"/>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item> </item>
</layout> <item row="1" column="0">
<widget class="QLabel" name="lblSslKeyStatus"/>
</item> </item>
<item row="2" column="0" colspan="3"> <item row="2" column="0" colspan="3">
<widget class="QLabel" name="lblWebUIInfo"> <widget class="QLabel" name="lblWebUIInfo">
@ -3050,6 +2996,12 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2">
<widget class="FileSystemPathLineEdit" name="textWebUIHttpsCert" native="true"/>
</item>
<item row="1" column="2">
<widget class="FileSystemPathLineEdit" name="textWebUIHttpsKey" native="true"/>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -3464,8 +3416,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
<tabstop>comboRatioLimitAct</tabstop> <tabstop>comboRatioLimitAct</tabstop>
<tabstop>checkWebUIUPnP</tabstop> <tabstop>checkWebUIUPnP</tabstop>
<tabstop>checkWebUiHttps</tabstop> <tabstop>checkWebUiHttps</tabstop>
<tabstop>btnWebUiCrt</tabstop>
<tabstop>btnWebUiKey</tabstop>
<tabstop>checkBypassLocalAuth</tabstop> <tabstop>checkBypassLocalAuth</tabstop>
<tabstop>checkBypassAuthSubnetWhitelist</tabstop> <tabstop>checkBypassAuthSubnetWhitelist</tabstop>
<tabstop>IPSubnetWhitelistButton</tabstop> <tabstop>IPSubnetWhitelistButton</tabstop>

25
src/webui/api/appcontroller.cpp

@ -39,11 +39,6 @@
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#ifndef QT_NO_OPENSSL
#include <QSslCertificate>
#include <QSslKey>
#endif
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "base/global.h" #include "base/global.h"
#include "base/net/portforwarder.h" #include "base/net/portforwarder.h"
@ -210,8 +205,8 @@ void AppController::preferencesAction()
data["web_ui_port"] = pref->getWebUiPort(); data["web_ui_port"] = pref->getWebUiPort();
data["web_ui_upnp"] = pref->useUPnPForWebUIPort(); data["web_ui_upnp"] = pref->useUPnPForWebUIPort();
data["use_https"] = pref->isWebUiHttpsEnabled(); data["use_https"] = pref->isWebUiHttpsEnabled();
data["ssl_key"] = QString::fromLatin1(pref->getWebUiHttpsKey()); data["web_ui_https_cert_path"] = pref->getWebUIHttpsCertificatePath();
data["ssl_cert"] = QString::fromLatin1(pref->getWebUiHttpsCertificate()); data["web_ui_https_key_path"] = pref->getWebUIHttpsKeyPath();
// Authentication // Authentication
data["web_ui_username"] = pref->getWebUiUsername(); data["web_ui_username"] = pref->getWebUiUsername();
data["bypass_local_auth"] = !pref->isWebUiLocalAuthEnabled(); data["bypass_local_auth"] = !pref->isWebUiLocalAuthEnabled();
@ -505,18 +500,10 @@ void AppController::setPreferencesAction()
pref->setUPnPForWebUIPort(m["web_ui_upnp"].toBool()); pref->setUPnPForWebUIPort(m["web_ui_upnp"].toBool());
if (m.contains("use_https")) if (m.contains("use_https"))
pref->setWebUiHttpsEnabled(m["use_https"].toBool()); pref->setWebUiHttpsEnabled(m["use_https"].toBool());
#ifndef QT_NO_OPENSSL if ((it = m.find(QLatin1String("web_ui_https_cert_path"))) != m.constEnd())
if (m.contains("ssl_key")) { pref->setWebUIHttpsCertificatePath(it.value().toString());
QByteArray raw_key = m["ssl_key"].toString().toLatin1(); if ((it = m.find(QLatin1String("web_ui_https_key_path"))) != m.constEnd())
if (!QSslKey(raw_key, QSsl::Rsa).isNull()) pref->setWebUIHttpsKeyPath(it.value().toString());
pref->setWebUiHttpsKey(raw_key);
}
if (m.contains("ssl_cert")) {
QByteArray raw_cert = m["ssl_cert"].toString().toLatin1();
if (!QSslCertificate(raw_cert).isNull())
pref->setWebUiHttpsCertificate(raw_cert);
}
#endif
// Authentication // Authentication
if (m.contains("web_ui_username")) if (m.contains("web_ui_username"))
pref->setWebUiUsername(m["web_ui_username"].toString()); pref->setWebUiUsername(m["web_ui_username"].toString());

19
src/webui/webui.cpp

@ -28,11 +28,14 @@
#include "webui.h" #include "webui.h"
#include <QFile>
#include "base/http/server.h" #include "base/http/server.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/net/dnsupdater.h" #include "base/net/dnsupdater.h"
#include "base/net/portforwarder.h" #include "base/net/portforwarder.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/utils/net.h"
#include "webapplication.h" #include "webapplication.h"
WebUI::WebUI() WebUI::WebUI()
@ -77,11 +80,18 @@ void WebUI::configure()
m_httpServer->close(); m_httpServer->close();
} }
#ifndef QT_NO_OPENSSL
if (pref->isWebUiHttpsEnabled()) { if (pref->isWebUiHttpsEnabled()) {
const QByteArray certs = pref->getWebUiHttpsCertificate(); const auto readData = [](const QString &path) -> QByteArray
const QByteArray key = pref->getWebUiHttpsKey(); {
bool success = m_httpServer->setupHttps(certs, key); QFile file(path);
if (!file.open(QIODevice::ReadOnly))
return {};
return file.read(Utils::Net::MAX_SSL_FILE_SIZE);
};
const QByteArray cert = readData(pref->getWebUIHttpsCertificatePath());
const QByteArray key = readData(pref->getWebUIHttpsKeyPath());
const bool success = m_httpServer->setupHttps(cert, key);
if (success) if (success)
logger->addMessage(tr("Web UI: HTTPS setup successful")); logger->addMessage(tr("Web UI: HTTPS setup successful"));
else else
@ -90,7 +100,6 @@ void WebUI::configure()
else { else {
m_httpServer->disableHttps(); m_httpServer->disableHttps();
} }
#endif
if (!m_httpServer->isListening()) { if (!m_httpServer->isListening()) {
const auto address = (serverAddressString == "*" || serverAddressString.isEmpty()) const auto address = (serverAddressString == "*" || serverAddressString.isEmpty())

20
src/webui/www/private/preferences_content.html

@ -682,18 +682,18 @@
<table> <table>
<tr> <tr>
<td> <td>
<label for="ssl_key_textarea">QBT_TR(Key:)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="ssl_cert_text">QBT_TR(Certificate:)QBT_TR[CONTEXT=OptionsDialog]</label>
</td> </td>
<td> <td>
<textarea id="ssl_key_textarea" rows="5" cols="70"></textarea> <input type="text" id="ssl_cert_text" style="width: 30em;" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<label for="ssl_cert_textarea">QBT_TR(Certificate:)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="ssl_key_text">QBT_TR(Key:)QBT_TR[CONTEXT=OptionsDialog]</label>
</td> </td>
<td> <td>
<textarea id="ssl_cert_textarea" rows="5" cols="70"></textarea> <input type="text" id="ssl_key_text" style="width: 30em;" />
</td> </td>
</tr> </tr>
</table> </table>
@ -1043,8 +1043,8 @@
// Web UI tab // Web UI tab
var updateHttpsSettings = function() { var updateHttpsSettings = function() {
var isUseHttpsEnabled = $('use_https_checkbox').getProperty('checked'); var isUseHttpsEnabled = $('use_https_checkbox').getProperty('checked');
$('ssl_key_textarea').setProperty('disabled', !isUseHttpsEnabled); $('ssl_cert_text').setProperty('disabled', !isUseHttpsEnabled);
$('ssl_cert_textarea').setProperty('disabled', !isUseHttpsEnabled); $('ssl_key_text').setProperty('disabled', !isUseHttpsEnabled);
}; };
var updateBypasssAuthSettings = function() { var updateBypasssAuthSettings = function() {
@ -1330,8 +1330,8 @@
$('webui_port_value').setProperty('value', pref.web_ui_port); $('webui_port_value').setProperty('value', pref.web_ui_port);
$('webui_upnp_checkbox').setProperty('checked', pref.web_ui_upnp); $('webui_upnp_checkbox').setProperty('checked', pref.web_ui_upnp);
$('use_https_checkbox').setProperty('checked', pref.use_https); $('use_https_checkbox').setProperty('checked', pref.use_https);
$('ssl_key_textarea').setProperty('value', pref.ssl_key); $('ssl_cert_text').setProperty('value', pref.web_ui_https_cert_path);
$('ssl_cert_textarea').setProperty('value', pref.ssl_cert); $('ssl_key_text').setProperty('value', pref.web_ui_https_key_path);
updateHttpsSettings(); updateHttpsSettings();
// Authentication // Authentication
@ -1646,8 +1646,8 @@
settings.set('web_ui_port', web_ui_port); settings.set('web_ui_port', web_ui_port);
settings.set('web_ui_upnp', $('webui_upnp_checkbox').getProperty('checked')); settings.set('web_ui_upnp', $('webui_upnp_checkbox').getProperty('checked'));
settings.set('use_https', $('use_https_checkbox').getProperty('checked')); settings.set('use_https', $('use_https_checkbox').getProperty('checked'));
settings.set('ssl_key', $('ssl_key_textarea').getProperty('value')); settings.set('web_ui_https_cert_path', $('ssl_cert_text').getProperty('value'));
settings.set('ssl_cert', $('ssl_cert_textarea').getProperty('value')); settings.set('web_ui_https_key_path', $('ssl_key_text').getProperty('value'));
// Authentication // Authentication
var web_ui_username = $('webui_username_text').getProperty('value'); var web_ui_username = $('webui_username_text').getProperty('value');

Loading…
Cancel
Save