diff --git a/libguh/guhsettings.cpp b/libguh/guhsettings.cpp index 1e72c29e..e523ff69 100644 --- a/libguh/guhsettings.cpp +++ b/libguh/guhsettings.cpp @@ -233,6 +233,24 @@ QString GuhSettings::translationsPath() #endif // SNAPPY } +QString GuhSettings::storagePath() +{ + QString path; +#ifdef SNAPPY + path = QString(qgetenv("SNAP_DATA")); +#else + QString organisationName = QCoreApplication::instance()->organizationName(); + if (organisationName == "guh-test") { + path = "/tmp/" + organisationName + "/"; + } else if (GuhSettings::isRoot()) { + path = "/var/lib/" + organisationName + "/"; + } else { + path = QDir::homePath() + "/.local/share/" + organisationName + "/"; + } +#endif + return path; +} + /*! Return a list of all settings keys.*/ QStringList GuhSettings::allKeys() const { diff --git a/libguh/guhsettings.h b/libguh/guhsettings.h index 8e078de4..ad4fadee 100644 --- a/libguh/guhsettings.h +++ b/libguh/guhsettings.h @@ -51,6 +51,7 @@ public: static QString logPath(); static QString settingsPath(); static QString translationsPath(); + static QString storagePath(); // forwarded QSettings methods QStringList allKeys() const; diff --git a/server/server.pro b/server/server.pro index eae1ef1e..aaad8c54 100644 --- a/server/server.pro +++ b/server/server.pro @@ -10,7 +10,7 @@ INSTALLS += target QT *= sql xml websockets bluetooth dbus -LIBS += -L$$top_builddir/libguh/ -lguh +LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto # Translations TRANSLATIONS *= $$top_srcdir/translations/guhd-en_US.ts \ diff --git a/server/servermanager.cpp b/server/servermanager.cpp index d09bfe57..b0fcecf7 100644 --- a/server/servermanager.cpp +++ b/server/servermanager.cpp @@ -33,7 +33,7 @@ */ #include "servermanager.h" -#include "guhsettings.h" +#include "guhcore.h" #include #include @@ -47,23 +47,37 @@ ServerManager::ServerManager(GuhConfiguration* configuration, QObject *parent) : m_sslConfiguration(QSslConfiguration()) { // check SSL - if (!QSslSocket::supportsSsl()) { + if (!configuration->sslEnabled()) { + qCDebug(dcConnection) << "SSL encryption disabled by config."; + } else if (!QSslSocket::supportsSsl()) { qCWarning(dcConnection) << "SSL is not supported/installed on this platform."; } else { qCDebug(dcConnection) << "SSL library version:" << QSslSocket::sslLibraryVersionString(); - // load SSL configuration settings - GuhSettings settings(GuhSettings::SettingsRoleGlobal); - qCDebug(dcConnection) << "Loading SSL-configuration from" << settings.fileName(); + QString configCertificateFileName = configuration->sslCertificate(); + QString configKeyFileName = configuration->sslCertificateKey(); - settings.beginGroup("SSL"); - 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(); + QString fallbackCertificateFileName = GuhSettings::storagePath() + "/certs/guhd-certificate.crt"; + QString fallbackKeyFileName = GuhSettings::storagePath() + "/certs/guhd-certificate.key"; - if (!loadCertificate(keyFileName, certificateFileName)) { - qCWarning(dcConnection) << "SSL encryption disabled"; + bool certsLoaded = false; + if (loadCertificate(configKeyFileName, configCertificateFileName)) { + qCDebug(dcConnection) << "Using SSL certificate:" << configKeyFileName; + certsLoaded = true; + } else if (loadCertificate(fallbackKeyFileName, fallbackCertificateFileName)) { + certsLoaded = true; + qCWarning(dcConnection) << "Using fallback self-signed SSL certificate:" << fallbackCertificateFileName; } else { + qCDebug(dcConnection) << "Generating self signed certificates..."; + CertificateGenerator::generate(fallbackCertificateFileName, fallbackKeyFileName); + if (loadCertificate(fallbackKeyFileName, fallbackCertificateFileName)) { + qCWarning(dcConnection) << "Using newly created self-signed SSL certificate:" << fallbackCertificateFileName; + certsLoaded = true; + } else { + qCWarning(dcConnection) << "Failed to load SSL certificates. SSL encryption disabled."; + } + } + if (certsLoaded) { m_sslConfiguration.setProtocol(QSsl::TlsV1_2); m_sslConfiguration.setPrivateKey(m_certificateKey); m_sslConfiguration.setLocalCertificate(m_certificate); @@ -137,20 +151,20 @@ bool ServerManager::loadCertificate(const QString &certificateKeyFileName, const { QFile certificateKeyFile(certificateKeyFileName); if (!certificateKeyFile.open(QIODevice::ReadOnly)) { - qCWarning(dcWebServer) << "Could not open" << certificateKeyFile.fileName() << ":" << certificateKeyFile.errorString(); + qCWarning(dcConnection) << "Could not open" << certificateKeyFile.fileName() << ":" << certificateKeyFile.errorString(); return false; } m_certificateKey = QSslKey(certificateKeyFile.readAll(), QSsl::Rsa); - qCDebug(dcWebServer) << "Loaded successfully private certificate key " << certificateKeyFileName; + qCDebug(dcConnection) << "Loaded private certificate key " << certificateKeyFileName; certificateKeyFile.close(); QFile certificateFile(certificateFileName); if (!certificateFile.open(QIODevice::ReadOnly)) { - qCWarning(dcWebServer) << "Could not open" << certificateFile.fileName() << ":" << certificateFile.errorString(); + qCWarning(dcConnection) << "Could not open" << certificateFile.fileName() << ":" << certificateFile.errorString(); return false; } m_certificate = QSslCertificate(certificateFile.readAll()); - qCDebug(dcWebServer) << "Loaded successfully certificate file " << certificateFileName; + qCDebug(dcConnection) << "Loaded certificate file " << certificateFileName; certificateFile.close(); return true; diff --git a/server/servermanager.h b/server/servermanager.h index 401ef7a5..6f6c2cad 100644 --- a/server/servermanager.h +++ b/server/servermanager.h @@ -35,6 +35,8 @@ #include "mocktcpserver.h" #endif +#include "openssl/ssl.h" + class QSslConfiguration; class QSslCertificate; class QSslKey; @@ -85,6 +87,81 @@ private: bool loadCertificate(const QString &certificateKeyFileName, const QString &certificateFileName); }; + +class CertificateGenerator +{ +public: + static void generate(const QString &certificateFilename, const QString &keyFilename) { + EVP_PKEY * pkey = nullptr; + RSA * rsa = nullptr; + X509 * x509 = nullptr; + X509_NAME * name = nullptr; + BIO * bp_public = nullptr, * bp_private = nullptr; + const char * buffer = nullptr; + long size; + + pkey = EVP_PKEY_new(); + q_check_ptr(pkey); + rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); + q_check_ptr(rsa); + EVP_PKEY_assign_RSA(pkey, rsa); + x509 = X509_new(); + q_check_ptr(x509); + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + X509_gmtime_adj(X509_get_notBefore(x509), 0); // not before current time + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // not after a year from this point + X509_set_pubkey(x509, pkey); + name = X509_get_subject_name(x509); + q_check_ptr(name); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"AT", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Guh GmbH", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"guh core autocreated", -1, -1, 0); + X509_set_issuer_name(x509, name); + X509_sign(x509, pkey, EVP_sha1()); + bp_private = BIO_new(BIO_s_mem()); + q_check_ptr(bp_private); + if(PEM_write_bio_PrivateKey(bp_private, pkey, nullptr, nullptr, 0, nullptr, nullptr) != 1) + { + EVP_PKEY_free(pkey); + X509_free(x509); + BIO_free_all(bp_private); + qFatal("PEM_write_bio_PrivateKey"); + } + bp_public = BIO_new(BIO_s_mem()); + q_check_ptr(bp_public); + if(PEM_write_bio_X509(bp_public, x509) != 1) + { + EVP_PKEY_free(pkey); + X509_free(x509); + BIO_free_all(bp_public); + BIO_free_all(bp_private); + qFatal("PEM_write_bio_PrivateKey"); + } + size = BIO_get_mem_data(bp_public, &buffer); + q_check_ptr(buffer); + QFileInfo certFi(certificateFilename); + QDir dir; + QFile certfile(certificateFilename); + if (!dir.mkpath(certFi.absolutePath()) || !certfile.open(QFile::WriteOnly | QFile::Truncate) || certfile.write(buffer, size) != size) { + qWarning() << "Error writing certificate file" << certificateFilename; + } + certfile.close(); + + size = BIO_get_mem_data(bp_private, &buffer); + q_check_ptr(buffer); + QFileInfo keyFi(keyFilename); + QFile keyFile(keyFilename); + if (!dir.mkpath(keyFi.absolutePath()) || !keyFile.open(QFile::WriteOnly | QFile::Truncate) || keyFile.write(buffer, size) != size) { + qWarning() << "Error writing key file" << keyFilename; + } + keyFile.close(); + + EVP_PKEY_free(pkey); // this will also free the rsa key + X509_free(x509); + BIO_free_all(bp_public); + BIO_free_all(bp_private); + } +}; } #endif // SERVERMANAGER_H diff --git a/tests/auto/autotests.pri b/tests/auto/autotests.pri index 675b5e94..5dec0742 100644 --- a/tests/auto/autotests.pri +++ b/tests/auto/autotests.pri @@ -9,7 +9,7 @@ INCLUDEPATH += $$top_srcdir/server/ \ $$top_srcdir/tests/auto/ LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/plugins/mock/ \ - -L$$top_builddir/tests/libguh-core/ -lguh-core + -L$$top_builddir/tests/libguh-core/ -lguh-core -lssl -lcrypto SOURCES += ../guhtestbase.cpp \ ../mocktcpserver.cpp \ diff --git a/tests/libguh-core/libguh-core.pro b/tests/libguh-core/libguh-core.pro index a021bae2..f345f435 100644 --- a/tests/libguh-core/libguh-core.pro +++ b/tests/libguh-core/libguh-core.pro @@ -13,7 +13,7 @@ INCLUDEPATH += $$top_srcdir/server/ \ $$top_srcdir/libguh \ $$top_srcdir/tests/auto/ -LIBS += -L$$top_builddir/libguh/ -lguh +LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') INSTALLS += target