Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions assets/webconfig/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,14 @@
"edt_conf_webc_heading_title": "Web Konfiguration",
"edt_conf_webc_docroot_title": "Verzeichnis",
"edt_conf_webc_docroot_expl": "Lokaler Pfad zum WebUI Wurzelverzeichnis (Nur für WebUI Entwickler)",
"edt_conf_webc_sslport_title" : "HTTPS Port",
"edt_conf_webc_sslport_expl" : "Port des HTTPS webservers",
"edt_conf_webc_crtPath_title" : "Zertifikats-Pfad",
"edt_conf_webc_crtPath_expl" : "Pfad zur Zertifikats-Datei (Format sollte PEM sein)",
"edt_conf_webc_keyPath_title" : "Schlüssel-Pfad",
"edt_conf_webc_keyPath_expl" : "Pfad zum privaten Schlüssel (Format in PEM, verschlüsselt mit RSA)",
"edt_conf_webc_keyPassPhrase_title" : "Schlüsselpasswort",
"edt_conf_webc_keyPassPhrase_expl" : "Optional: Der Schlüssel könnte mit einem Passwort geschützt sein",
"edt_conf_effp_heading_title": "Effekt Pfade",
"edt_conf_effp_paths_title": "Effekt Pfad(e)",
"edt_conf_effp_paths_expl": "Es können mehrere Ordner definiert werden die Effekte enthalten. Der Effekt Konfigurator speichert immer im Ersten Ordner.",
Expand Down
8 changes: 8 additions & 0 deletions assets/webconfig/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,14 @@
"edt_conf_webc_heading_title" : "Web Configuration",
"edt_conf_webc_docroot_title" : "Document Root",
"edt_conf_webc_docroot_expl" : "Local webinterface root path (just for webui developer)",
"edt_conf_webc_sslport_title" : "HTTPS Port",
"edt_conf_webc_sslport_expl" : "Port oft the HTTPS webserver",
"edt_conf_webc_crtPath_title" : "Certificate path",
"edt_conf_webc_crtPath_expl" : "Path to the certification file (format should be PEM)",
"edt_conf_webc_keyPath_title" : "Private key path",
"edt_conf_webc_keyPath_expl" : "Path to the key file (format PEM, encrypted with RSA)",
"edt_conf_webc_keyPassPhrase_title" : "Key password",
"edt_conf_webc_keyPassPhrase_expl" : "Optional: The key might be protected with a password",
"edt_conf_effp_heading_title" : "Effect Paths",
"edt_conf_effp_paths_title" : "Effect Path(s)",
"edt_conf_effp_paths_expl" : "You could define more folders that contain effects. The effect configurator will always save inside the first folder.",
Expand Down
2 changes: 1 addition & 1 deletion assets/webconfig/js/hyperion.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function initWebSocket()
if (window.websocket == null)
{
window.jsonPort = (document.location.port == '') ? '80' : document.location.port;
window.websocket = new WebSocket('ws://'+document.location.hostname+":"+window.jsonPort);
window.websocket = (document.location.protocol == "https:") ? new WebSocket('wss://'+document.location.hostname+":"+window.jsonPort) : new WebSocket('ws://'+document.location.hostname+":"+window.jsonPort);

window.websocket.onopen = function (event) {
$(window.hyperion).trigger({type:"open"});
Expand Down
10 changes: 9 additions & 1 deletion config/hyperion.config.json.commented
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,18 @@
/// Configuration of the Hyperion webserver
/// * document_root : path to hyperion webapp files (webconfig developer only)
/// * port : the port where hyperion webapp is accasible
/// * sslPort : the secure (HTTPS) port of the hyperion webapp
/// * crtPath : the path to a certificate file to allow HTTPS connections. Should be in PEM format
/// * keyPath : the path to a private key file to allow HTTPS connections. Should be in PEM format and RSA encrypted
/// * keyPassPhrase : optional: If the key file requires a password add it here
"webConfig" :
{
"document_root" : "/path/to/files",
"port" : 8090
"port" : 8090,
"sslPort" : 8092,
"crtPath" : "/path/to/mycert.crt",
"keyPath" : "/path/to/mykey.key",
"keyPassPhrase" : ""
},

/// The configuration of the effect engine, contains the following items:
Expand Down
6 changes: 5 additions & 1 deletion config/hyperion.config.json.default
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@
"webConfig" :
{
"document_root" : "",
"port" : 8090
"port" : 8090,
"sslPort" : 8092,
"crtPath" : "",
"keyPath" : "",
"keyPassPhrase" : ""
},

"effects" :
Expand Down
22 changes: 19 additions & 3 deletions include/webserver/WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ class BonjourServiceRegister;
class StaticFileServing;
class QtHttpServer;

/*
OPENSSL command that generated the embedded key and cert file

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout hyperion.key -out hyperion.crt -extensions san -config \
<(echo "[req]";
echo distinguished_name=req;
echo "[san]";
echo subjectAltName=DNS:hyperion-project.org,IP:127.0.0.1
) \
-subj /CN=hyperion-project.org
*/

class WebServer : public QObject {
Q_OBJECT

public:
WebServer (const QJsonDocument& config, QObject * parent = 0);
WebServer (const QJsonDocument& config, const bool& useSsl, QObject * parent = 0);

virtual ~WebServer (void);

Expand Down Expand Up @@ -67,15 +80,18 @@ public slots:

private:
QJsonDocument _config;
bool _useSsl;
Logger* _log;
QString _baseUrl;
quint16 _port;
StaticFileServing* _staticFileServing;
QtHttpServer* _server;
bool _inited = false;

const QString WEBSERVER_DEFAULT_PATH = ":/webconfig";
const quint16 WEBSERVER_DEFAULT_PORT = 8090;
const QString WEBSERVER_DEFAULT_PATH = ":/webconfig";
const QString WEBSERVER_DEFAULT_CRT_PATH = ":/hyperion.crt";
const QString WEBSERVER_DEFAULT_KEY_PATH = ":/hyperion.key";
quint16 WEBSERVER_DEFAULT_PORT = 8090;

BonjourServiceRegister * _serviceRegister = nullptr;
};
Expand Down
35 changes: 34 additions & 1 deletion libsrc/hyperion/schema/schema-webConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,41 @@
"minimum" : 80,
"maximum" : 65535,
"default" : 8090,
"access" : "expert",
"propertyOrder" : 3
},
"sslPort" :
{
"type" : "integer",
"title" : "edt_conf_webc_sslport_title",
"required" : true,
"minimum" : 80,
"maximum" : 65535,
"default" : 8092,
"propertyOrder" : 4
},
"crtPath" :
{
"type" : "string",
"title" : "edt_conf_webc_crtPath_title",
"required" : true,
"default" : "",
"propertyOrder" : 5
},
"keyPath" :
{
"type" : "string",
"title" : "edt_conf_webc_keyPath_title",
"required" : true,
"default" : "",
"propertyOrder" : 6
},
"keyPassPhrase" :
{
"type" : "string",
"title" : "edt_conf_webc_keyPassPhrase_title",
"required" : true,
"default" : "",
"propertyOrder" : 7
}
},
"additionalProperties" : false
Expand Down
2 changes: 2 additions & 0 deletions libsrc/webserver/QtHttpServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public slots:
void setServerName (const QString & serverName) { m_serverName = serverName; };
void setPrivateKey (const QSslKey & key) { m_sslKey = key; };
void setCertificates (const QList<QSslCertificate> & certs) { m_sslCerts = certs; };
QSslKey getPrivateKey() { return m_sslKey; };
QList<QSslCertificate> getCertificates() { return m_sslCerts; };

