Merge pull request #2824

5f27a45 '--verify-update', shasum support, OpenPGP signatures verification (xiphon)
This commit is contained in:
luigi1111 2020-04-13 15:43:24 -05:00
commit 2156a6533b
No known key found for this signature in database
GPG key ID: F4ACA0183641E010
18 changed files with 1298 additions and 6 deletions

View file

@ -10,7 +10,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: update brew and install dependencies - name: update brew and install dependencies
run: brew update && brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf qt5 run: brew update && brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf qt5 libgcrypt
- name: build - name: build
run: export PATH=$PATH:/usr/local/opt/qt/bin && ./build.sh run: export PATH=$PATH:/usr/local/opt/qt/bin && ./build.sh
- name: test qml - name: test qml
@ -34,7 +34,7 @@ jobs:
- name: install monero dependencies - name: install monero dependencies
run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev
- name: install monero gui dependencies - name: install monero gui dependencies
run: sudo apt -y install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev xvfb run: sudo apt -y install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev libgcrypt20-dev xvfb
- name: build - name: build
run: ./build.sh run: ./build.sh
- name: test qml - name: test qml
@ -50,7 +50,7 @@ jobs:
- name: update pacman - name: update pacman
run: msys2do pacman -Syu --noconfirm run: msys2do pacman -Syu --noconfirm
- name: install monero dependencies - name: install monero dependencies
run: msys2do pacman -S --noconfirm mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb git mingw-w64-x86_64-qt5 run: msys2do pacman -S --noconfirm mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb git mingw-w64-x86_64-qt5 mingw-w64-x86_64-libgcrypt
- name: build - name: build
run: msys2do ./build.sh release run: msys2do ./build.sh release
- name: test qml - name: test qml

View file

@ -104,7 +104,7 @@ The following instructions will fetch Qt from your distribution's repositories i
- For Ubuntu 17.10+ - For Ubuntu 17.10+
`sudo apt install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev` `sudo apt install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-xmllistmodel qml-module-qt-labs-settings qml-module-qt-labs-folderlistmodel qttools5-dev-tools qml-module-qtquick-templates2 libqt5svg5-dev libgcrypt20-dev`
- For Gentoo - For Gentoo
@ -144,7 +144,7 @@ The executable can be found in the build/release/bin folder.
3. Install [monero](https://github.com/monero-project/monero) dependencies: 3. Install [monero](https://github.com/monero-project/monero) dependencies:
`brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf` `brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf libgcrypt`
4. Install Qt: 4. Install Qt:
@ -180,7 +180,7 @@ The Monero GUI on Windows is 64 bits only; 32-bit Windows GUI builds are not off
3. Install MSYS2 packages for Monero dependencies; the needed 64-bit packages have `x86_64` in their names 3. Install MSYS2 packages for Monero dependencies; the needed 64-bit packages have `x86_64` in their names
``` ```
pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb mingw-w64-x86_64-libgcrypt
``` ```
Optional : To build the flag `WITH_SCANNER` Optional : To build the flag `WITH_SCANNER`

View file

@ -97,6 +97,7 @@ SOURCES += src/main/main.cpp \
src/libwalletqt/TransactionInfo.cpp \ src/libwalletqt/TransactionInfo.cpp \
src/libwalletqt/QRCodeImageProvider.cpp \ src/libwalletqt/QRCodeImageProvider.cpp \
src/main/oshelper.cpp \ src/main/oshelper.cpp \
src/openpgp/openpgp.cpp \
src/TranslationManager.cpp \ src/TranslationManager.cpp \
src/model/TransactionHistoryModel.cpp \ src/model/TransactionHistoryModel.cpp \
src/model/TransactionHistorySortFilterModel.cpp \ src/model/TransactionHistorySortFilterModel.cpp \
@ -118,6 +119,7 @@ SOURCES += src/main/main.cpp \
src/qt/ipc.cpp \ src/qt/ipc.cpp \
src/qt/KeysFiles.cpp \ src/qt/KeysFiles.cpp \
src/qt/network.cpp \ src/qt/network.cpp \
src/qt/updater.cpp \
src/qt/utils.cpp \ src/qt/utils.cpp \
src/qt/MoneroSettings.cpp \ src/qt/MoneroSettings.cpp \
src/qt/TailsOS.cpp src/qt/TailsOS.cpp
@ -159,6 +161,8 @@ ios:arm64 {
} }
LIBS_COMMON = \ LIBS_COMMON = \
-lgcrypt \
-lgpg-error \
-lwallet_merged \ -lwallet_merged \
-llmdb \ -llmdb \
-lepee \ -lepee \
@ -392,6 +396,20 @@ macx {
INCLUDEPATH += /usr/local/include INCLUDEPATH += /usr/local/include
} }
GCRYPT_DIR = $$system(brew --prefix libgcrypt, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$GCRYPT_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
GPGP_ERROR_DIR = $$system(brew --prefix libgpg-error, lines, EXIT_CODE)
equals(EXIT_CODE, 0) {
INCLUDEPATH += $$GPGP_ERROR_DIR/include
} else {
INCLUDEPATH += /usr/local/include
}
QT += macextras QT += macextras
OBJECTIVE_SOURCES += src/qt/macoshelper.mm OBJECTIVE_SOURCES += src/qt/macoshelper.mm
LIBS+= -Wl,-dead_strip LIBS+= -Wl,-dead_strip

View file

@ -10,6 +10,9 @@
<file>images/whatIsIcon@2x.png</file> <file>images/whatIsIcon@2x.png</file>
<file>images/lockIcon.png</file> <file>images/lockIcon.png</file>
<file>components/MenuButton.qml</file> <file>components/MenuButton.qml</file>
<file>monero/utils/gpg_keys/binaryfate.asc</file>
<file>monero/utils/gpg_keys/fluffypony.asc</file>
<file>monero/utils/gpg_keys/luigi1111.asc</file>
<file>pages/Account.qml</file> <file>pages/Account.qml</file>
<file>pages/Transfer.qml</file> <file>pages/Transfer.qml</file>
<file>pages/History.qml</file> <file>pages/History.qml</file>

View file

@ -3,6 +3,7 @@ add_subdirectory(QR-Code-scanner)
add_subdirectory(daemon) add_subdirectory(daemon)
add_subdirectory(libwalletqt) add_subdirectory(libwalletqt)
add_subdirectory(model) add_subdirectory(model)
add_subdirectory(openpgp)
add_subdirectory(zxcvbn-c) add_subdirectory(zxcvbn-c)
qt5_add_resources(RESOURCES ../qml.qrc) qt5_add_resources(RESOURCES ../qml.qrc)
@ -149,6 +150,7 @@ target_link_libraries(monero-gui
${QT5_LIBRARIES} ${QT5_LIBRARIES}
${EXTRA_LIBRARIES} ${EXTRA_LIBRARIES}
${ICU_LIBRARIES} ${ICU_LIBRARIES}
openpgp
) )
if(WITH_SCANNER) if(WITH_SCANNER)

