diff --git a/utils/rbutilqt/base/httpget.cpp b/utils/rbutilqt/base/httpget.cpp index fb74514e73..0cd9236209 100644 --- a/utils/rbutilqt/base/httpget.cpp +++ b/utils/rbutilqt/base/httpget.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "httpget.h" #include "Logger.h" @@ -27,6 +28,7 @@ QString HttpGet::m_globalUserAgent; //< globally set user agent for requests QDir HttpGet::m_globalCache; //< global cach path value for new objects QNetworkProxy HttpGet::m_globalProxy; +QList HttpGet::m_acceptedClientCerts; HttpGet::HttpGet(QObject *parent) : QObject(parent), @@ -211,9 +213,30 @@ void HttpGet::startRequest(QUrl url) connect(m_reply, &QNetworkReply::errorOccurred, this, &HttpGet::networkError); #endif connect(m_reply, &QNetworkReply::downloadProgress, this, &HttpGet::downloadProgress); + connect(m_reply, &QNetworkReply::sslErrors, this, &HttpGet::gotSslError); } +void HttpGet::gotSslError(const QList &errors) +{ + LOG_WARNING() << "Got SSL error" << errors; + + // if this is a cert error, and only if we already accepted a remote cert + // ignore the error. + // This will make QNAM continue the request and finish it. + if (errors.size() == 1 + && errors.at(0).error() == QSslError::UnableToGetLocalIssuerCertificate + && m_acceptedClientCerts.contains(m_reply->sslConfiguration().peerCertificate())) { + LOG_INFO() << "client cert temporarily trusted by user."; + m_reply->ignoreSslErrors(); + } + else { + LOG_ERROR() << m_reply->sslConfiguration().peerCertificate().toText(); + emit sslError(errors.at(0), m_reply->sslConfiguration().peerCertificate()); + } + +} + void HttpGet::networkError(QNetworkReply::NetworkError error) { LOG_ERROR() << "NetworkError occured:" << error << m_reply->errorString(); diff --git a/utils/rbutilqt/base/httpget.h b/utils/rbutilqt/base/httpget.h index 443a606e6d..fb5b920b47 100644 --- a/utils/rbutilqt/base/httpget.h +++ b/utils/rbutilqt/base/httpget.h @@ -73,6 +73,8 @@ class HttpGet : public QObject //< set global user agent string static void setGlobalUserAgent(const QString& u) { m_globalUserAgent = u; } + static void addTrustedPeerCert(QSslCertificate cert) + { m_acceptedClientCerts.append(cert);} public slots: void abort(void); @@ -81,14 +83,17 @@ class HttpGet : public QObject void done(QNetworkReply::NetworkError error); void dataReadProgress(int, int); void headerFinished(void); + void sslError(const QSslError& error, const QSslCertificate& peerCert); private slots: void requestFinished(QNetworkReply* reply); void startRequest(QUrl url); void downloadProgress(qint64 received, qint64 total); void networkError(QNetworkReply::NetworkError error); + void gotSslError(const QList &errors); private: + static QList m_acceptedClientCerts; static QString m_globalUserAgent; static QNetworkProxy m_globalProxy; QNetworkAccessManager m_mgr; diff --git a/utils/rbutilqt/rbutilqt.cpp b/utils/rbutilqt/rbutilqt.cpp index 6d0da3390f..680303859e 100644 --- a/utils/rbutilqt/rbutilqt.cpp +++ b/utils/rbutilqt/rbutilqt.cpp @@ -205,6 +205,7 @@ void RbUtilQt::downloadInfo() // try to get the current build information daily = new HttpGet(this); connect(daily, &HttpGet::done, this, &RbUtilQt::downloadDone); + connect(daily, &HttpGet::sslError, this, &RbUtilQt::sslError); connect(qApp, &QGuiApplication::lastWindowClosed, daily, &HttpGet::abort); daily->setCache(false); ui.statusbar->showMessage(tr("Downloading build information, please wait ...")); @@ -213,10 +214,49 @@ void RbUtilQt::downloadInfo() daily->getFile(QUrl(PlayerBuildInfo::instance()->value(PlayerBuildInfo::BuildInfoUrl).toString())); } +void RbUtilQt::sslError(const QSslError& error, const QSslCertificate& peerCert) +{ + LOG_WARNING() << "sslError" << (int)error.error(); + // On Rockbox Utility start we always try to get the build info first. + // Thus we can use that to catch potential certificate errors. + // If the user accepts the certificate we'll have HttpGet ignore all cert + // errors for the exact certificate we got during this first request. + // Thus we don't need to handle cert errors later anymore. + if (error.error() == QSslError::UnableToGetLocalIssuerCertificate) { + QMessageBox mb(this); + mb.setWindowTitle(tr("Certificate error")); + mb.setIcon(QMessageBox::Warning); + mb.setText(tr("%1\n\n" + "Issuer: %2\n" + "Subject: %3\n" + "Valid since: %4\n" + "Valid until: %5\n\n" + "Temporarily trust certificate?") + .arg(error.errorString()) + .arg(peerCert.issuerInfo(QSslCertificate::Organization).at(0)) + .arg(peerCert.subjectDisplayName()) + .arg(peerCert.effectiveDate().toString()) + .arg(peerCert.expiryDate().toString()) + ); + mb.setDetailedText(peerCert.toText()); + mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + auto r = mb.exec(); + if (r == QMessageBox::Yes) { + HttpGet::addTrustedPeerCert(peerCert); + downloadInfo(); + } + else { + downloadDone(QNetworkReply::OperationCanceledError); + } + } +} + void RbUtilQt::downloadDone(QNetworkReply::NetworkError error) { - if(error != QNetworkReply::NoError) { + if(error != QNetworkReply::NoError + && error != QNetworkReply::SslHandshakeFailedError) { LOG_INFO() << "network error:" << daily->errorString(); ui.statusbar->showMessage(tr("Can't get version information!")); QMessageBox::critical(this, tr("Network error"), diff --git a/utils/rbutilqt/rbutilqt.h b/utils/rbutilqt/rbutilqt.h index c507317fa2..9caa8a1267 100644 --- a/utils/rbutilqt/rbutilqt.h +++ b/utils/rbutilqt/rbutilqt.h @@ -72,6 +72,7 @@ class RbUtilQt : public QMainWindow bool m_auto; private slots: + void sslError(const QSslError& error, const QSslCertificate& peerCert); void shutdown(void); void about(void); void help(void);