Feat: Add SSL support for webserver + websocket (#612)

* Feat: Add SSL support for webserver + websocket

Finally, Hyperion reaches the SSL century!
- Uses by default a internal key and certificate to provide working HTTPS out-of-the-box
- Your browser won't like that, for a green ssl seal next to the browser address bar you need to use Let's Encrypt with a own legit domain. This is out of the scope of Hyperion
This commit is contained in:
brindosch
2019-08-21 16:09:28 +02:00
committed by GitHub
parent fe12b36fce
commit 8e5f3251b5
14 changed files with 268 additions and 12 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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()
{
@@ -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);
@@ -98,9 +105,11 @@ 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();
}
@@ -108,6 +117,78 @@ void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocu
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);
}