signals:
void started (quint16 port);
Expand Down
87 changes: 84 additions & 3 deletions libsrc/webserver/WebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
#include <utils/NetUtils.h>


WebServer::WebServer(const QJsonDocument& config, QObject * parent)
WebServer::WebServer(const QJsonDocument& config, const bool& useSsl, QObject * parent)
: QObject(parent)
, _config(config)
, _useSsl(useSsl)
, _log(Logger::getInstance("WEBSERVER"))
, _server()
{
Expand All @@ -31,6 +32,12 @@ void WebServer::initServer()
_server = new QtHttpServer (this);
_server->setServerName (QStringLiteral ("Hyperion Webserver"));

if(_useSsl)
{
_server->setUseSecure();
WEBSERVER_DEFAULT_PORT = 8092;
}

connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted);
connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped);
connect (_server, &QtHttpServer::error, this, &WebServer::onServerError);
Expand Down Expand Up @@ -98,16 +105,90 @@ void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocu
Debug(_log, "Set document root to: %s", _baseUrl.toUtf8().constData());
_staticFileServing->setBaseUrl(_baseUrl);

if(_port != obj["port"].toInt(WEBSERVER_DEFAULT_PORT))
// ssl different port
quint16 newPort = _useSsl ? obj["sslPort"].toInt(WEBSERVER_DEFAULT_PORT) : obj["port"].toInt(WEBSERVER_DEFAULT_PORT);
if(_port != newPort)
{
_port = obj["port"].toInt(WEBSERVER_DEFAULT_PORT);
_port = newPort;
stop();
}

