mirror of
https://github.com/monero-project/monero-gui
synced 2024-12-01 08:11:54 +01:00
Merge pull request #2824
5f27a45
'--verify-update', shasum support, OpenPGP signatures verification (xiphon)
This commit is contained in:
commit
2156a6533b
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- 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
|
||||
run: export PATH=$PATH:/usr/local/opt/qt/bin && ./build.sh
|
||||
- name: test qml
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
- 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
|
||||
- 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
|
||||
run: ./build.sh
|
||||
- name: test qml
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
- name: update pacman
|
||||
run: msys2do pacman -Syu --noconfirm
|
||||
- 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
|
||||
run: msys2do ./build.sh release
|
||||
- name: test qml
|
||||
|
@ -104,7 +104,7 @@ The following instructions will fetch Qt from your distribution's repositories i
|
||||
|
||||
- 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
|
||||
|
||||
@ -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:
|
||||
|
||||
`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:
|
||||
|
||||
@ -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
|
||||
|
||||
```
|
||||
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`
|
||||
|
@ -97,6 +97,7 @@ SOURCES += src/main/main.cpp \
|
||||
src/libwalletqt/TransactionInfo.cpp \
|
||||
src/libwalletqt/QRCodeImageProvider.cpp \
|
||||
src/main/oshelper.cpp \
|
||||
src/openpgp/openpgp.cpp \
|
||||
src/TranslationManager.cpp \
|
||||
src/model/TransactionHistoryModel.cpp \
|
||||
src/model/TransactionHistorySortFilterModel.cpp \
|
||||
@ -118,6 +119,7 @@ SOURCES += src/main/main.cpp \
|
||||
src/qt/ipc.cpp \
|
||||
src/qt/KeysFiles.cpp \
|
||||
src/qt/network.cpp \
|
||||
src/qt/updater.cpp \
|
||||
src/qt/utils.cpp \
|
||||
src/qt/MoneroSettings.cpp \
|
||||
src/qt/TailsOS.cpp
|
||||
@ -159,6 +161,8 @@ ios:arm64 {
|
||||
}
|
||||
|
||||
LIBS_COMMON = \
|
||||
-lgcrypt \
|
||||
-lgpg-error \
|
||||
-lwallet_merged \
|
||||
-llmdb \
|
||||
-lepee \
|
||||
@ -392,6 +396,20 @@ macx {
|
||||
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
|
||||
OBJECTIVE_SOURCES += src/qt/macoshelper.mm
|
||||
LIBS+= -Wl,-dead_strip
|
||||
|
3
qml.qrc
3
qml.qrc
@ -10,6 +10,9 @@
|
||||
<file>images/whatIsIcon@2x.png</file>
|
||||
<file>images/lockIcon.png</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/Transfer.qml</file>
|
||||
<file>pages/History.qml</file>
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
18
src/openpgp/CMakeLists.txt
Normal file
18
src/openpgp/CMakeLists.txt
Normal 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
107
src/openpgp/hash.h
Normal 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
78
src/openpgp/mpi.h
Normal 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
380
src/openpgp/openpgp.cpp
Normal 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
122
src/openpgp/openpgp.h
Normal 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
|
76
src/openpgp/packet_stream.h
Normal file
76
src/openpgp/packet_stream.h
Normal 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
|
78
src/openpgp/s_expression.h
Normal file
78
src/openpgp/s_expression.h
Normal 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
171
src/openpgp/serialization.h
Normal 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
130
src/qt/updater.cpp
Normal 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
55
src/qt/updater.h
Normal 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;
|
||||
};
|
@ -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))
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user