aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/main/main.cpp35
-rw-r--r--src/openpgp/CMakeLists.txt18
-rw-r--r--src/openpgp/hash.h107
-rw-r--r--src/openpgp/mpi.h78
-rw-r--r--src/openpgp/openpgp.cpp380
-rw-r--r--src/openpgp/openpgp.h122
-rw-r--r--src/openpgp/packet_stream.h76
-rw-r--r--src/openpgp/s_expression.h78
-rw-r--r--src/openpgp/serialization.h171
-rw-r--r--src/qt/updater.cpp130
-rw-r--r--src/qt/updater.h55
-rw-r--r--src/qt/utils.cpp18
-rw-r--r--src/qt/utils.h1
14 files changed, 1271 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9cfe114b..de84824b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(QR-Code-scanner)
add_subdirectory(daemon)
add_subdirectory(libwalletqt)
add_subdirectory(model)
+add_subdirectory(openpgp)
add_subdirectory(zxcvbn-c)
qt5_add_resources(RESOURCES ../qml.qrc)
@@ -149,6 +150,7 @@ target_link_libraries(monero-gui
${QT5_LIBRARIES}
${EXTRA_LIBRARIES}
${ICU_LIBRARIES}
+ openpgp
)
if(WITH_SCANNER)
diff --git a/src/main/main.cpp b/src/main/main.cpp
index c564c59f..2a64abdd 100644
--- a/src/main/main.cpp
+++ b/src/main/main.cpp
@@ -64,6 +64,7 @@
#include "qt/downloader.h"
#include "qt/ipc.h"
#include "qt/network.h"
+#include "qt/updater.h"
#include "qt/utils.h"
#include "qt/TailsOS.h"
#include "qt/KeysFiles.h"
@@ -222,6 +223,14 @@ int main(int argc, char *argv[])
QCoreApplication::translate("main", "Log to specified file"),
QCoreApplication::translate("main", "file"));
+ QCommandLineOption verifyUpdateOption("verify-update", "\
+Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by two maintainers.\n\
+* Requires 'hashes.txt' - signed 'shasum' output \
+(i.e. 'gpg -o hashes.txt --clear-sign <shasum_output>') generated by a maintainer.\n\
+* Requires 'hashes.txt.sig' - detached signature of 'hashes.txt' \
+(i.e. 'gpg -b hashes.txt') generated by another maintainer.", "update-binary");
+ parser.addOption(verifyUpdateOption);
+
QCommandLineOption testQmlOption("test-qml");
testQmlOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(logPathOption);
@@ -245,6 +254,32 @@ int main(int argc, char *argv[])
}
qWarning().noquote() << "app startd" << "(log: " + logPath + ")";
+ if (parser.isSet(verifyUpdateOption))
+ {
+ const QString updateBinaryFullPath = parser.value(verifyUpdateOption);
+ const QFileInfo updateBinaryInfo(updateBinaryFullPath);
+ const QString updateBinaryDir = QDir::toNativeSeparators(updateBinaryInfo.absolutePath()) + QDir::separator();
+ const QString hashesTxt = updateBinaryDir + "hashes.txt";
+ const QString hashesTxtSig = hashesTxt + ".sig";
+ try
+ {
+ const QByteArray updateBinaryContents = fileGetContents(updateBinaryFullPath);
+ const QPair<QString, QString> signers = Updater().verifySignaturesAndHashSum(
+ fileGetContents(hashesTxt),
+ fileGetContents(hashesTxtSig),
+ updateBinaryInfo.fileName(),
+ updateBinaryContents.data(),
+ updateBinaryContents.size());
+ qCritical() << "successfully verified, signed by" << signers.first << "and" << signers.second;
+ return 0;
+ }
+ catch (const std::exception &e)
+ {
+ qCritical() << e.what();
+ }
+ return 1;
+ }
+
// Desktop entry
#ifdef Q_OS_LINUX
registerXdgMime(app);
diff --git a/src/openpgp/CMakeLists.txt b/src/openpgp/CMakeLists.txt
new file mode 100644
index 00000000..a5a36ee7
--- /dev/null
+++ b/src/openpgp/CMakeLists.txt
@@ -0,0 +1,18 @@
+file(GLOB_RECURSE SOURCES *.cpp)
+file(GLOB_RECURSE HEADERS *.h)
+
+find_library(GCRYPT_LIBRARY gcrypt)
+find_library(GPG_ERROR_LIBRARY gpg-error)
+
+add_library(openpgp
+ ${SOURCES}
+ ${HEADERS})
+
+target_include_directories(openpgp
+ PUBLIC
+ ${CMAKE_SOURCE_DIR}/monero/contrib/epee/include)
+
+target_link_libraries(openpgp
+ PUBLIC
+ ${GCRYPT_LIBRARY}
+ ${GPG_ERROR_LIBRARY})
diff --git a/src/openpgp/hash.h b/src/openpgp/hash.h
new file mode 100644
index 00000000..0952c602
--- /dev/null
+++ b/src/openpgp/hash.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <vector>
+
+#include <gcrypt.h>
+#include <span.h>
+
+namespace openpgp
+{
+
+class hash
+{
+public:
+ enum algorithm : uint8_t
+ {
+ sha256 = 8,
+ };
+
+ hash(const hash &) = delete;
+ hash &operator=(const hash &) = delete;
+
+ hash(uint8_t algorithm)
+ : algorithm(algorithm)
+ , consumed(0)
+ {
+ if (gcry_md_open(&md, algorithm, 0) != GPG_ERR_NO_ERROR)
+ {
+ throw std::runtime_error("failed to create message digest object");
+ }
+ }
+
+ ~hash()
+ {
+ gcry_md_close(md);
+ }
+
+ hash &operator<<(uint8_t byte)
+ {
+ gcry_md_putc(md, byte);
+ ++consumed;
+ return *this;
+ }
+
+ hash &operator<<(const epee::span<const uint8_t> &bytes)
+ {
+ gcry_md_write(md, &bytes[0], bytes.size());
+ consumed += bytes.size();
+ return *this;
+ }
+
+ hash &operator<<(const std::vector<uint8_t> &bytes)
+ {
+ return *this << epee::to_span(bytes);
+ }
+
+ std::vector<uint8_t> finish() const
+ {
+ std::vector<uint8_t> result(gcry_md_get_algo_dlen(algorithm));
+ const void *digest = gcry_md_read(md, algorithm);
+ if (digest == nullptr)
+ {
+ throw std::runtime_error("failed to read the digest");
+ }
+ memcpy(&result[0], digest, result.size());
+ return result;
+ }
+
+ size_t consumed_bytes() const
+ {
+ return consumed;
+ }
+
+private:
+ const uint8_t algorithm;
+ gcry_md_hd_t md;
+ size_t consumed;
+};
+
+}
diff --git a/src/openpgp/mpi.h b/src/openpgp/mpi.h
new file mode 100644
index 00000000..800bcac5
--- /dev/null
+++ b/src/openpgp/mpi.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <gcrypt.h>
+
+namespace openpgp
+{
+
+class mpi
+{
+public:
+ mpi(const mpi &) = delete;
+ mpi &operator=(const mpi &) = delete;
+
+ mpi(mpi &&other)
+ : data(other.data)
+ {
+ other.data = nullptr;
+ }
+
+ template <
+ typename byte_container,
+ typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
+ mpi(const byte_container &buffer, gcry_mpi_format format = GCRYMPI_FMT_USG)
+ : mpi(&buffer[0], buffer.size(), format)
+ {
+ }
+
+ mpi(const void *buffer, size_t size, gcry_mpi_format format = GCRYMPI_FMT_USG)
+ {
+ if (gcry_mpi_scan(&data, format, buffer, size, nullptr) != GPG_ERR_NO_ERROR)
+ {
+ throw std::runtime_error("failed to read mpi from buffer");
+ }
+ }
+
+ ~mpi()
+ {
+ gcry_mpi_release(data);
+ }
+
+ const gcry_mpi_t &get() const
+ {
+ return data;
+ }
+
+private:
+ gcry_mpi_t data;
+};
+
+} // namespace openpgp
diff --git a/src/openpgp/openpgp.cpp b/src/openpgp/openpgp.cpp
new file mode 100644
index 00000000..108990fb
--- /dev/null
+++ b/src/openpgp/openpgp.cpp
@@ -0,0 +1,380 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "openpgp.h"
+
+#include <algorithm>
+#include <locale>
+#include <vector>
+
+#include <string_coding.h>
+
+#include "hash.h"
+#include "mpi.h"
+#include "packet_stream.h"
+#include "s_expression.h"
+#include "serialization.h"
+
+namespace openpgp
+{
+namespace
+{
+
+std::string::const_iterator find_next_line(std::string::const_iterator begin, const std::string::const_iterator &end)
+{
+ begin = std::find(begin, end, '\n');
+ return begin != end ? ++begin : end;
+}
+
+std::string::const_iterator find_line_starting_with(
+ std::string::const_iterator it,
+ const std::string::const_iterator &end,
+ const std::string &starts_with)
+{
+ for (std::string::const_iterator next_line; it != end; it = next_line)
+ {
+ next_line = find_next_line(it, end);
+ const size_t line_length = static_cast<size_t>(std::distance(it, next_line));
+ if (line_length >= starts_with.size() && std::equal(starts_with.begin(), starts_with.end(), it))
+ {
+ return it;
+ }
+ }
+ return end;
+}
+
+std::string::const_iterator find_empty_line(std::string::const_iterator it, const std::string::const_iterator &end)
+{
+ for (; it != end && *it != '\r' && *it != '\n'; it = find_next_line(it, end))
+ {
+ }
+ return it;
+}
+
+std::string get_armored_block_contents(const std::string &text, const std::string &block_name)
+{
+ static constexpr const char dashes[] = "-----";
+ const std::string armor_header = dashes + block_name + dashes;
+ auto block_start = find_line_starting_with(text.begin(), text.end(), armor_header);
+ auto block_headers = find_next_line(block_start, text.end());
+ auto block_end = find_line_starting_with(block_headers, text.end(), dashes);
+ auto contents_begin = find_next_line(find_empty_line(block_headers, block_end), block_end);
+ if (contents_begin == block_end)
+ {
+ throw std::runtime_error("armored block not found");
+ }
+ return std::string(contents_begin, block_end);
+}
+
+} // namespace
+
+public_key_rsa::public_key_rsa(const std::string &armored)
+ : public_key_rsa(decode(armored))
+{
+}
+
+public_key_rsa::public_key_rsa(std::tuple<std::string, s_expression, size_t> params)
+ : m_expression(std::move(std::get<1>(params)))
+ , m_bits(std::get<2>(params))
+ , m_user_id(std::move(std::get<0>(params)))
+{
+}
+
+const gcry_sexp_t &public_key_rsa::get() const
+{
+ return m_expression.get();
+}
+
+size_t public_key_rsa::bits() const
+{
+ return m_bits;
+}
+
+std::string public_key_rsa::user_id() const
+{
+ return m_user_id;
+}
+
+std::tuple<std::string, s_expression, size_t> public_key_rsa::decode(const std::string &armored)
+{
+ const std::string buffer = epee::string_encoding::base64_decode(
+ strip_line_breaks(get_armored_block_contents(armored, "BEGIN PGP PUBLIC KEY BLOCK")));
+ return decode(epee::to_byte_span(epee::to_span(buffer)));
+}
+
+std::tuple<std::string, s_expression, size_t> public_key_rsa::decode(const epee::span<const uint8_t> buffer)
+{
+ packet_stream packets(buffer);
+
+ const std::vector<uint8_t> *data = packets.find_first(packet_tag::type::user_id);
+ if (data == nullptr)
+ {
+ throw std::runtime_error("user id is missing");
+ }
+ std::string user_id(data->begin(), data->end());
+
+ data = packets.find_first(packet_tag::type::public_key);
+ if (data == nullptr)
+ {
+ throw std::runtime_error("public key is missing");
+ }
+
+ deserializer<std::vector<uint8_t>> serialized(*data);
+
+ const auto version = serialized.read_big_endian<uint8_t>();
+ if (version != 4)
+ {
+ throw std::runtime_error("unsupported public key version");
+ }
+
+ /* const auto timestamp = */ serialized.read_big_endian<uint32_t>();
+
+ const auto algorithm = serialized.read_big_endian<uint8_t>();
+ if (algorithm != algorithm::rsa)
+ {
+ throw std::runtime_error("unsupported public key algorithm");
+ }
+
+ const mpi public_key_n = serialized.read_mpi();
+ const mpi public_key_e = serialized.read_mpi();
+
+ s_expression expression("(public-key (rsa (n %m) (e %m)))", public_key_n.get(), public_key_e.get());
+
+ return {std::move(user_id), std::move(expression), gcry_mpi_get_nbits(public_key_n.get())};
+}
+
+signature_rsa::signature_rsa(
+ uint8_t algorithm,
+ std::pair<uint8_t, uint8_t> hash_leftmost_bytes,
+ uint8_t hash_algorithm,
+ const std::vector<uint8_t> &hashed_data,
+ type type,
+ s_expression signature,
+ uint8_t version)
+ : m_hash_algorithm(hash_algorithm)
+ , m_hash_leftmost_bytes(hash_leftmost_bytes)
+ , m_hashed_appendix(format_hashed_appendix(algorithm, hash_algorithm, hashed_data, type, version))
+ , m_signature(std::move(signature))
+ , m_type(type)
+{
+}
+
+signature_rsa signature_rsa::from_armored(const std::string &armored_signed_message)
+{
+ return from_base64(get_armored_block_contents(armored_signed_message, "BEGIN PGP SIGNATURE"));
+}
+
+signature_rsa signature_rsa::from_base64(const std::string &base64)
+{
+ std::string decoded = epee::string_encoding::base64_decode(strip_line_breaks(base64));
+ epee::span<const uint8_t> buffer(reinterpret_cast<const uint8_t *>(&decoded[0]), decoded.size());
+ return from_buffer(buffer);
+}
+
+signature_rsa signature_rsa::from_buffer(const epee::span<const uint8_t> input)
+{
+ packet_stream packets(input);
+
+ const std::vector<uint8_t> *data = packets.find_first(packet_tag::type::signature);
+ if (data == nullptr)
+ {
+ throw std::runtime_error("signature is missing");
+ }
+
+ deserializer<std::vector<uint8_t>> buffer(*data);
+
+ const auto version = buffer.read_big_endian<uint8_t>();
+ if (version != 4)
+ {
+ throw std::runtime_error("unsupported signature version");
+ }
+
+ const auto signature_type = static_cast<type>(buffer.read_big_endian<uint8_t>());
+
+ const auto algorithm = buffer.read_big_endian<uint8_t>();
+ if (algorithm != algorithm::rsa)
+ {
+ throw std::runtime_error("unsupported signature algorithm");
+ }
+
+ const auto hash_algorithm = buffer.read_big_endian<uint8_t>();
+
+ const auto hashed_data_length = buffer.read_big_endian<uint16_t>();
+ std::vector<uint8_t> hashed_data = buffer.read(hashed_data_length);
+
+ const auto unhashed_data_length = buffer.read_big_endian<uint16_t>();
+ buffer.read_span(unhashed_data_length);
+
+ std::pair<uint8_t, uint8_t> hash_leftmost_bytes{buffer.read_big_endian<uint8_t>(), buffer.read_big_endian<uint8_t>()};
+
+ const mpi signature = buffer.read_mpi();
+
+ return signature_rsa(
+ algorithm,
+ std::move(hash_leftmost_bytes),
+ hash_algorithm,
+ hashed_data,
+ signature_type,
+ s_expression("(sig-val (rsa (s %m)))", signature.get()),
+ version);
+}
+
+bool signature_rsa::verify(const epee::span<const uint8_t> message, const public_key_rsa &public_key) const
+{
+ const s_expression signed_data = hash_message(message, public_key.bits());
+ return gcry_pk_verify(m_signature.get(), signed_data.get(), public_key.get()) == 0;
+}
+
+s_expression signature_rsa::hash_message(const epee::span<const uint8_t> message, size_t public_key_bits) const
+{
+ switch (m_type)
+ {
+ case type::binary_document:
+ return hash_bytes(message, public_key_bits);
+ case type::canonical_text_document:
+ {
+ std::vector<uint8_t> crlf_formatted;
+ crlf_formatted.reserve(message.size());
+ const size_t message_size = message.size();
+ for (size_t offset = 0; offset < message_size; ++offset)
+ {
+ const auto &character = message[offset];
+ if (character == '\r')
+ {
+ continue;
+ }
+ if (character == '\n')
+ {
+ const bool skip_last_crlf = offset + 1 == message_size;
+ if (skip_last_crlf)
+ {
+ break;
+ }
+ crlf_formatted.push_back('\r');
+ }
+ crlf_formatted.push_back(character);
+ }
+ return hash_bytes(epee::to_span(crlf_formatted), public_key_bits);
+ }
+ default:
+ throw std::runtime_error("unsupported signature type");
+ }
+}
+
+std::vector<uint8_t> signature_rsa::hash_asn_object_id() const
+{
+ size_t size;
+ if (gcry_md_algo_info(m_hash_algorithm, GCRYCTL_GET_ASNOID, nullptr, &size) != GPG_ERR_NO_ERROR)
+ {
+ throw std::runtime_error("failed to get ASN.1 Object Identifier (OID) size");
+ }
+
+ std::vector<uint8_t> asn_object_id(size);
+ if (gcry_md_algo_info(m_hash_algorithm, GCRYCTL_GET_ASNOID, &asn_object_id[0], &size) != GPG_ERR_NO_ERROR)
+ {
+ throw std::runtime_error("failed to get ASN.1 Object Identifier (OID)");
+ }
+
+ return asn_object_id;
+}
+
+s_expression signature_rsa::hash_bytes(const epee::span<const uint8_t> message, size_t public_key_bits) const
+{
+ const std::vector<uint8_t> plain_hash = (hash(m_hash_algorithm) << message << m_hashed_appendix).finish();
+ if (plain_hash.size() < 2)
+ {
+ throw std::runtime_error("insufficient message hash size");
+ }
+ if (plain_hash[0] != m_hash_leftmost_bytes.first || plain_hash[1] != m_hash_leftmost_bytes.second)
+ {
+ throw std::runtime_error("signature checksum doesn't match the expected value");
+ }
+
+ std::vector<uint8_t> asn_object_id = hash_asn_object_id();
+
+ const size_t public_key_bytes = bits_to_bytes(public_key_bits);
+ if (public_key_bytes < plain_hash.size() + asn_object_id.size() + 11)
+ {
+ throw std::runtime_error("insufficient public key bit length");
+ }
+
+ std::vector<uint8_t> emsa_pkcs1_v1_5_encoded;
+ emsa_pkcs1_v1_5_encoded.reserve(public_key_bytes);
+ emsa_pkcs1_v1_5_encoded.push_back(0);
+ emsa_pkcs1_v1_5_encoded.push_back(1);
+ const size_t ps_size = public_key_bytes - plain_hash.size() - asn_object_id.size() - 3;
+ emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), ps_size, 0xff);
+ emsa_pkcs1_v1_5_encoded.push_back(0);
+ emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), asn_object_id.begin(), asn_object_id.end());
+ emsa_pkcs1_v1_5_encoded.insert(emsa_pkcs1_v1_5_encoded.end(), plain_hash.begin(), plain_hash.end());
+
+ mpi value(emsa_pkcs1_v1_5_encoded);
+ return s_expression("(data (flags raw) (value %m))", value.get());
+}
+
+std::vector<uint8_t> signature_rsa::format_hashed_appendix(
+ uint8_t algorithm,
+ uint8_t hash_algorithm,
+ const std::vector<uint8_t> &hashed_data,
+ uint8_t type,
+ uint8_t version)
+{
+ const uint16_t hashed_data_size = static_cast<uint16_t>(hashed_data.size());
+ const uint32_t hashed_pefix_size = sizeof(version) + sizeof(type) + sizeof(algorithm) + sizeof(hash_algorithm) +
+ sizeof(hashed_data_size) + hashed_data.size();
+
+ std::vector<uint8_t> appendix;
+ appendix.reserve(hashed_pefix_size + sizeof(version) + sizeof(uint8_t) + sizeof(hashed_pefix_size));
+ appendix.push_back(version);
+ appendix.push_back(type);
+ appendix.push_back(algorithm);
+ appendix.push_back(hash_algorithm);
+ appendix.push_back(static_cast<uint8_t>(hashed_data_size >> 8));
+ appendix.push_back(static_cast<uint8_t>(hashed_data_size));
+ appendix.insert(appendix.end(), hashed_data.begin(), hashed_data.end());
+ appendix.push_back(version);
+ appendix.push_back(0xff);
+ appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 24));
+ appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 16));
+ appendix.push_back(static_cast<uint8_t>(hashed_pefix_size >> 8));
+ appendix.push_back(static_cast<uint8_t>(hashed_pefix_size));
+
+ return appendix;
+}
+
+message_armored::message_armored(const std::string &message_armored)
+ : m_message(get_armored_block_contents(message_armored, "BEGIN PGP SIGNED MESSAGE"))
+{
+}
+
+message_armored::operator epee::span<const uint8_t>() const
+{
+ return epee::to_byte_span(epee::to_span(m_message));
+}
+
+} // namespace openpgp
diff --git a/src/openpgp/openpgp.h b/src/openpgp/openpgp.h
new file mode 100644
index 00000000..cef003ed
--- /dev/null
+++ b/src/openpgp/openpgp.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <vector>
+
+#include <gcrypt.h>
+
+#include <span.h>
+
+#include "s_expression.h"
+
+namespace openpgp
+{
+
+enum algorithm : uint8_t
+{
+ rsa = 1,
+};
+
+class public_key_rsa
+{
+public:
+ public_key_rsa(const std::string &armored);
+ public_key_rsa(std::tuple<std::string, s_expression, size_t> params);
+
+ size_t bits() const;
+ const gcry_sexp_t &get() const;
+ std::string user_id() const;
+
+private:
+ static std::tuple<std::string, s_expression, size_t> decode(const std::string &armored);
+ static std::tuple<std::string, s_expression, size_t> decode(const epee::span<const uint8_t> buffer);
+
+private:
+ s_expression m_expression;
+ size_t m_bits;
+ std::string m_user_id;
+};
+
+class signature_rsa
+{
+public:
+ enum type : uint8_t
+ {
+ binary_document = 0,
+ canonical_text_document = 1,
+ };
+
+ signature_rsa(
+ uint8_t algorithm,
+ std::pair<uint8_t, uint8_t> hash_leftmost_bytes,
+ uint8_t hash_algorithm,
+ const std::vector<uint8_t> &hashed_data,
+ type type,
+ s_expression signature,
+ uint8_t version);
+
+ static signature_rsa from_armored(const std::string &armored_signed_message);
+ static signature_rsa from_base64(const std::string &base64);
+ static signature_rsa from_buffer(const epee::span<const uint8_t> input);
+
+ bool verify(const epee::span<const uint8_t> message, const public_key_rsa &public_key) const;
+
+private:
+ s_expression hash_message(const epee::span<const uint8_t> message, size_t public_key_bits) const;
+ std::vector<uint8_t> hash_asn_object_id() const;
+ s_expression hash_bytes(const epee::span<const uint8_t> message, size_t public_key_bits) const;
+
+ static std::vector<uint8_t> format_hashed_appendix(
+ uint8_t algorithm,
+ uint8_t hash_algorithm,
+ const std::vector<uint8_t> &hashed_data,
+ uint8_t type,
+ uint8_t version);
+
+private:
+ uint8_t m_hash_algorithm;
+ std::pair<uint8_t, uint8_t> m_hash_leftmost_bytes;
+ std::vector<uint8_t> m_hashed_appendix;
+ s_expression m_signature;
+ type m_type;
+};
+
+class message_armored
+{
+public:
+ message_armored(const std::string &message_armored);
+
+ operator epee::span<const uint8_t>() const;
+
+private:
+ std::string m_message;
+};
+
+} // namespace openpgp
diff --git a/src/openpgp/packet_stream.h b/src/openpgp/packet_stream.h
new file mode 100644
index 00000000..b93b8747
--- /dev/null
+++ b/src/openpgp/packet_stream.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <vector>
+
+#include <span.h>
+
+#include "serialization.h"
+
+namespace openpgp
+{
+
+class packet_stream
+{
+public:
+ packet_stream(const epee::span<const uint8_t> buffer)
+ : packet_stream(deserializer<epee::span<const uint8_t>>(buffer))
+ {
+ }
+
+ template <
+ typename byte_container,
+ typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
+ packet_stream(deserializer<byte_container> buffer)
+ {
+ while (!buffer.empty())
+ {
+ packet_tag tag = buffer.read_packet_tag();
+ packets.push_back({std::move(tag), buffer.read(tag.length)});
+ }
+ }
+
+ const std::vector<uint8_t> *find_first(packet_tag::type type) const
+ {
+ for (const auto &packet : packets)
+ {
+ if (packet.first.packet_type == type)
+ {
+ return &packet.second;
+ }
+ }
+ return nullptr;
+ }
+
+private:
+ std::vector<std::pair<packet_tag, std::vector<uint8_t>>> packets;
+};
+
+} // namespace openpgp
diff --git a/src/openpgp/s_expression.h b/src/openpgp/s_expression.h
new file mode 100644
index 00000000..b46bf216
--- /dev/null
+++ b/src/openpgp/s_expression.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <gcrypt.h>
+
+namespace openpgp
+{
+
+class s_expression
+{
+public:
+ s_expression(const s_expression &) = delete;
+ s_expression &operator=(const s_expression &) = delete;
+
+ template <typename... Args>
+ s_expression(Args... args)
+ {
+ if (gcry_sexp_build(&data, nullptr, args...) != GPG_ERR_NO_ERROR)
+ {
+ throw std::runtime_error("failed to build S-expression");
+ }
+ }
+
+ s_expression(s_expression &&other)
+ {
+ std::swap(data, other.data);
+ }
+
+ s_expression(gcry_sexp_t data)
+ : data(data)
+ {
+ }
+
+ ~s_expression()
+ {
+ gcry_sexp_release(data);
+ }
+
+ const gcry_sexp_t &get() const
+ {
+ return data;
+ }
+
+private:
+ gcry_sexp_t data = nullptr;
+};
+
+} // namespace openpgp
diff --git a/src/openpgp/serialization.h b/src/openpgp/serialization.h
new file mode 100644
index 00000000..33de9216
--- /dev/null
+++ b/src/openpgp/serialization.h
@@ -0,0 +1,171 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "mpi.h"
+
+namespace openpgp
+{
+
+size_t bits_to_bytes(size_t bits)
+{
+ constexpr const uint16_t bits_in_byte = 8;
+ return (bits + bits_in_byte - 1) / bits_in_byte;
+}
+
+std::string strip_line_breaks(const std::string &string)
+{
+ std::string result;
+ result.reserve(string.size());
+ for (const auto &character : string)
+ {
+ if (character != '\r' && character != '\n')
+ {
+ result.push_back(character);
+ }
+ }
+ return result;
+}
+
+struct packet_tag
+{
+ enum type : uint8_t
+ {
+ signature = 2,
+ public_key = 6,
+ user_id = 13,
+ };
+
+ const type packet_type;
+ const size_t length;
+};
+
+template <
+ typename byte_container,
+ typename = typename std::enable_if<(sizeof(typename byte_container::value_type) == 1)>::type>
+class deserializer
+{
+public:
+ deserializer(byte_container buffer)
+ : buffer(std::move(buffer))
+ , cursor(0)
+ {
+ }
+
+ bool empty() const
+ {
+ return buffer.size() - cursor == 0;
+ }
+
+ packet_tag read_packet_tag()
+ {
+ const auto tag = read_big_endian<uint8_t>();
+
+ constexpr const uint8_t format_mask = 0b11000000;
+ constexpr const uint8_t format_old_tag = 0b10000000;
+ if ((tag & format_mask) != format_old_tag)
+ {
+ throw std::runtime_error("invalid packet tag");
+ }
+
+ const packet_tag::type packet_type = static_cast<packet_tag::type>((tag & 0b00111100) >> 2);
+ const uint8_t length_type = tag & 0b00000011;
+
+ size_t length;
+ switch (length_type)
+ {
+ case 0:
+ length = read_big_endian<uint8_t>();
+ break;
+ case 1:
+ length = read_big_endian<uint16_t>();
+ break;
+ case 2:
+ length = read_big_endian<uint32_t>();
+ break;
+ default:
+ throw std::runtime_error("unsupported packet length type");
+ }
+
+ return {packet_type, length};
+ }
+
+ mpi read_mpi()
+ {
+ const size_t bit_length = read_big_endian<uint16_t>();
+ return mpi(read_span(bits_to_bytes(bit_length)));
+ }
+
+ std::vector<uint8_t> read(size_t size)
+ {
+ if (buffer.size() - cursor < size)
+ {
+ throw std::runtime_error("insufficient buffer size");
+ }
+
+ const size_t offset = cursor;
+ cursor += size;
+
+ return {&buffer[offset], &buffer[cursor]};
+ }
+
+ template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
+ T read_big_endian()
+ {
+ if (buffer.size() - cursor < sizeof(T))
+ {
+ throw std::runtime_error("insufficient buffer size");
+ }
+ T result = 0;
+ for (size_t read = 0; read < sizeof(T); ++read)
+ {
+ result = (result << 8) | static_cast<uint8_t>(buffer[cursor++]);
+ }
+ return result;
+ }
+
+ epee::span<const uint8_t> read_span(size_t size)
+ {
+ if (buffer.size() - cursor < size)
+ {
+ throw std::runtime_error("insufficient buffer size");
+ }
+
+ const size_t offset = cursor;
+ cursor += size;
+
+ return {reinterpret_cast<const uint8_t *>(&buffer[offset]), size};
+ }
+
+private:
+ byte_container buffer;
+ size_t cursor;
+};
+
+} // namespace openpgp
diff --git a/src/qt/updater.cpp b/src/qt/updater.cpp
new file mode 100644
index 00000000..5968c2b5
--- /dev/null
+++ b/src/qt/updater.cpp
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "updater.h"
+
+#include <openpgp/hash.h>
+
+#include "utils.h"
+
+Updater::Updater()
+{
+ m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/binaryfate.asc").toStdString());
+ m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/fluffypony.asc").toStdString());
+ m_maintainers.emplace_back(fileGetContents(":/monero/utils/gpg_keys/luigi1111.asc").toStdString());
+}
+
+QPair<QString, QString> Updater::verifySignaturesAndHashSum(
+ const QByteArray &armoredSignedHashes,
+ const QByteArray &secondDetachedSignature,
+ const QString &binaryFilename,
+ const void *binaryData,
+ size_t binarySize) const
+{
+ QString firstSigner;
+ const QString signedMessage = verifySignature(armoredSignedHashes, firstSigner);
+
+ QString secondSigner = verifySignature(
+ epee::span<const uint8_t>(
+ reinterpret_cast<const uint8_t *>(armoredSignedHashes.data()),
+ armoredSignedHashes.size()),
+ openpgp::signature_rsa::from_buffer(epee::span<const uint8_t>(
+ reinterpret_cast<const uint8_t *>(secondDetachedSignature.data()),
+ secondDetachedSignature.size())));
+
+ if (firstSigner == secondSigner)
+ {
+ throw std::runtime_error("both signatures were generated by the same person");
+ }
+
+ const QByteArray signedHash = parseShasumOutput(signedMessage, binaryFilename);
+ const QByteArray calculatedHash = getHash(binaryData, binarySize);
+ if (signedHash != calculatedHash)
+ {
+ throw std::runtime_error("hash sum mismatch");
+ }
+
+ return {firstSigner, secondSigner};
+}
+
+QByteArray Updater::getHash(const void *data, size_t size) const
+{
+ openpgp::hash hasher(openpgp::hash::algorithm::sha256);
+ hasher << epee::span<const uint8_t>(reinterpret_cast<const uint8_t *>(data), size);
+ const std::vector<uint8_t> hash = hasher.finish();
+ return QByteArray(reinterpret_cast<const char *>(&hash[0]), hash.size());
+}
+
+QByteArray Updater::parseShasumOutput(const QString &message, const QString &filename) const
+{
+ for (const auto &line : message.splitRef("\n"))
+ {
+ const auto trimmed = line.trimmed();
+ if (trimmed.endsWith(filename))
+ {
+ const int pos = trimmed.indexOf(' ');
+ if (pos != -1)
+ {
+ return QByteArray::fromHex(trimmed.left(pos).toUtf8());
+ }
+ }
+ else if (trimmed.startsWith(filename))
+ {
+ const int pos = trimmed.lastIndexOf(' ');
+ if (pos != -1)
+ {
+ return QByteArray::fromHex(trimmed.right(trimmed.size() - pos).toUtf8());
+ }
+ }
+ }
+
+ throw std::runtime_error("hash not found");
+}
+
+QString Updater::verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const
+{
+ const std::string messageString = armoredSignedMessage.toStdString();
+ const openpgp::message_armored signedMessage(messageString);
+ signer = verifySignature(signedMessage, openpgp::signature_rsa::from_armored(messageString));
+
+ const epee::span<const uint8_t> message = signedMessage;
+ return QString(QByteArray(reinterpret_cast<const char *>(&message[0]), message.size()));
+}
+
+QString Updater::verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const
+{
+ for (const auto &maintainer : m_maintainers)
+ {
+ if (signature.verify(data, maintainer))
+ {
+ return QString::fromStdString(maintainer.user_id());
+ }
+ }
+
+ throw std::runtime_error("not signed by a maintainer");
+}
diff --git a/src/qt/updater.h b/src/qt/updater.h
new file mode 100644
index 00000000..bc06d0c0
--- /dev/null
+++ b/src/qt/updater.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2020, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <QPair>
+
+#include <openpgp/openpgp.h>
+
+class Updater
+{
+public:
+ Updater();
+
+ QPair<QString, QString> verifySignaturesAndHashSum(
+ const QByteArray &armoredSignedHashes,
+ const QByteArray &secondDetachedSignature,
+ const QString &binaryFilename,
+ const void *binaryData,
+ size_t binarySize) const;
+
+private:
+ QByteArray getHash(const void *data, size_t size) 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;
+
+private:
+ std::vector<openpgp::public_key_rsa> m_maintainers;
+};
diff --git a/src/qt/utils.cpp b/src/qt/utils.cpp
index bcf69917..258400c3 100644
--- a/src/qt/utils.cpp
+++ b/src/qt/utils.cpp
@@ -37,6 +37,24 @@ bool fileExists(QString path) {
return check_file.exists() && check_file.isFile();
}
+QByteArray fileGetContents(QString path)
+{
+ QFile file(path);
+ if (!file.open(QFile::ReadOnly))
+ {
+ throw std::runtime_error(QString("failed to open %1").arg(path).toStdString());
+ }
+
+ QByteArray data;
+ data.resize(file.size());
+ if (file.read(data.data(), data.size()) != data.size())
+ {
+ throw std::runtime_error(QString("failed to read %1").arg(path).toStdString());
+ }
+
+ return data;
+}
+
QByteArray fileOpen(QString path) {
QFile file(path);
if(!file.open(QFile::ReadOnly | QFile::Text))
diff --git a/src/qt/utils.h b/src/qt/utils.h
index 17fb44d9..aace5d00 100644
--- a/src/qt/utils.h
+++ b/src/qt/utils.h
@@ -34,6 +34,7 @@
#include <QApplication>
bool fileExists(QString path);
+QByteArray fileGetContents(QString path);
QByteArray fileOpen(QString path);
bool fileWrite(QString path, QString data);
QString getAccountName();