// eval if the port is available, will be incremented if not
if(!_server->isListening())
NetUtils::portAvailable(_port, _log);

// on ssl we want .key .cert and probably key password
if(_useSsl)
{
QString keyPath = obj["keyPath"].toString(WEBSERVER_DEFAULT_KEY_PATH);
QString crtPath = obj["crtPath"].toString(WEBSERVER_DEFAULT_CRT_PATH);

QSslKey currKey = _server->getPrivateKey();
QList<QSslCertificate> currCerts = _server->getCertificates();

// check keyPath
if ( (keyPath != WEBSERVER_DEFAULT_KEY_PATH) && !keyPath.trimmed().isEmpty())
{
QFileInfo kinfo(keyPath);
if (!kinfo.exists())
{
Error(_log, "No SSL key found at '%s' falling back to internal", keyPath.toUtf8().constData());
keyPath = WEBSERVER_DEFAULT_KEY_PATH;
}
}
else
keyPath = WEBSERVER_DEFAULT_KEY_PATH;

// check crtPath
if ( (crtPath != WEBSERVER_DEFAULT_CRT_PATH) && !crtPath.trimmed().isEmpty())
{
QFileInfo cinfo(crtPath);
if (!cinfo.exists())
{
Error(_log, "No SSL certificate found at '%s' falling back to internal", crtPath.toUtf8().constData());
crtPath = WEBSERVER_DEFAULT_CRT_PATH;
}
}
else
crtPath = WEBSERVER_DEFAULT_CRT_PATH;

// load and verify crt
QFile cfile(crtPath);
cfile.open(QIODevice::ReadOnly);
QList<QSslCertificate> validList;
QList<QSslCertificate> cList = QSslCertificate::fromDevice(&cfile, QSsl::Pem);
cfile.close();

// Filter for valid certs
for(const auto & entry : cList){
if(!entry.isNull() && QDateTime::currentDateTime().daysTo(entry.expiryDate()) > 0)
validList.append(entry);
else
Error(_log, "The provided SSL certificate is invalid/not supported/reached expiry date ('%s')", crtPath.toUtf8().constData());
}

if(!validList.isEmpty()){
Debug(_log,"Setup SSL certificate");
_server->setCertificates(validList);
} else {
Error(_log, "No valid SSL certificate has been found ('%s')", crtPath.toUtf8().constData());
}

// load and verify key
QFile kfile(keyPath);
kfile.open(QIODevice::ReadOnly);
// The key should be RSA enrcrypted and PEM format, optional the passPhrase
QSslKey key(&kfile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, obj["keyPassPhrase"].toString().toUtf8());
kfile.close();

if(key.isNull()){
Error(_log, "The provided SSL key is invalid or not supported use RSA encrypt and PEM format ('%s')", keyPath.toUtf8().constData());
} else {
Debug(_log,"Setup private SSL key");
_server->setPrivateKey(key);
}
}

start();
emit portChanged(_port);
}
Expand Down
2 changes: 1 addition & 1 deletion resources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/resources)

# catch all files
FILE ( GLOB Hyperion_RESFILES "${CURRENT_SOURCE_DIR}/icons/*" )
FILE ( GLOB Hyperion_RESFILES "${CURRENT_SOURCE_DIR}/icons/*" "${CURRENT_SOURCE_DIR}/ssl/*" )