View file

@ -64,6 +64,7 @@
#include "qt/downloader.h" #include "qt/downloader.h"
#include "qt/ipc.h" #include "qt/ipc.h"
#include "qt/network.h" #include "qt/network.h"
#include "qt/updater.h"
#include "qt/utils.h" #include "qt/utils.h"
#include "qt/TailsOS.h" #include "qt/TailsOS.h"
#include "qt/KeysFiles.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", "Log to specified file"),
QCoreApplication::translate("main", "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"); QCommandLineOption testQmlOption("test-qml");
testQmlOption.setFlags(QCommandLineOption::HiddenFromHelp); testQmlOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(logPathOption); parser.addOption(logPathOption);
@ -245,6 +254,32 @@ int main(int argc, char *argv[])
} }
qWarning().noquote() << "app startd" << "(log: " + logPath + ")"; 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 // Desktop entry
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
registerXdgMime(app); registerXdgMime(app);

View file

@ -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})

107
src/openpgp/hash.h Normal file
View file

@ -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;
};
}

78
src/openpgp/mpi.h Normal file
View file

@ -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

380
src/openpgp/openpgp.cpp Normal file
View file

@ -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

122
src/openpgp/openpgp.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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

171
src/openpgp/serialization.h Normal file
View file

@ -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

130
src/qt/updater.cpp Normal file
View file

@ -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");
}

55
src/qt/updater.h Normal file
View file

@ -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;
};

View file

@ -37,6 +37,24 @@ bool fileExists(QString path) {
return check_file.exists() && check_file.isFile(); 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) { QByteArray fileOpen(QString path) {
QFile file(path); QFile file(path);
if(!file.open(QFile::ReadOnly | QFile::Text)) if(!file.open(QFile::ReadOnly | QFile::Text))

View file

@ -34,6 +34,7 @@
#include <QApplication> #include <QApplication>
bool fileExists(QString path); bool fileExists(QString path);
QByteArray fileGetContents(QString path);
QByteArray fileOpen(QString path); QByteArray fileOpen(QString path);
bool fileWrite(QString path, QString data); bool fileWrite(QString path, QString data);
QString getAccountName(); QString getAccountName();