Avoid TLS errors on Syncthing's automatic certificate renewal
* Reload the certificate when running into TLS errors an it looks like the certificate was renewed * See https://github.com/Martchus/syncthingtray/issues/226
This commit is contained in:
parent
a6e7a0002c
commit
07ff8a5c1b
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include <QAuthenticator>
|
#include <QAuthenticator>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
@ -776,6 +777,10 @@ void SyncthingConnection::continueConnecting()
|
||||||
* - Loading the certificate is only possible if the connection object is configured
|
* - Loading the certificate is only possible if the connection object is configured
|
||||||
* to connect to the locally running Syncthing instance. Otherwise this method will
|
* to connect to the locally running Syncthing instance. Otherwise this method will
|
||||||
* only do the cleanup of previous certificates but not emit any errors.
|
* only do the cleanup of previous certificates but not emit any errors.
|
||||||
|
* - This function uses m_certificatePath which is set by applySettings() if the user
|
||||||
|
* specified a certificate path manually. Otherwise the path is detected automatically
|
||||||
|
* and stored in m_dynamicallyDeterminedCertificatePath so the certificate path is
|
||||||
|
* known in handleSslErrors().
|
||||||
* \returns Returns whether a certificate could be loaded.
|
* \returns Returns whether a certificate could be loaded.
|
||||||
*/
|
*/
|
||||||
bool SyncthingConnection::loadSelfSignedCertificate(const QUrl &url)
|
bool SyncthingConnection::loadSelfSignedCertificate(const QUrl &url)
|
||||||
|
@ -795,7 +800,9 @@ bool SyncthingConnection::loadSelfSignedCertificate(const QUrl &url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// find cert
|
// find cert
|
||||||
const auto certPath = !m_configDir.isEmpty() ? (m_configDir + QStringLiteral("/https-cert.pem")) : SyncthingConfig::locateHttpsCertificate();
|
const auto certPath = !m_certificatePath.isEmpty()
|
||||||
|
? m_certificatePath
|
||||||
|
: (!m_configDir.isEmpty() ? (m_configDir + QStringLiteral("/https-cert.pem")) : SyncthingConfig::locateHttpsCertificate());
|
||||||
if (certPath.isEmpty()) {
|
if (certPath.isEmpty()) {
|
||||||
emit error(tr("Unable to locate certificate used by Syncthing."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
emit error(tr("Unable to locate certificate used by Syncthing."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
||||||
return false;
|
return false;
|
||||||
|
@ -807,13 +814,21 @@ bool SyncthingConnection::loadSelfSignedCertificate(const QUrl &url)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_expectedSslErrors = SyncthingConnectionSettings::compileSslErrors(certs.at(0));
|
m_expectedSslErrors = SyncthingConnectionSettings::compileSslErrors(certs.at(0));
|
||||||
|
// keep track of the dynamically determined certificate path for handleSslErrors()
|
||||||
|
if (m_certificatePath.isEmpty()) {
|
||||||
|
m_dynamicallyDeterminedCertificatePath = certPath;
|
||||||
|
m_certificateLastModified = QFileInfo(certPath).lastModified();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Applies the specified configuration.
|
* \brief Applies the specified configuration.
|
||||||
* \remarks
|
* \remarks
|
||||||
* - The expected SSL errors of the specified configuration are updated accordingly.
|
* - The expected SSL errors are taken from the specified \a connectionSettings. If empty, this
|
||||||
|
* function attempts to load expected SSL errors automatically as needed/possible via
|
||||||
|
* loadSelfSignedCertificate(). It then writes back those SSL errors to \a connectionSettings.
|
||||||
|
* This way \a connectionSettings can act as a cache for SSL exceptions.
|
||||||
* - The configuration is not used instantly. It will be used on the next reconnect.
|
* - The configuration is not used instantly. It will be used on the next reconnect.
|
||||||
* \returns Returns whether at least one property requiring a reconnect to take effect has changed.
|
* \returns Returns whether at least one property requiring a reconnect to take effect has changed.
|
||||||
* \sa reconnect()
|
* \sa reconnect()
|
||||||
|
@ -838,6 +853,8 @@ bool SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionS
|
||||||
}
|
}
|
||||||
reconnectRequired = true;
|
reconnectRequired = true;
|
||||||
}
|
}
|
||||||
|
m_certificatePath = connectionSettings.httpsCertPath;
|
||||||
|
m_certificateLastModified = connectionSettings.httpCertLastModified;
|
||||||
if (connectionSettings.expectedSslErrors.isEmpty()) {
|
if (connectionSettings.expectedSslErrors.isEmpty()) {
|
||||||
const bool previouslyHadExpectedSslErrors = !expectedSslErrors().isEmpty();
|
const bool previouslyHadExpectedSslErrors = !expectedSslErrors().isEmpty();
|
||||||
const bool ok = loadSelfSignedCertificate();
|
const bool ok = loadSelfSignedCertificate();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <c++utilities/misc/flagenumclass.h>
|
#include <c++utilities/misc/flagenumclass.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
@ -422,6 +423,9 @@ private:
|
||||||
QString m_lastFileName;
|
QString m_lastFileName;
|
||||||
QString m_syncthingVersion;
|
QString m_syncthingVersion;
|
||||||
bool m_lastFileDeleted;
|
bool m_lastFileDeleted;
|
||||||
|
QString m_certificatePath;
|
||||||
|
QString m_dynamicallyDeterminedCertificatePath;
|
||||||
|
QDateTime m_certificateLastModified;
|
||||||
QList<QSslError> m_expectedSslErrors;
|
QList<QSslError> m_expectedSslErrors;
|
||||||
QSslCertificate m_certFromLastSslError;
|
QSslCertificate m_certFromLastSslError;
|
||||||
QJsonObject m_rawConfig;
|
QJsonObject m_rawConfig;
|
||||||
|
|
|
@ -146,7 +146,14 @@ static QString certText(const QSslCertificate &cert)
|
||||||
/// \endcond
|
/// \endcond
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Handles SSL errors of replies; just for logging purposes at this point.
|
* \brief Handles SSL errors of replies.
|
||||||
|
* \remarks
|
||||||
|
* - Ignores expected errors which are usually assigned via applySettings() or loadSelfSignedCertificate() to handle a self-signed
|
||||||
|
* certificate.
|
||||||
|
* - If expected errors have previously been assigned to handle a self-signed certificate this function attempts to reload the
|
||||||
|
* certificate via loadSelfSignedCertificate() if it appears to be re-generated. This is done because Syncthing might re-generate
|
||||||
|
* the certificate if it will expire soon (indicated by the log message "Loading HTTPS certificate: certificate will soon expire"
|
||||||
|
* followed by "Creating new HTTPS certificate").
|
||||||
*/
|
*/
|
||||||
void SyncthingConnection::handleSslErrors(const QList<QSslError> &errors)
|
void SyncthingConnection::handleSslErrors(const QList<QSslError> &errors)
|
||||||
{
|
{
|
||||||
|
@ -162,6 +169,20 @@ void SyncthingConnection::handleSslErrors(const QList<QSslError> &errors)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check whether the certificate has changed and reload it before emitting error
|
||||||
|
if (const auto &certPath = m_certificatePath.isEmpty() ? m_dynamicallyDeterminedCertificatePath : m_certificatePath;
|
||||||
|
!certPath.isEmpty() && m_certificateLastModified.isValid()) {
|
||||||
|
if (const auto lastModified = QFileInfo(certPath).lastModified(); lastModified > m_certificateLastModified) {
|
||||||
|
if (const auto ok = loadSelfSignedCertificate(); ok && !m_certificatePath.isEmpty()) {
|
||||||
|
m_certificateLastModified = lastModified;
|
||||||
|
}
|
||||||
|
// re-check whether error is expected after reloading and skip it accordingly
|
||||||
|
if (m_expectedSslErrors.contains(error)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle the error by emitting the error signal with all the details including the certificate
|
// handle the error by emitting the error signal with all the details including the certificate
|
||||||
// note: Of course the failing request would cause a QNetworkReply::SslHandshakeFailedError anyways. However,
|
// note: Of course the failing request would cause a QNetworkReply::SslHandshakeFailedError anyways. However,
|
||||||
// at this point the concrete SSL error with the certificate is not accessible anymore.
|
// at this point the concrete SSL error with the certificate is not accessible anymore.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "./syncthingconnectionsettings.h"
|
#include "./syncthingconnectionsettings.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
QList<QSslError> SyncthingConnectionSettings::compileSslErrors(const QSslCertificate &trustedCert)
|
QList<QSslError> SyncthingConnectionSettings::compileSslErrors(const QSslCertificate &trustedCert)
|
||||||
|
@ -27,6 +29,7 @@ bool SyncthingConnectionSettings::loadHttpsCert()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpCertLastModified = QFileInfo(httpsCertPath).lastModified();
|
||||||
expectedSslErrors = compileSslErrors(certs.at(0));
|
expectedSslErrors = compileSslErrors(certs.at(0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <c++utilities/misc/flagenumclass.h>
|
#include <c++utilities/misc/flagenumclass.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QSslError>
|
#include <QSslError>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -48,6 +49,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnectionSettings {
|
||||||
int requestTimeout = defaultRequestTimeout;
|
int requestTimeout = defaultRequestTimeout;
|
||||||
int longPollingTimeout = defaultLongPollingTimeout;
|
int longPollingTimeout = defaultLongPollingTimeout;
|
||||||
QString httpsCertPath;
|
QString httpsCertPath;
|
||||||
|
QDateTime httpCertLastModified;
|
||||||
QList<QSslError> expectedSslErrors;
|
QList<QSslError> expectedSslErrors;
|
||||||
SyncthingStatusComputionFlags statusComputionFlags = SyncthingStatusComputionFlags::Default;
|
SyncthingStatusComputionFlags statusComputionFlags = SyncthingStatusComputionFlags::Default;
|
||||||
bool autoConnect = false;
|
bool autoConnect = false;
|
||||||
|
|
Loading…
Reference in New Issue