diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 990405078..029c326fc 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -73,6 +73,8 @@ const char* AppConfig::m_SynergySettingsName[] = { "useInternalConfig", "groupClientChecked", "serverHostname", + "tlsCertPath", + "tlsKeyLength", }; static const char* logLevelNames[] = @@ -249,6 +251,13 @@ void AppConfig::loadSettings() m_ClientGroupChecked = loadSetting(kGroupClientCheck, true).toBool(); m_ServerHostname = loadSetting(kServerHostname).toString(); + //Set the default path of the TLS certificate file in the users DIR + QString certificateFilename = QString("%1/%2/%3").arg(m_CoreInterface.getProfileDir(), + "SSL", + "Synergy.pem"); + + m_TLSCertificatePath = loadSetting(kTLSCertPath, certificateFilename).toString(); + m_TLSKeyLength = loadSetting(kTLSKeyLength, "2048").toString(); } @@ -527,3 +536,19 @@ void AppConfig::setSettingModified(T &variable, const T& newValue) { } } +void AppConfig::setTLSCertPath(const QString& path) { + m_TLSCertificatePath = path; +} + +QString AppConfig::getTLSCertPath() const { + return m_TLSCertificatePath; +} + +QString AppConfig::getTLSKeyLength() const { + return m_TLSKeyLength; +} + +void AppConfig::setTLSKeyLength(const QString& length) { + m_TLSKeyLength = length; +} + diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 452899484..0f40a7049 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -27,6 +27,7 @@ #include #include #include "ConfigBase.h" +#include "CoreInterface.h" // this should be incremented each time a new page is added. this is // saved to settings when the user finishes running the wizard. if @@ -126,6 +127,14 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase bool getClientGroupChecked() const; QString getServerHostname() const; + /// @brief Gets the current TLS certificate path + /// @return QString The path to the cert + QString getTLSCertPath() const; + + /// @brief Get the key length to be used for the private key of a TLS cert + /// @return QString The key length in bits + QString getTLSKeyLength() const; + void setServerGroupChecked(bool); void setUseExternalConfig(bool) ; void setConfigFile(const QString&); @@ -133,6 +142,15 @@ class AppConfig: public QObject, public GUI::Config::ConfigBase void setClientGroupChecked(bool) ; void setServerHostname(const QString&); + /// @brief Set the path to the TLS/SSL certificate file that will be used + /// @param [in] path The path to the Certificate + void setTLSCertPath(const QString& path); + + /// @brief Sets the key length of the private key to use in a TLS connection + /// @param [in] QString length The key length eg: 1024, 2048, 4096 + void setTLSKeyLength(const QString& length); + + QString lastVersion() const; void setMinimizeToTray(bool b); @@ -174,6 +192,8 @@ protected: kUseInternalConfig, kGroupClientCheck, kServerHostname, + kTLSCertPath, + kTLSKeyLength, }; void setScreenName(const QString& s); @@ -224,10 +244,15 @@ protected: bool m_ClientGroupChecked; QString m_ServerHostname; + QString m_TLSCertificatePath; /// @brief The path to the TLS certificate file + QString m_TLSKeyLength; /// @brief The key length of the TLS cert to make + bool m_LoadFromSystemScope; /// @brief should the setting be loaded from SystemScope /// If the user has settings but this is true then /// system settings will be loaded instead of the users + CoreInterface m_CoreInterface; + static const char m_SynergysName[]; static const char m_SynergycName[]; static const char m_SynergyLogDir[]; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 9b4da2b5d..4cc156abb 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -670,6 +670,7 @@ void MainWindow::startSynergy() if (m_AppConfig->getCryptoEnabled()) { args << "--enable-crypto"; + args << "--tls-cert" << m_AppConfig->getTLSCertPath(); } #if defined(Q_OS_WIN) @@ -744,10 +745,6 @@ void MainWindow::retryStart() void MainWindow::sslToggled (bool enabled) { - if (enabled) { - m_pSslCertificate = new SslCertificate(this); - m_pSslCertificate->generateCertificate(); - } updateLocalFingerprint(); } diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index b8c720ec0..9f44a9a75 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -65,9 +65,22 @@ void SettingsDialog::accept() appConfig().setAutoHide(m_pCheckBoxAutoHide->isChecked()); appConfig().setAutoConfig(m_pCheckBoxAutoConfig->isChecked()); appConfig().setMinimizeToTray(m_pCheckBoxMinimizeToTray->isChecked()); + appConfig().setTLSCertPath(m_pLineEditCertificatePath->text()); + + bool keyLengthChanged = appConfig().getTLSKeyLength() != m_pComboBoxKeyLength->currentText(); + appConfig().setTLSKeyLength(m_pComboBoxKeyLength->currentText()); //We only need to test the System scoped Radio as they are connected appConfig().setLoadFromSystemScope(m_pRadioSystemScope->isChecked()); + + if(m_pCheckBoxEnableCrypto->isChecked()) { + SslCertificate sslCertificate; + sslCertificate.generateCertificate(appConfig().getTLSCertPath(), + m_pComboBoxKeyLength->currentText(), + keyLengthChanged); + } + m_appConfig.setCryptoEnabled(m_pCheckBoxEnableCrypto->isChecked()); + QDialog::accept(); } @@ -115,8 +128,17 @@ void SettingsDialog::loadFromConfig() { setIndexFromItemData(m_pComboLanguage, appConfig().language()); m_pCheckBoxAutoHide->setChecked(appConfig().getAutoHide()); m_pCheckBoxMinimizeToTray->setChecked(appConfig().getMinimizeToTray()); + m_pLineEditCertificatePath->setText(appConfig().getTLSCertPath()); m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled()); + //If the tls file exists test its key length + if (QFile(appConfig().getTLSCertPath()).exists()) { + updateKeyLengthOnFile(appConfig().getTLSCertPath()); + } else { + m_pComboBoxKeyLength->setCurrentIndex(m_pComboBoxKeyLength->findText(appConfig().getTLSKeyLength())); + } + + if (m_appConfig.isSystemScoped()) { m_pRadioSystemScope->setChecked(true); } @@ -142,6 +164,8 @@ void SettingsDialog::loadFromConfig() { #endif m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled()); + m_pGroupBoxTLS->setVisible(m_appConfig.getCryptoEnabled()); + #ifdef SYNERGY_ENTERPRISE @@ -160,6 +184,7 @@ void SettingsDialog::loadFromConfig() { m_pCheckBoxAutoConfig->setChecked(appConfig().autoConfig()); #endif + adjustSize(); } @@ -202,9 +227,13 @@ void SettingsDialog::on_m_pCheckBoxEnableCrypto_toggled(bool checked) m_appConfig.setCryptoEnabled(checked); if (checked) { SslCertificate sslCertificate; - sslCertificate.generateCertificate(); + sslCertificate.generateCertificate(m_pLineEditCertificatePath->text(), m_pComboBoxKeyLength->currentText()); m_pMainWindow->updateLocalFingerprint(); + verticalSpacer_4->changeSize(10, 10, QSizePolicy::Minimum); + } else { + verticalSpacer_4->changeSize(10, 0, QSizePolicy::Ignored); } + adjustSize(); } void SettingsDialog::on_m_pLabelInstallBonjour_linkActivated(const QString&) @@ -219,3 +248,57 @@ void SettingsDialog::on_m_pRadioSystemScope_toggled(bool checked) appConfig().setLoadFromSystemScope(checked); loadFromConfig(); } + +void SettingsDialog::on_m_pPushButtonBrowseCert_clicked() { + QString fileName = QFileDialog::getSaveFileName( + this, tr("Select a TLS certificate to use..."), + m_pLineEditCertificatePath->text(), + "Cert (*.pem)", + nullptr, + QFileDialog::DontConfirmOverwrite); + + if (!fileName.isEmpty()) { + m_pLineEditCertificatePath->setText(fileName); + //If the tls file exists test its key length and update + if (QFile(appConfig().getTLSCertPath()).exists()) { + updateKeyLengthOnFile(fileName); + } + } + updateRegenButton(); +} + +void SettingsDialog::regenerateSSLCert() { + SslCertificate sslCertificate; + sslCertificate.generateCertificate(appConfig().getTLSCertPath(), + appConfig().getTLSKeyLength(), + true); + + m_pMainWindow->updateLocalFingerprint(); +} + +void SettingsDialog::on_m_pComboBoxKeyLength_currentIndexChanged(int index) { + updateRegenButton(); +} + +void SettingsDialog::updateRegenButton() { + // Disable the Regenerate cert button if the key length is different to saved + auto keyChanged = appConfig().getTLSKeyLength() != m_pComboBoxKeyLength->currentText(); + auto pathChanged = appConfig().getTLSCertPath() != m_pLineEditCertificatePath->text(); + auto cryptoChanged = appConfig().getCryptoEnabled() != m_pCheckBoxEnableCrypto->isChecked(); + //NOR the above bools, if any have changed regen should be disabled as it will be done on save + auto nor = !(keyChanged || pathChanged || cryptoChanged); + m_pPushButtonRegenCert->setEnabled(nor); +} + +void SettingsDialog::on_m_pPushButtonRegenCert_clicked() { + regenerateSSLCert(); +} + +void SettingsDialog::updateKeyLengthOnFile(const QString &path) { + SslCertificate ssl; + auto length = ssl.getCertKeyLength(path); + auto index = m_pComboBoxKeyLength->findText(length); + m_pComboBoxKeyLength->setCurrentIndex(index); + //Also update what is in the appconfig to match the file itself + appConfig().setTLSKeyLength(length); +} diff --git a/src/gui/src/SettingsDialog.h b/src/gui/src/SettingsDialog.h index 64596dda5..1b03dcd9b 100644 --- a/src/gui/src/SettingsDialog.h +++ b/src/gui/src/SettingsDialog.h @@ -48,6 +48,16 @@ class SettingsDialog : public QDialog, public Ui::SettingsDialogBase /// @brief Causes the dialog to load all the settings from m_appConfig void loadFromConfig(); + /// @brief Forces the regeneration of the TLS cert from the saved settings + void regenerateSSLCert(); + + /// @brief Check if the regenerate button should be enabled or disabled and sets it + void updateRegenButton(); + + /// @brief Updates the key length value based on the loaded file + /// @param [in] QString path The path to the file to test + void updateKeyLengthOnFile(const QString& path); + private: MainWindow* m_pMainWindow; AppConfig& m_appConfig; @@ -65,6 +75,17 @@ class SettingsDialog : public QDialog, public Ui::SettingsDialogBase /// @brief Handles the toggling of the system scoped radio button /// As the user scope radio is connected this will fire for either radio button void on_m_pRadioSystemScope_toggled(bool checked); + + /// @brief Handles the click event of the Cert Path browse button + /// displaying a file browser + void on_m_pPushButtonBrowseCert_clicked(); + + /// @brief Handles the TLS cert key length changed event + void on_m_pComboBoxKeyLength_currentIndexChanged(int index); + + /// @brief handels the regenerate cert button event + /// This will regenerate the TLS certificate as long as the settings haven't changed + void on_m_pPushButtonRegenCert_clicked(); }; #endif diff --git a/src/gui/src/SettingsDialogBase.ui b/src/gui/src/SettingsDialogBase.ui index ef7fcc806..a79c59226 100644 --- a/src/gui/src/SettingsDialogBase.ui +++ b/src/gui/src/SettingsDialogBase.ui @@ -6,16 +6,15 @@ 0 0 - 357 - 496 + 378 + 756 Settings - - - + + &Settings Scope @@ -41,7 +40,7 @@ - + &Miscellaneous @@ -190,7 +189,7 @@ - + Qt::Vertical @@ -206,7 +205,7 @@ - + true @@ -220,10 +219,7 @@ &Network - - - QFormLayout::AllNonFixedFieldsGrow - + 2 @@ -236,7 +232,7 @@ 12 - + 0 @@ -244,36 +240,6 @@ 12 - - - - false - - - Enable &TLS Encryption - - - - - - - false - - - Enable Auto Config - - - - - - - <html><head/><body><p><a href="#"><span style=" text-decoration: underline; color:#007af4;">Install Bonjour</span></a></p></body></html> - - - Qt::RichText - - - @@ -287,12 +253,42 @@ + + + + <html><head/><body><p><a href="#"><span style=" text-decoration: underline; color:#007af4;">Install Bonjour</span></a></p></body></html> + + + Qt::RichText + + + + + + + false + + + Enable Auto Config + + + + + + + false + + + Enable &TLS Encryption + + + - + Qt::Vertical @@ -308,7 +304,85 @@ - + + + + TLS/SSL Settings + + + + + + + + + Key length + + + + + + + Certificate Path + + + + + + + Browse + + + + + + + 2048 + + + + 1024 + + + + + 2048 + + + + + 4096 + + + + + + + + Regenerate Cert + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 10 + + + + + @@ -393,7 +467,7 @@ - + Qt::Vertical @@ -409,7 +483,7 @@ - + Qt::Horizontal @@ -422,13 +496,21 @@ + m_pRadioSystemScope + m_pRadioUserScope m_pComboLanguage m_pLineEditScreenName m_pSpinBoxPort m_pLineEditInterface - m_pCheckBoxMinimizeToTray m_pComboElevate m_pCheckBoxAutoHide + m_pCheckBoxMinimizeToTray + m_pCheckBoxAutoConfig + m_pCheckBoxEnableCrypto + m_pComboBoxKeyLength + m_pLineEditCertificatePath + m_pPushButtonBrowseCert + m_pPushButtonRegenCert m_pComboLogLevel m_pCheckBoxLogToFile m_pLineEditLogFilename @@ -468,5 +550,21 @@ + + m_pCheckBoxEnableCrypto + toggled(bool) + m_pGroupBoxTLS + setVisible(bool) + + + 100 + 413 + + + 188 + 508 + + + diff --git a/src/gui/src/SslCertificate.cpp b/src/gui/src/SslCertificate.cpp index 777d4fd41..3e48cd732 100644 --- a/src/gui/src/SslCertificate.cpp +++ b/src/gui/src/SslCertificate.cpp @@ -25,7 +25,7 @@ -static const char kCertificateKeyLength[] = "rsa:2048"; //RSA Bit length (e.g. 1024/2048/4096) +static const char kCertificateKeyLength[] = "rsa:"; //RSA Bit length (e.g. 1024/2048/4096) static const char kCertificateHashAlgorithm[] = "-sha256"; //fingerprint hashing algorithm static const char kCertificateLifetime[] = "365"; static const char kCertificateSubjectInfo[] = "/CN=Synergy"; @@ -93,7 +93,7 @@ bool SslCertificate::runTool(const QStringList& args) return true; } -void SslCertificate::generateCertificate() +void SslCertificate::generateCertificate(const QString& path, const QString& keyLength, bool forceGen) { QString sslDirPath = QString("%1%2%3") .arg(m_ProfileDir) @@ -105,8 +105,13 @@ void SslCertificate::generateCertificate() .arg(QDir::separator()) .arg(kCertificateFilename); - QFile file(filename); - if (!file.exists()) { + QString keySize = kCertificateKeyLength + keyLength; + + const QString pathToUse = path.isEmpty() ? filename : path; + + //If path is empty use filename + QFile file(pathToUse); + if (!file.exists() || forceGen) { QStringList arguments; // self signed certificate @@ -126,7 +131,7 @@ void SslCertificate::generateCertificate() // private key arguments.append("-newkey"); - arguments.append(kCertificateKeyLength); + arguments.append(keySize); QDir sslDir(sslDirPath); if (!sslDir.exists()) { @@ -135,11 +140,11 @@ void SslCertificate::generateCertificate() // key output filename arguments.append("-keyout"); - arguments.append(filename); + arguments.append(pathToUse); // certificate output filename arguments.append("-out"); - arguments.append(filename); + arguments.append(pathToUse); if (!runTool(arguments)) { return; @@ -148,7 +153,7 @@ void SslCertificate::generateCertificate() emit info(tr("SSL certificate generated.")); } - generateFingerprint(filename); + generateFingerprint(pathToUse); emit generateFinished(); } @@ -181,3 +186,28 @@ void SslCertificate::generateFingerprint(const QString& certificateFilename) emit error(tr("Failed to find SSL fingerprint.")); } } + +QString SslCertificate::getCertKeyLength(const QString &path) { + + QStringList arguments; + arguments.append("rsa"); + arguments.append("-in"); + arguments.append(path); + arguments.append("-text"); + arguments.append("-noout"); + + if (!runTool(arguments)) { + return QString(); + } + const QString searchStart("Private-Key: ("); + const QString searchEnd(" bit"); + + //Get the line that contains the key length from the output + const auto indexStart = m_ToolOutput.indexOf(searchStart); + const auto indexEnd = m_ToolOutput.indexOf(searchEnd, indexStart); + const auto start = indexStart + searchStart.length(); + const auto end = indexEnd - (indexStart + searchStart.length()); + auto keyLength = m_ToolOutput.mid(start, end); + + return keyLength; +} diff --git a/src/gui/src/SslCertificate.h b/src/gui/src/SslCertificate.h index 6683269df..a88a41af1 100644 --- a/src/gui/src/SslCertificate.h +++ b/src/gui/src/SslCertificate.h @@ -20,6 +20,7 @@ #include "CoreInterface.h" #include +#include class SslCertificate : public QObject { @@ -29,7 +30,16 @@ public: explicit SslCertificate(QObject *parent = 0); public slots: - void generateCertificate(); + /// @brief Generates a TLS cert and private key + /// @param [in] QString path The path of the file to be generated + /// @param [in] QString keyLength The size of the private key. default: 2048 + /// @param [in] bool Should the file be created regardless of if the file already exists + void generateCertificate(const QString& path = QString(), const QString& keyLength = "2048", bool forceGen = false); + + /// @brief Get the key length of a TLS private key + /// @param [in] QString path The path of the file to checked + /// @return QString The key legnth as a string + QString getCertKeyLength(const QString& path); signals: void error(QString e);