aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxiphon <xiphon@protonmail.com>2020-04-14 21:03:15 +0000
committerxiphon <xiphon@protonmail.com>2020-04-14 21:37:04 +0000
commitea25b71ca66a7d81203bbfed7fa1a1d6fa79553f (patch)
tree40bfaac76cba0e0f68a7b1e2b79cec0eab4841d6
parent8e4124f06aa6c580ee62dd1b78735bc488e21366 (diff)
downloadmonzero-gui-ea25b71ca66a7d81203bbfed7fa1a1d6fa79553f.tar.gz
monzero-gui-ea25b71ca66a7d81203bbfed7fa1a1d6fa79553f.tar.xz
monzero-gui-ea25b71ca66a7d81203bbfed7fa1a1d6fa79553f.zip
updater: fetch signed hashes from getmonero.org, verify downloads
-rw-r--r--components/UpdateDialog.qml15
-rw-r--r--main.qml14
-rw-r--r--src/libwalletqt/WalletManager.cpp28
-rw-r--r--src/libwalletqt/WalletManager.h8
-rw-r--r--src/qt/downloader.cpp19
-rw-r--r--src/qt/downloader.h2
-rw-r--r--src/qt/network.cpp11
-rw-r--r--src/qt/network.h1
-rw-r--r--src/qt/updater.cpp55
-rw-r--r--src/qt/updater.h11
10 files changed, 131 insertions, 33 deletions
diff --git a/components/UpdateDialog.qml b/components/UpdateDialog.qml
index 74c62516..5fbef79e 100644
--- a/components/UpdateDialog.qml
+++ b/components/UpdateDialog.qml
@@ -37,11 +37,12 @@ import "../components" as MoneroComponents
Popup {
id: updateDialog
+ property bool active: false
property bool allowed: true
property string error: ""
property string filename: ""
+ property string hash: ""
property double progress: url && downloader.total > 0 ? downloader.loaded * 100 / downloader.total : 0
- property bool active: false
property string url: ""
property bool valid: false
property string version: ""
@@ -55,8 +56,9 @@ Popup {
padding: 20
visible: active && allowed
- function show(version, url) {
+ function show(version, url, hash) {
updateDialog.error = "";
+ updateDialog.hash = hash;
updateDialog.url = url;
updateDialog.valid = false;
updateDialog.version = version;
@@ -86,7 +88,7 @@ Popup {
Text {
id: statusText
- color: MoneroComponents.Style.defaultFontColor
+ color: updateDialog.valid ? MoneroComponents.Style.green : MoneroComponents.Style.defaultFontColor
font.family: MoneroComponents.Style.fontRegular.name
font.pixelSize: 18
visible: !errorText.visible
@@ -102,9 +104,9 @@ Popup {
+ translationManager.emptyString;
}
if (updateDialog.valid) {
- return qsTr("Download finished") + translationManager.emptyString;
+ return qsTr("Update downloaded, signature verified") + translationManager.emptyString;
}
- return qsTr("Do you want to download new version?") + translationManager.emptyString;
+ return qsTr("Do you want to download and verify new version?") + translationManager.emptyString;
}
}
@@ -156,8 +158,9 @@ Popup {
onClicked: {
updateDialog.error = "";
updateDialog.filename = updateDialog.url.replace(/^.*\//, '');
- const downloadingStarted = downloader.get(updateDialog.url, function(error) {
+ const downloadingStarted = downloader.get(updateDialog.url, updateDialog.hash, function(error) {
if (error) {
+ console.error("Download failed", error);
updateDialog.error = qsTr("Download failed") + translationManager.emptyString;
} else {
updateDialog.valid = true;
diff --git a/main.qml b/main.qml
index 5c171400..37bf7799 100644
--- a/main.qml
+++ b/main.qml
@@ -1978,15 +1978,11 @@ ApplicationWindow {
closeWallet(Qt.quit);
}
- function onWalletCheckUpdatesComplete(update) {
- if (update === "")
- return
- print("Update found: " + update)
- var parts = update.split("|")
- if (parts.length == 4) {
- updateDialog.show(parts[0], isMac || isWindows || isLinux ? parts[3] : "");
- } else {
- print("Failed to parse update spec")
+ function onWalletCheckUpdatesComplete(version, downloadUrl, hash, firstSigner, secondSigner) {
+ const alreadyAsked = updateDialog.url == downloadUrl && updateDialog.hash == hash;
+ if (!alreadyAsked)
+ {
+ updateDialog.show(version, isMac || isWindows || isLinux ? downloadUrl : "", hash);
}
}
diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp
index 01faf905..ed4f5776 100644
--- a/src/libwalletqt/WalletManager.cpp
+++ b/src/libwalletqt/WalletManager.cpp
@@ -41,6 +41,8 @@
#include <QMutexLocker>
#include <QString>
+#include "qt/updater.h"
+
class WalletPassphraseListenerImpl : public Monero::WalletListener
{
public:
@@ -464,11 +466,31 @@ bool WalletManager::saveQrCode(const QString &code, const QString &path) const
void WalletManager::checkUpdatesAsync(const QString &software, const QString &subdir)
{
m_scheduler.run([this, software, subdir] {
- emit checkUpdatesComplete(checkUpdates(software, subdir));
- });
-}
+ const auto updateInfo = Monero::WalletManager::checkUpdates(software.toStdString(), subdir.toStdString());
+ if (!std::get<0>(updateInfo))
+ {
+ return;
+ }
+
+ const QString version = QString::fromStdString(std::get<1>(updateInfo));
+ const QByteArray hashFromDns = QByteArray::fromHex(QString::fromStdString(std::get<2>(updateInfo)).toUtf8());
+ const QString downloadUrl = QString::fromStdString(std::get<4>(updateInfo));
+ try
+ {
+ const QString binaryFilename = QUrl(downloadUrl).fileName();
+ QPair<QString, QString> signers;
+ const QString signedHash = Updater().fetchSignedHash(binaryFilename, hashFromDns, signers).toHex();
+ qInfo() << "Update found" << version << downloadUrl << "hash" << signedHash << "signed by" << signers;
+ emit checkUpdatesComplete(version, downloadUrl, signedHash, signers.first, signers.second);
+ }
+ catch (const std::exception &e)
+ {
+ qCritical() << "Failed to fetch and verify signed hash:" << e.what();
+ }
+ });
+}
QString WalletManager::checkUpdates(const QString &software, const QString &subdir) const
{
diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h
index d03e4e13..6470152d 100644
--- a/src/libwalletqt/WalletManager.h
+++ b/src/libwalletqt/WalletManager.h
@@ -36,7 +36,6 @@
#include <QMutex>
#include <QPointer>
#include <QWaitCondition>
-#include <QMutex>
#include "qt/FutureScheduler.h"
#include "NetworkType.h"
@@ -192,7 +191,12 @@ signals:
void walletPassphraseNeeded();
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
- void checkUpdatesComplete(const QString &result) const;
+ void checkUpdatesComplete(
+ const QString &version,
+ const QString &downloadUrl,
+ const QString &hash,
+ const QString &firstSigner,
+ const QString &secondSigner) const;
void miningStatus(bool isMining) const;
public slots:
diff --git a/src/qt/downloader.cpp b/src/qt/downloader.cpp
index afd2049e..d925c77c 100644
--- a/src/qt/downloader.cpp
+++ b/src/qt/downloader.cpp
@@ -31,6 +31,8 @@
#include <QReadLocker>
#include <QWriteLocker>
+#include "updater.h"
+
namespace
{
@@ -112,10 +114,10 @@ void Downloader::cancel()
m_contents.clear();
}
-bool Downloader::get(const QString &url, const QJSValue &callback)
+bool Downloader::get(const QString &url, const QString &hash, const QJSValue &callback)
{
auto future = m_scheduler.run(
- [this, url]() {
+ [this, url, hash]() {
DownloaderStateGuard stateGuard(m_active, m_mutex, [this]() {
emit activeChanged();
});
@@ -153,6 +155,19 @@ bool Downloader::get(const QString &url, const QJSValue &callback)
return QJSValueList({"empty response"});
}
+ try
+ {
+ const QByteArray calculatedHash = Updater().getHash(&response[0], response.size());
+ if (QByteArray::fromHex(hash.toUtf8()) != calculatedHash)
+ {
+ return QJSValueList({"hash sum mismatch"});
+ }
+ }
+ catch (const std::exception &e)
+ {
+ return QJSValueList({e.what()});
+ }
+
{
QWriteLocker locker(&m_mutex);
diff --git a/src/qt/downloader.h b/src/qt/downloader.h
index 44840535..418627a5 100644
--- a/src/qt/downloader.h
+++ b/src/qt/downloader.h
@@ -44,7 +44,7 @@ public:
~Downloader();
Q_INVOKABLE void cancel();
- Q_INVOKABLE bool get(const QString &url, const QJSValue &callback);
+ Q_INVOKABLE bool get(const QString &url, const QString &hash, const QJSValue &callback);
Q_INVOKABLE bool saveToFile(const QString &path) const;
signals:
diff --git a/src/qt/network.cpp b/src/qt/network.cpp
index 01d0f296..1a674508 100644
--- a/src/qt/network.cpp
+++ b/src/qt/network.cpp
@@ -117,6 +117,17 @@ void Network::getJSON(const QString &url, const QJSValue &callback) const
get(url, callback, "application/json; charset=utf-8");
}
+std::string Network::get(const QString &url, const QString &contentType /* = {} */) const
+{
+ std::string response;
+ QString error = get(std::shared_ptr<http_simple_client>(new http_simple_client()), url, response, contentType);
+ if (!error.isEmpty())
+ {
+ throw std::runtime_error(QString("failed to fetch %1: %2").arg(url).arg(error).toStdString());
+ }
+ return response;
+}
+
QString Network::get(
std::shared_ptr<http_simple_client> httpClient,
const QString &url,
diff --git a/src/qt/network.h b/src/qt/network.h
index acdd6280..e741247d 100644
--- a/src/qt/network.h
+++ b/src/qt/network.h
@@ -77,6 +77,7 @@ public:
Q_INVOKABLE void get(const QString &url, const QJSValue &callback, const QString &contentType = {}) const;
Q_INVOKABLE void getJSON(const QString &url, const QJSValue &callback) const;
+ std::string get(const QString &url, const QString &contentType = {}) const;
QString get(
std::shared_ptr<epee::net_utils::http::http_simple_client> httpClient,
const QString &url,
diff --git a/src/qt/updater.cpp b/src/qt/updater.cpp
index 5968c2b5..17d6eddb 100644
--- a/src/qt/updater.cpp
+++ b/src/qt/updater.cpp
@@ -30,6 +30,7 @@
#include <openpgp/hash.h>
+#include "network.h"
#include "utils.h"
Updater::Updater()
@@ -39,17 +40,41 @@ Updater::Updater()
m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/luigi1111.asc").toStdString());
}
-QPair<QString, QString> Updater::verifySignaturesAndHashSum(
+QByteArray Updater::fetchSignedHash(
+ const QString &binaryFilename,
+ const QByteArray &hashFromDns,
+ QPair<QString, QString> &signers) const
+{
+ static constexpr const char hashesTxtUrl[] = "https://web.getmonero.org/downloads/hashes.txt";
+ static constexpr const char hashesTxtSigUrl[] = "https://web.getmonero.org/downloads/hashes.txt.sig";
+
+ const Network network;
+ std::string hashesTxt = network.get(hashesTxtUrl);
+ std::string hashesTxtSig = network.get(hashesTxtSigUrl);
+
+ const QByteArray signedHash = verifyParseSignedHahes(
+ QByteArray(&hashesTxt[0], hashesTxt.size()),
+ QByteArray(&hashesTxtSig[0], hashesTxtSig.size()),
+ binaryFilename,
+ signers);
+
+ if (signedHash != hashFromDns)
+ {
+ throw std::runtime_error("DNS hash mismatch");
+ }
+
+ return signedHash;
+}
+
+QByteArray Updater::verifyParseSignedHahes(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
const QString &binaryFilename,
- const void *binaryData,
- size_t binarySize) const
+ QPair<QString, QString> &signers) const
{
- QString firstSigner;
- const QString signedMessage = verifySignature(armoredSignedHashes, firstSigner);
+ const QString signedMessage = verifySignature(armoredSignedHashes, signers.first);
- QString secondSigner = verifySignature(
+ signers.second = verifySignature(
epee::span<const uint8_t>(
reinterpret_cast<const uint8_t *>(armoredSignedHashes.data()),
armoredSignedHashes.size()),
@@ -57,19 +82,31 @@ QPair<QString, QString> Updater::verifySignaturesAndHashSum(
reinterpret_cast<const uint8_t *>(secondDetachedSignature.data()),
secondDetachedSignature.size())));
- if (firstSigner == secondSigner)
+ if (signers.first == signers.second)
{
throw std::runtime_error("both signatures were generated by the same person");
}
- const QByteArray signedHash = parseShasumOutput(signedMessage, binaryFilename);
+ return parseShasumOutput(signedMessage, binaryFilename);
+}
+
+QPair<QString, QString> Updater::verifySignaturesAndHashSum(
+ const QByteArray &armoredSignedHashes,
+ const QByteArray &secondDetachedSignature,
+ const QString &binaryFilename,
+ const void *binaryData,
+ size_t binarySize) const
+{
+ QPair<QString, QString> signers;
+ const QByteArray signedHash =
+ verifyParseSignedHahes(armoredSignedHashes, secondDetachedSignature, binaryFilename, signers);
const QByteArray calculatedHash = getHash(binaryData, binarySize);
if (signedHash != calculatedHash)
{
throw std::runtime_error("hash sum mismatch");
}
- return {firstSigner, secondSigner};
+ return signers;
}
QByteArray Updater::getHash(const void *data, size_t size) const
diff --git a/src/qt/updater.h b/src/qt/updater.h
index bc06d0c0..48a59a71 100644
--- a/src/qt/updater.h
+++ b/src/qt/updater.h
@@ -37,6 +37,11 @@ class Updater
public:
Updater();
+ QByteArray fetchSignedHash(
+ const QString &binaryFilename,
+ const QByteArray &hashFromDns,
+ QPair<QString, QString> &signers) const;
+ QByteArray getHash(const void *data, size_t size) const;
QPair<QString, QString> verifySignaturesAndHashSum(
const QByteArray &armoredSignedHashes,
const QByteArray &secondDetachedSignature,
@@ -45,7 +50,11 @@ public:
size_t binarySize) const;
private:
- QByteArray getHash(const void *data, size_t size) const;
+ QByteArray verifyParseSignedHahes(
+ const QByteArray &armoredSignedHashes,
+ const QByteArray &secondDetachedSignature,
+ const QString &binaryFilename,
+ QPair<QString, QString> &signers) const;
QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const;
QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
QByteArray parseShasumOutput(const QString &message, const QString &filename) const;