# fill resources.qrc with RESFILES
FOREACH( f ${Hyperion_RESFILES} )
Expand Down
29 changes: 29 additions & 0 deletions resources/ssl/hyperion.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE6jCCAtKgAwIBAgIJAJlO0qRW51TjMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV
BAMMFGh5cGVyaW9uLXByb2plY3Qub3JnMB4XDTE5MDgyMDEyMjE1N1oXDTI5MDgx
NzEyMjE1N1owHzEdMBsGA1UEAwwUaHlwZXJpb24tcHJvamVjdC5vcmcwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbBa9e1Ez9qK4/qxcAJdadEIfgyaxR
O6q8UjAkklPhd7Vnql78omni9pukS24+sndla7Snyx06ajCrkJR4VM14uKMCecf+
fBTFNFp1oQJauh16Gh/rVlcN28HIW/BQLEsvC3QXxJhmRkSC5+oPO3NA7h2gYCVV
Pvg8kzoyBIF/1uUlMB7HXS952Vbswr9tCxo0qiqgf57l/bbUliSHrBgE0HnXGl8n
N7ZiQAtSa644gwvPXhg4PZjjLHDlM69RChsoE9osDCh0lLpML0pfMaH9xZd2Sehe
wV8Jd3yDnwZ7IGli3YtA+n1zqdFvYsePCdu7HxRfuLSJf1yoWJv8Eg+fhafT+9KU
uZFaPn9BmDbB6Q6g2NmNVzWWjpBeKHn8jhoYFxfmpqcikbjMlljv1lkJIC7ZIwxq
ei6d7wB/5GK7r7KcwTuHTYaV8uj6sG/qGP2KazE9bJWxCF34w0hvPcwRiPbA4IQH
7OfUYCZgi8pgVJjtuhRF0tF/JOPKyhShJ8+JENbw2QOOh869e7FF/rvHgZfwRjRq
V9EOWTclQyEWSN3SKXP2S9Om8zvIPdn5r7n3DYibJNDegHaGfDYzD0Ruh0H9eOwE
qiR6QHawQ7r5cDalz1YLcL21vd1GdtMJMSoxpsvjgJQV6sB2ZWL1BsAJ5lmJ2Mp3
NuPEb3r0DQZ+3wIDAQABoykwJzAlBgNVHREEHjAcghRoeXBlcmlvbi1wcm9qZWN0
Lm9yZ4cEfwAAATANBgkqhkiG9w0BAQsFAAOCAgEAgrgWwGW64JE7UbuYJP3fs2XF
wkhVY+1UTPpYrkk6ZQ//XqPKnJpLz51lLxPBaO04YvYh5RrLZ+cz1XWsLaHXZtRW
GEpOjGYsZsb5jaT4WS8hC+7PHmfbRWKGnWnDx93wG3ipBJ6/jIh01Dyb1V1YEo/X
F1bZrpLH6iN1c0aIcKIu2ewzxHEulZDNSaN8Wpr8mK+Hde3JBIFhS36y+Y8iXVbd
V4HegBm5XHwAkbkNPjpXsyJ0RPLAtv7Ba6gvJzii/u13Zrn7731nYACN2zMO/90o
U4TvW+u9NC8kzKs8iex7gBfSXKr724znMcQ+2Go1sY6QjRFHas4lJ7BeU6CA8ZsV
CKSgvDay7KX/DvhR4J0WhxCAZ+eX0HIyMZ50mm8W/BaDvTTqghT0+iWV7nxAuZ2p
kdkVd+giTT5kyNObA6WIn4qJdxcxoiwB1qvPmEDbGZ4ZxySfIJ44mNBZu0f9VxaC
5kfbnkVwuvjnvbyERwpaEs7q6+Kkz0uEyglLvmcLzaDn+8zmEEV4pVp5I1H7Cmji
pYA2dqwupTFlXGjDtaNG/mZKp2uD4Rmt2vq5vQDRd/zfPRIvu2ZBWHEHkXuH9Qfa
+IcrvtZxCkF9m39f0jHQ8bXOyVAzpLrWWia6fDP42F82vce4Q69ejZmdhyeMZteI
mMgFOwY2tPspNj8848g=
-----END CERTIFICATE-----
52 changes: 52 additions & 0 deletions resources/ssl/hyperion.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
d8b5Ntiu2G/qZODXNmFh9x3/AAnkhqk=
-----END PRIVATE KEY-----
Loading