From ea25b71ca66a7d81203bbfed7fa1a1d6fa79553f Mon Sep 17 00:00:00 2001 From: xiphon Date: Tue, 14 Apr 2020 21:03:15 +0000 Subject: updater: fetch signed hashes from getmonero.org, verify downloads --- src/qt/downloader.cpp | 19 ++++++++++++++++-- src/qt/downloader.h | 2 +- src/qt/network.cpp | 11 +++++++++++ src/qt/network.h | 1 + src/qt/updater.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++--------- src/qt/updater.h | 11 ++++++++++- 6 files changed, 86 insertions(+), 13 deletions(-) (limited to 'src/qt') 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 #include +#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(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 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 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 +#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 Updater::verifySignaturesAndHashSum( +QByteArray Updater::fetchSignedHash( + const QString &binaryFilename, + const QByteArray &hashFromDns, + QPair &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 &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( reinterpret_cast(armoredSignedHashes.data()), armoredSignedHashes.size()), @@ -57,19 +82,31 @@ QPair Updater::verifySignaturesAndHashSum( reinterpret_cast(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 Updater::verifySignaturesAndHashSum( + const QByteArray &armoredSignedHashes, + const QByteArray &secondDetachedSignature, + const QString &binaryFilename, + const void *binaryData, + size_t binarySize) const +{ + QPair 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 &signers) const; + QByteArray getHash(const void *data, size_t size) const; QPair 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 &signers) const; QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const; QString verifySignature(const epee::span data, const openpgp::signature_rsa &signature) const; QByteArray parseShasumOutput(const QString &message, const QString &filename) const; -- cgit v1.2.3