add HTTPS support for webserver

This commit is contained in:
Simon Stürz 2015-08-02 22:34:04 +02:00 committed by Michael Zanetti
parent 36fbf27762
commit 660f91b165
10 changed files with 178 additions and 63 deletions

View File

@ -1,13 +1,14 @@
[JSONRPC] [JSONRPC]
port=1234 port=1234
interfaces="lo","all" interfaces="lo", "all"
ip="IPv4", "IPv6" ip="IPv4", "IPv6"
[Webserver] [Webserver]
port=3000 port=3000
publicFolder=/usr/share/guh-webinterface/
https=false https=false
certificate=/etc/ssl/certs/guhd-certificate.crt
certificate-key=/etc/ssl/private/guhd-certificate.key
publicFolder=/usr/share/guh-webinterface/public
[GPIO] [GPIO]
rf433rx=27 rf433rx=27

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
guh (0.5.0) vivid; urgency=medium
* Add webserver and REST API
-- Simon Stürz <simon.stuerz@guh.guru> Sat, 01 Aug 2015 14:52:12 +0200
guh (0.4.0) vivid; urgency=medium guh (0.4.0) vivid; urgency=medium
* add EditDevice and notifications * add EditDevice and notifications

View File

@ -204,7 +204,7 @@ HttpRequest::RequestMethod HttpRequest::getRequestMethodType(const QString &meth
QDebug operator<<(QDebug debug, const HttpRequest &httpRequest) QDebug operator<<(QDebug debug, const HttpRequest &httpRequest)
{ {
debug << "\n===================================" << "\n"; debug << "===================================" << "\n";
debug << " HTTP version: " << httpRequest.httpVersion() << "\n"; debug << " HTTP version: " << httpRequest.httpVersion() << "\n";
debug << " method: " << httpRequest.methodString() << "\n"; debug << " method: " << httpRequest.methodString() << "\n";
debug << " URL path: " << httpRequest.url().path() << "\n"; debug << " URL path: " << httpRequest.url().path() << "\n";

View File

@ -209,7 +209,7 @@ void TcpServer::onClientDisconnected()
m_clientList.take(clientId)->deleteLater(); m_clientList.take(clientId)->deleteLater();
} }
void TcpServer::onError(const QAbstractSocket::SocketError &error) void TcpServer::onError(QAbstractSocket::SocketError error)
{ {
QTcpServer *server = qobject_cast<QTcpServer *>(sender()); QTcpServer *server = qobject_cast<QTcpServer *>(sender());
QUuid uuid = m_serverList.key(server); QUuid uuid = m_serverList.key(server);

View File

@ -63,7 +63,7 @@ private slots:
void onClientConnected(); void onClientConnected();
void onClientDisconnected(); void onClientDisconnected();
void readPackage(); void readPackage();
void onError(const QAbstractSocket::SocketError &error); void onError(QAbstractSocket::SocketError error);
void onTimeout(); void onTimeout();
public slots: public slots:

View File

@ -21,7 +21,7 @@
#ifndef TRANSPORTINTERFACE_H #ifndef TRANSPORTINTERFACE_H
#define TRANSPORTINTERFACE_H #define TRANSPORTINTERFACE_H
#include <QObject> #include <QVariant>
#include <QString> #include <QString>
#include <QList> #include <QList>
#include <QUuid> #include <QUuid>

View File

@ -27,6 +27,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>
#include <QSslSocket>
#include <QUrlQuery> #include <QUrlQuery>
#include <QUuid> #include <QUuid>
#include <QUrl> #include <QUrl>
@ -35,36 +36,49 @@
namespace guhserver { namespace guhserver {
WebServer::WebServer(QObject *parent) : WebServer::WebServer(QObject *parent) :
TransportInterface(parent), QTcpServer(parent),
m_enabled(false) m_enabled(false),
m_useSsl(false)
{ {
// load webserver settings // load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal); GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcTcpServer) << "Loading webserver settings from:" << settings.fileName(); qCDebug(dcWebServer) << "Loading webserver settings from:" << settings.fileName();
settings.beginGroup("Webserver"); settings.beginGroup("Webserver");
m_port = settings.value("port", 3000).toInt(); m_port = settings.value("port", 3000).toInt();
m_useSsl = settings.value("https", false).toBool();
m_webinterfaceDir = QDir(settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString()); m_webinterfaceDir = QDir(settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString());
QString certificateFileName = settings.value("certificate", QVariant("/etc/ssl/certs/guhd-certificate.crt")).toString();
QString keyFileName = settings.value("certificate-key", QVariant("/etc/ssl/private/guhd-certificate.key")).toString();
settings.endGroup(); settings.endGroup();
qCDebug(dcTcpServer) << "Publish webinterface from" << m_webinterfaceDir.path(); // check public directory
qCDebug(dcWebServer) << "Publish webinterface folder" << m_webinterfaceDir.path();
if (!m_webinterfaceDir.exists()) if (!m_webinterfaceDir.exists())
qCWarning(dcWebServer) << "Web interface path" << m_webinterfaceDir.path() << "does not exist."; qCWarning(dcWebServer) << "Web interface public folder" << m_webinterfaceDir.path() << "does not exist.";
// create webserver // check SSL
m_server = new QTcpServer(this); if (m_useSsl && !QSslSocket::supportsSsl()) {
connect(m_server, &QTcpServer::newConnection, this, &WebServer::onNewConnection); qCWarning(dcWebServer) << "SSL is not supported/installed on this platform.";
m_useSsl = false;
}
if (m_useSsl && !loadCertificate(keyFileName, certificateFileName)) {
qCWarning(dcWebServer) << "SSL encryption disabled";
m_useSsl = false;
return;
}
qCDebug(dcWebServer) << "Using SSL lib version:" << QSslSocket::sslLibraryVersionString();
} }
WebServer::~WebServer() WebServer::~WebServer()
{ {
m_server->close(); this->close();
} }
void WebServer::sendData(const QUuid &clientId, const QVariantMap &data) void WebServer::sendData(const QUuid &clientId, const QVariantMap &data)
{ {
QTcpSocket *socket = m_clientList.value(clientId); QSslSocket *socket = m_clientList.value(clientId);
HttpReply reply(HttpReply::Ok); HttpReply reply(HttpReply::Ok);
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";"); reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
reply.setPayload(QJsonDocument::fromVariant(data).toJson()); reply.setPayload(QJsonDocument::fromVariant(data).toJson());
@ -75,7 +89,7 @@ void WebServer::sendData(const QUuid &clientId, const QVariantMap &data)
void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data) void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{ {
foreach (const QUuid &client, clients) { foreach (const QUuid &client, clients) {
QTcpSocket *socket = m_clientList.value(client); QSslSocket *socket = m_clientList.value(client);
HttpReply reply(HttpReply::Ok); HttpReply reply(HttpReply::Ok);
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";"); reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
reply.setPayload(QJsonDocument::fromVariant(data).toJson()); reply.setPayload(QJsonDocument::fromVariant(data).toJson());
@ -86,7 +100,7 @@ void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
void WebServer::sendHttpReply(HttpReply *reply) void WebServer::sendHttpReply(HttpReply *reply)
{ {
QTcpSocket *socket = 0; QSslSocket *socket = 0;
socket = m_clientList.value(reply->clientId()); socket = m_clientList.value(reply->clientId());
if (!socket) { if (!socket) {
@ -96,7 +110,7 @@ void WebServer::sendHttpReply(HttpReply *reply)
writeData(socket, reply->data()); writeData(socket, reply->data());
} }
bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName) bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName)
{ {
QFileInfo file(fileName); QFileInfo file(fileName);
@ -146,40 +160,83 @@ QString WebServer::fileName(const QString &query)
return m_webinterfaceDir.path() + fileName; return m_webinterfaceDir.path() + fileName;
} }
void WebServer::writeData(QTcpSocket *socket, const QByteArray &data) bool WebServer::loadCertificate(const QString &keyFileName, const QString &certificateFileName)
{ {
QTextStream os(socket); QByteArray certificateData;
os.setAutoDetectUnicode(true); QByteArray certificateKeyData;
os << data;
QFile certificateKeyFile(keyFileName);
if (!certificateKeyFile.open(QIODevice::ReadOnly)) {
qCWarning(dcWebServer) << "Could not open" << certificateKeyFile.fileName() << ":" << certificateKeyFile.errorString();
return false;
}
certificateKeyData = certificateKeyFile.readAll();
certificateKeyFile.close();
qCDebug(dcWebServer) << "Loaded successfully private certificate key.";
QFile certificateFile(certificateFileName);
if (!certificateFile.open(QIODevice::ReadOnly)) {
qCWarning(dcWebServer) << "Could not open" << certificateFile.fileName() << ":" << certificateFile.errorString();
return false;
}
certificateData = certificateFile.readAll();
certificateFile.close();
qCDebug(dcWebServer) << "Loaded successfully certificate file.";
m_certificate = QSslCertificate(certificateData);
m_certificateKey = QSslKey(certificateKeyData, QSsl::Rsa);
return true;
}
void WebServer::writeData(QSslSocket *socket, const QByteArray &data)
{
socket->write(data);
socket->close(); socket->close();
} }
void WebServer::onNewConnection() void WebServer::incomingConnection(qintptr socketDescriptor)
{ {
if (!m_enabled) if (!m_enabled)
return; return;
QTcpSocket* socket = m_server->nextPendingConnection(); QSslSocket *socket = new QSslSocket();
if (!socket->setSocketDescriptor(socketDescriptor)) {
qCWarning(dcConnection) << "Could not set socket descriptor. Rejecting connection.";
delete socket;
return;
}
// append the new client to the client list // append the new client to the client list
QUuid clientId = QUuid::createUuid(); QUuid clientId = QUuid::createUuid();
m_clientList.insert(clientId, socket); m_clientList.insert(clientId, socket);
connect(socket, &QTcpSocket::readyRead, this, &WebServer::readClient); qCDebug(dcConnection) << QString("Webserver client %1:%2 connected").arg(socket->peerAddress().toString()).arg(socket->peerPort());
connect(socket, &QTcpSocket::disconnected, this, &WebServer::onDisconnected);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
qCDebug(dcConnection) << "Webserver client connected" << socket->peerName() << socket->peerAddress().toString() << socket->peerPort(); if (m_useSsl) {
// configure client connection
socket->setProtocol(QSsl::TlsV1_2);
socket->setPrivateKey(m_certificateKey);
socket->setLocalCertificate(m_certificate);
connect(socket, SIGNAL(encrypted()), this, SLOT(onEncrypted()));
socket->startServerEncryption();
// wait for encrypted connection before continue
return;
}
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
emit clientConnected(clientId); emit clientConnected(clientId);
} }
void WebServer::readClient() void WebServer::readClient()
{ {
if (!m_enabled) if (!m_enabled)
return; return;
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender()); QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
QUuid clientId = m_clientList.key(socket); QUuid clientId = m_clientList.key(socket);
// check client // check client
@ -243,7 +300,7 @@ void WebServer::readClient()
} }
// request for a file... // request for a file...
if (request.method() == HttpRequest::Get) { if (request.method() == HttpRequest::Get && m_webinterfaceDir.exists()) {
QString path = fileName(request.url().path()); QString path = fileName(request.url().path());
if (!verifyFile(socket, path)) if (!verifyFile(socket, path))
return; return;
@ -264,13 +321,13 @@ void WebServer::readClient()
// reject everything else... // reject everything else...
qCWarning(dcWebServer) << "Unknown message received. Respond client with 501: Not Implemented."; qCWarning(dcWebServer) << "Unknown message received. Respond client with 501: Not Implemented.";
HttpReply reply(HttpReply::NotImplemented); HttpReply reply(HttpReply::NotImplemented);
reply.setPayload("501 Not implemented."); reply.setPayload("404 Not found.");
writeData(socket, reply.data()); writeData(socket, reply.data());
} }
void WebServer::onDisconnected() void WebServer::onDisconnected()
{ {
QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender()); QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCDebug(dcConnection) << "Webserver client disonnected."; qCDebug(dcConnection) << "Webserver client disonnected.";
// clean up // clean up
@ -282,28 +339,42 @@ void WebServer::onDisconnected()
emit clientDisconnected(clientId); emit clientDisconnected(clientId);
} }
void WebServer::onEncrypted()
{
QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCDebug(dcConnection) << QString("Encrypted connection %1:%2 successfully established.").arg(socket->peerAddress().toString()).arg(socket->peerPort());
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
emit clientConnected(m_clientList.key(socket));
}
void WebServer::onError(QAbstractSocket::SocketError error) void WebServer::onError(QAbstractSocket::SocketError error)
{ {
QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender()); QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCWarning(dcConnection) << "Client socket error" << socket->peerAddress() << error << socket->errorString(); qCWarning(dcConnection) << "Client socket error" << socket->peerAddress() << error << socket->errorString();
} }
bool WebServer::startServer() bool WebServer::startServer()
{ {
if (!m_server->listen(QHostAddress::Any, m_port)) { if (!listen(QHostAddress::Any, m_port)) {
qCWarning(dcConnection) << "Webserver could not listen on" << m_server->serverAddress().toString() << m_port; qCWarning(dcConnection) << "Webserver could not listen on" << serverAddress().toString() << m_port;
m_enabled = false; m_enabled = false;
return false; return false;
} }
qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port); if (m_useSsl) {
qCDebug(dcConnection) << "Started webserver on" << QString("https://%1:%2").arg(serverAddress().toString()).arg(m_port);
} else {
qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(serverAddress().toString()).arg(m_port);
}
m_enabled = true; m_enabled = true;
return true; return true;
} }
bool WebServer::stopServer() bool WebServer::stopServer()
{ {
m_server->close(); close();
m_enabled = false; m_enabled = false;
qCDebug(dcConnection) << "Webserver closed."; qCDebug(dcConnection) << "Webserver closed.";
return true; return true;

View File

@ -22,14 +22,14 @@
#define WEBSERVER_H #define WEBSERVER_H
#include <QObject> #include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHash> #include <QHash>
#include <QDir> #include <QDir>
#include <QUuid> #include <QSslSocket>
#include <QTcpSocket> #include <QSslCertificate>
#include <QSslKey>
#include "transportinterface.h"
class QTcpServer;
class HttpRequest; class HttpRequest;
class HttpReply; class HttpReply;
@ -38,47 +38,53 @@ class HttpReply;
namespace guhserver { namespace guhserver {
class WebServer : public TransportInterface class WebServer : public QTcpServer
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit WebServer(QObject *parent = 0); explicit WebServer(QObject *parent = 0);
~WebServer(); ~WebServer();
void sendData(const QUuid &clientId, const QVariantMap &data) override; void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override; void sendData(const QList<QUuid> &clients, const QVariantMap &data);
void sendHttpReply(HttpReply *reply); void sendHttpReply(HttpReply *reply);
private: private:
QTcpServer *m_server; QHash<QUuid, QSslSocket *> m_clientList;
QHash<QSslSocket *, HttpRequest> m_incompleteRequests;
QHash<QUuid, QTcpSocket *> m_clientList;
QHash<QTcpSocket *, HttpRequest> m_incompleteRequests;
bool m_enabled; bool m_enabled;
bool m_useSsl;
qint16 m_port; qint16 m_port;
QDir m_webinterfaceDir; QDir m_webinterfaceDir;
QSslCertificate m_certificate;
QSslKey m_certificateKey;
bool verifyFile(QTcpSocket *socket, const QString &fileName); bool verifyFile(QSslSocket *socket, const QString &fileName);
QString fileName(const QString &query); QString fileName(const QString &query);
void writeData(QTcpSocket *socket, const QByteArray &data); bool loadCertificate(const QString &keyFileName, const QString &certificateFileName);
void writeData(QSslSocket *socket, const QByteArray &data);
protected:
void incomingConnection(qintptr socketDescriptor) override;
signals: signals:
void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest); void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest);
void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId);
void dataAvailable(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message);
private slots: private slots:
void onNewConnection();
void readClient(); void readClient();
void onDisconnected(); void onDisconnected();
void onEncrypted();
void onError(QAbstractSocket::SocketError error); void onError(QAbstractSocket::SocketError error);
public slots: public slots:
bool startServer() override; bool startServer();
bool stopServer() override; bool stopServer();
}; };

View File

@ -25,7 +25,32 @@ namespace guhserver {
WebSocketServer::WebSocketServer(QObject *parent) : WebSocketServer::WebSocketServer(QObject *parent) :
TransportInterface(parent) TransportInterface(parent)
{ {
}
WebSocketServer::~WebSocketServer()
{
}
void WebSocketServer::sendData(const QUuid &clientId, const QVariantMap &data)
{
Q_UNUSED(clientId)
Q_UNUSED(data)
}
void WebSocketServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
Q_UNUSED(clients)
Q_UNUSED(data)
}
bool WebSocketServer::startServer()
{
return false;
}
bool WebSocketServer::stopServer()
{
return false;
} }
} }

View File

@ -22,6 +22,9 @@
#define WEBSOCKETSERVER_H #define WEBSOCKETSERVER_H
#include <QObject> #include <QObject>
#include <QUuid>
#include <QVariant>
#include <QList>
#include "transportinterface.h" #include "transportinterface.h"
@ -35,11 +38,14 @@ class WebSocketServer : public TransportInterface
Q_OBJECT Q_OBJECT
public: public:
explicit WebSocketServer(QObject *parent = 0); explicit WebSocketServer(QObject *parent = 0);
~WebSocketServer();
signals: void sendData(const QUuid &clientId, const QVariantMap &data) override;
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
public slots: public slots:
bool startServer() override;
bool stopServer() override;
}; };
} }