mirror of
https://github.com/transmission/transmission
synced 2024-12-21 23:32:35 +00:00
refactor: remove tr_dh code (#3443)
Refactor the MSE handshake Diffie-Hellman key code.
This commit is contained in:
parent
c3db52e310
commit
2bcab6be7e
28 changed files with 517 additions and 1196 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -47,3 +47,6 @@
|
|||
[submodule "third-party/fast_float"]
|
||||
path = third-party/fast_float
|
||||
url = https://github.com/transmission/fast_float
|
||||
[submodule "third-party/wide-integer"]
|
||||
path = third-party/wide-integer
|
||||
url = https://github.com/transmission/wide-integer
|
||||
|
|
|
@ -153,6 +153,7 @@ find_package(Fmt)
|
|||
add_definitions(-DFMT_HEADER_ONLY -DFMT_EXCEPTIONS=0)
|
||||
include_directories(SYSTEM ${LIBFMT_INCLUDE_DIRS})
|
||||
|
||||
find_package(WideInteger)
|
||||
find_package(FastFloat)
|
||||
find_package(UtfCpp)
|
||||
find_package(Threads)
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
4D118E1A08CB46B20033958F /* PrefsController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4D118E1908CB46B20033958F /* PrefsController.mm */; };
|
||||
4D1838DD09DEC0E80047D688 /* libtransmission.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D18389709DEC0030047D688 /* libtransmission.a */; };
|
||||
4D364DA0091FBB2C00377D12 /* TorrentTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4D364D9F091FBB2C00377D12 /* TorrentTableView.mm */; };
|
||||
4D36BA6F0CA2F00800A63CA5 /* crypto.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D36BA600CA2F00800A63CA5 /* crypto.cc */; };
|
||||
4D36BA700CA2F00800A63CA5 /* crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D36BA610CA2F00800A63CA5 /* crypto.h */; };
|
||||
4D36BA6F0CA2F00800A63CA5 /* peer-mse.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D36BA600CA2F00800A63CA5 /* peer-mse.cc */; };
|
||||
4D36BA700CA2F00800A63CA5 /* peer-mse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D36BA610CA2F00800A63CA5 /* peer-mse.h */; };
|
||||
4D36BA720CA2F00800A63CA5 /* handshake.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D36BA630CA2F00800A63CA5 /* handshake.cc */; };
|
||||
4D36BA730CA2F00800A63CA5 /* handshake.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D36BA640CA2F00800A63CA5 /* handshake.h */; };
|
||||
4D36BA740CA2F00800A63CA5 /* peer-io.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D36BA650CA2F00800A63CA5 /* peer-io.cc */; };
|
||||
|
@ -631,8 +631,8 @@
|
|||
4D18389709DEC0030047D688 /* libtransmission.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libtransmission.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4D364D9E091FBB2C00377D12 /* TorrentTableView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TorrentTableView.h; sourceTree = "<group>"; };
|
||||
4D364D9F091FBB2C00377D12 /* TorrentTableView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentTableView.mm; sourceTree = "<group>"; };
|
||||
4D36BA600CA2F00800A63CA5 /* crypto.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = crypto.cc; sourceTree = "<group>"; };
|
||||
4D36BA610CA2F00800A63CA5 /* crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = "<group>"; };
|
||||
4D36BA600CA2F00800A63CA5 /* peer-mse.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = peer-mse.cc; sourceTree = "<group>"; };
|
||||
4D36BA610CA2F00800A63CA5 /* peer-mse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = peer-mse.h; sourceTree = "<group>"; };
|
||||
4D36BA630CA2F00800A63CA5 /* handshake.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = handshake.cc; sourceTree = "<group>"; };
|
||||
4D36BA640CA2F00800A63CA5 /* handshake.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = handshake.h; sourceTree = "<group>"; };
|
||||
4D36BA650CA2F00800A63CA5 /* peer-io.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-io.cc"; sourceTree = "<group>"; };
|
||||
|
@ -1665,8 +1665,8 @@
|
|||
C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */,
|
||||
C1033E051A3279B800EF44D8 /* crypto-utils.cc */,
|
||||
C1033E061A3279B800EF44D8 /* crypto-utils.h */,
|
||||
4D36BA600CA2F00800A63CA5 /* crypto.cc */,
|
||||
4D36BA610CA2F00800A63CA5 /* crypto.h */,
|
||||
4D36BA600CA2F00800A63CA5 /* peer-mse.cc */,
|
||||
4D36BA610CA2F00800A63CA5 /* peer-mse.h */,
|
||||
4D36BA630CA2F00800A63CA5 /* handshake.cc */,
|
||||
4D36BA640CA2F00800A63CA5 /* handshake.h */,
|
||||
4D36BA650CA2F00800A63CA5 /* peer-io.cc */,
|
||||
|
@ -2188,7 +2188,7 @@
|
|||
BEFC1E570C07861A00B0BB3C /* clients.h in Headers */,
|
||||
A2BE9C530C1E4AF7002D16E6 /* makemeta.h in Headers */,
|
||||
A24621410C769D0900088E81 /* trevent.h in Headers */,
|
||||
4D36BA700CA2F00800A63CA5 /* crypto.h in Headers */,
|
||||
4D36BA700CA2F00800A63CA5 /* peer-mse.h in Headers */,
|
||||
C10C644E1D9AF328003C1B4C /* session-id.h in Headers */,
|
||||
4D36BA730CA2F00800A63CA5 /* handshake.h in Headers */,
|
||||
4D36BA750CA2F00800A63CA5 /* peer-io.h in Headers */,
|
||||
|
@ -2938,7 +2938,7 @@
|
|||
A2BE9C520C1E4AF5002D16E6 /* makemeta.cc in Sources */,
|
||||
A24621420C769D0900088E81 /* trevent.cc in Sources */,
|
||||
C11DEA161FCD31C0009E22B9 /* subprocess-posix.cc in Sources */,
|
||||
4D36BA6F0CA2F00800A63CA5 /* crypto.cc in Sources */,
|
||||
4D36BA6F0CA2F00800A63CA5 /* peer-mse.cc in Sources */,
|
||||
4D36BA720CA2F00800A63CA5 /* handshake.cc in Sources */,
|
||||
4D36BA740CA2F00800A63CA5 /* peer-io.cc in Sources */,
|
||||
C1033E071A3279B800EF44D8 /* crypto-utils-fallback.cc in Sources */,
|
||||
|
@ -3592,6 +3592,7 @@
|
|||
"third-party/arc4/src",
|
||||
"third-party/dht",
|
||||
"third-party/fast_float/include",
|
||||
"third-party/wide-integer",
|
||||
"third-party/libb64/include",
|
||||
"third-party/libdeflate",
|
||||
"third-party/libpsl/include",
|
||||
|
@ -3602,6 +3603,8 @@
|
|||
"$(inherited)",
|
||||
"-DWITH_UTP",
|
||||
"-D__TRANSMISSION__",
|
||||
"-DWIDE_INTEGER_DISABLE_FLOAT_INTEROP",
|
||||
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
|
||||
"-DHAVE_FLOCK",
|
||||
"-DHAVE_STRLCPY",
|
||||
"-DHAVE_ICONV",
|
||||
|
@ -3833,6 +3836,7 @@
|
|||
"third-party/arc4/src",
|
||||
"third-party/dht",
|
||||
"third-party/fast_float/include",
|
||||
"third-party/wide-integer",
|
||||
"third-party/libb64/include",
|
||||
"third-party/libdeflate",
|
||||
"third-party/libpsl/include",
|
||||
|
@ -3843,6 +3847,8 @@
|
|||
"$(inherited)",
|
||||
"-DWITH_UTP",
|
||||
"-D__TRANSMISSION__",
|
||||
"-DWIDE_INTEGER_DISABLE_FLOAT_INTEROP",
|
||||
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
|
||||
"-DHAVE_FLOCK",
|
||||
"-DHAVE_STRLCPY",
|
||||
"-DHAVE_ICONV",
|
||||
|
@ -4148,6 +4154,7 @@
|
|||
"third-party/arc4/src",
|
||||
"third-party/dht",
|
||||
"third-party/fast_float/include",
|
||||
"third-party/wide-integer",
|
||||
"third-party/libb64/include",
|
||||
"third-party/libdeflate",
|
||||
"third-party/libpsl/include",
|
||||
|
@ -4158,6 +4165,8 @@
|
|||
"$(inherited)",
|
||||
"-DWITH_UTP",
|
||||
"-D__TRANSMISSION__",
|
||||
"-DWIDE_INTEGER_DISABLE_FLOAT_INTEROP",
|
||||
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
|
||||
"-DHAVE_FLOCK",
|
||||
"-DHAVE_STRLCPY",
|
||||
"-DHAVE_ICONV",
|
||||
|
|
1
cmake/FindWideInteger.cmake
Normal file
1
cmake/FindWideInteger.cmake
Normal file
|
@ -0,0 +1 @@
|
|||
set(WIDE_INTEGER_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third-party/wide-integer)
|
|
@ -23,7 +23,6 @@ set(PROJECT_FILES
|
|||
crypto-utils-openssl.cc
|
||||
crypto-utils-polarssl.cc
|
||||
crypto-utils.cc
|
||||
crypto.cc
|
||||
error.cc
|
||||
file-piece-map.cc
|
||||
file-posix.cc
|
||||
|
@ -41,6 +40,7 @@ set(PROJECT_FILES
|
|||
peer-mgr-active-requests.cc
|
||||
peer-mgr-wishlist.cc
|
||||
peer-mgr.cc
|
||||
peer-mse.cc
|
||||
peer-msgs.cc
|
||||
platform-quota.cc
|
||||
platform.cc
|
||||
|
@ -165,7 +165,6 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||
clients.h
|
||||
completion.h
|
||||
crypto-utils.h
|
||||
crypto.h
|
||||
file-piece-map.h
|
||||
handshake.h
|
||||
history.h
|
||||
|
@ -181,6 +180,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||
peer-mgr-active-requests.h
|
||||
peer-mgr-wishlist.h
|
||||
peer-mgr.h
|
||||
peer-mse.h
|
||||
peer-msgs.h
|
||||
peer-socket.h
|
||||
platform-quota.h
|
||||
|
@ -215,6 +215,8 @@ endif()
|
|||
|
||||
add_definitions(
|
||||
-D__TRANSMISSION__
|
||||
-DWIDE_INTEGER_DISABLE_FLOAT_INTEROP
|
||||
-DWIDE_INTEGER_DISABLE_IOSTREAM
|
||||
"-DPACKAGE_DATA_DIR=\"${CMAKE_INSTALL_FULL_DATAROOTDIR}\""
|
||||
${NATPMP_DEFINITIONS}
|
||||
${MINIUPNPC_DEFINITIONS}
|
||||
|
@ -256,6 +258,7 @@ include_directories(
|
|||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
${WIDE_INTEGER_INCLUDE_DIRS}
|
||||
${UTFCPP_INCLUDE_DIRS}
|
||||
${DEFLATE_INCLUDE_DIRS}
|
||||
${FAST_FLOAT_INCLUDE_DIRS}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef HAVE_COMMONCRYPTO_COMMONBIGNUM_H
|
||||
#include <CommonCrypto/CommonBigNum.h>
|
||||
#endif
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#include <CommonCrypto/CommonRandom.h>
|
||||
|
||||
|
@ -21,7 +18,6 @@
|
|||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TR_CRYPTO_DH_SECRET_FALLBACK
|
||||
#define TR_CRYPTO_X509_FALLBACK
|
||||
#include "crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
|
||||
|
||||
|
@ -29,29 +25,6 @@
|
|||
****
|
||||
***/
|
||||
|
||||
#ifndef HAVE_COMMONCRYPTO_COMMONBIGNUM_H
|
||||
|
||||
using CCBigNumRef = struct _CCBigNumRef*;
|
||||
using CCBigNumConstRef = struct _CCBigNumRef const*;
|
||||
using CCStatus = CCCryptorStatus;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
CCBigNumRef CCBigNumFromData(CCStatus* status, void const* s, size_t len);
|
||||
CCBigNumRef CCCreateBigNum(CCStatus* status);
|
||||
CCBigNumRef CCBigNumCreateRandom(CCStatus* status, int bits, int top, int bottom);
|
||||
void CCBigNumFree(CCBigNumRef bn);
|
||||
CCStatus CCBigNumModExp(CCBigNumRef result, CCBigNumConstRef a, CCBigNumConstRef power, CCBigNumConstRef modulus);
|
||||
uint32_t CCBigNumByteCount(CCBigNumConstRef bn);
|
||||
size_t CCBigNumToData(CCStatus* status, CCBigNumConstRef bn, void* to);
|
||||
}
|
||||
|
||||
#endif /* !HAVE_COMMONCRYPTO_COMMONBIGNUM_H */
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -218,150 +191,6 @@ std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
|
|||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct CCBigNumDeleter
|
||||
{
|
||||
void operator()(CCBigNumRef bn) const noexcept
|
||||
{
|
||||
if (bn != nullptr)
|
||||
{
|
||||
CCBigNumFree(bn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using CCBigNumPtr = std::unique_ptr<std::remove_pointer_t<CCBigNumRef>, CCBigNumDeleter>;
|
||||
|
||||
struct tr_dh_ctx
|
||||
{
|
||||
CCBigNumPtr p;
|
||||
CCBigNumPtr g;
|
||||
CCBigNumPtr private_key;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
tr_dh_ctx_t tr_dh_new(
|
||||
uint8_t const* prime_num,
|
||||
size_t prime_num_length,
|
||||
uint8_t const* generator_num,
|
||||
size_t generator_num_length)
|
||||
{
|
||||
TR_ASSERT(prime_num != nullptr);
|
||||
TR_ASSERT(generator_num != nullptr);
|
||||
|
||||
auto handle = std::make_unique<tr_dh_ctx>();
|
||||
auto status = CCStatus{};
|
||||
|
||||
handle->p = CCBigNumPtr(CCBigNumFromData(&status, prime_num, prime_num_length));
|
||||
if (!check_pointer(handle->p.get(), &status))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
handle->g = CCBigNumPtr(CCBigNumFromData(&status, generator_num, generator_num_length));
|
||||
if (!check_pointer(handle->g.get(), &status))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return handle.release();
|
||||
}
|
||||
|
||||
void tr_dh_free(tr_dh_ctx_t handle)
|
||||
{
|
||||
delete static_cast<tr_dh_ctx*>(handle);
|
||||
}
|
||||
|
||||
bool tr_dh_make_key(tr_dh_ctx_t raw_handle, size_t private_key_length, uint8_t* public_key, size_t* public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(public_key != nullptr);
|
||||
|
||||
auto& handle = *static_cast<tr_dh_ctx*>(raw_handle);
|
||||
auto status = CCStatus{};
|
||||
|
||||
handle.private_key = CCBigNumPtr(CCBigNumCreateRandom(&status, private_key_length * 8, private_key_length * 8, 0));
|
||||
if (!check_pointer(handle.private_key.get(), &status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const my_public_key = CCBigNumPtr(CCCreateBigNum(&status));
|
||||
if (!check_pointer(my_public_key.get(), &status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!check_result(CCBigNumModExp(my_public_key.get(), handle.g.get(), handle.private_key.get(), handle.p.get())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const my_public_key_length = CCBigNumByteCount(my_public_key.get());
|
||||
CCBigNumToData(&status, my_public_key.get(), public_key);
|
||||
if (!check_result(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const dh_size = CCBigNumByteCount(handle.p.get());
|
||||
tr_dh_align_key(public_key, my_public_key_length, dh_size);
|
||||
|
||||
if (public_key_length != nullptr)
|
||||
{
|
||||
*public_key_length = dh_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tr_dh_secret_t tr_dh_agree(tr_dh_ctx_t raw_handle, uint8_t const* other_public_key, size_t other_public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(other_public_key != nullptr);
|
||||
|
||||
auto const& handle = *static_cast<tr_dh_ctx*>(raw_handle);
|
||||
auto status = CCStatus{};
|
||||
|
||||
auto const other_key = CCBigNumPtr(CCBigNumFromData(&status, other_public_key, other_public_key_length));
|
||||
if (!check_pointer(other_key.get(), &status))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const my_secret_key = CCBigNumPtr(CCCreateBigNum(&status));
|
||||
if (!check_pointer(my_secret_key.get(), &status))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!check_result(CCBigNumModExp(my_secret_key.get(), other_key.get(), handle.private_key.get(), handle.p.get())))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const dh_size = CCBigNumByteCount(handle.p.get());
|
||||
auto ret = std::unique_ptr<tr_dh_secret, decltype(&tr_free)>(tr_dh_secret_new(dh_size), &tr_free);
|
||||
|
||||
auto const my_secret_key_length = CCBigNumByteCount(my_secret_key.get());
|
||||
CCBigNumToData(&status, my_secret_key.get(), ret->key);
|
||||
if (!check_result(status))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tr_dh_secret_align(ret.get(), my_secret_key_length);
|
||||
|
||||
return ret.release();
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
bool tr_rand_buffer(void* buffer, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#endif
|
||||
|
||||
#include API_HEADER(options.h)
|
||||
#include API_HEADER_CRYPT(dh.h)
|
||||
#include API_HEADER_CRYPT(error-crypt.h)
|
||||
#include API_HEADER_CRYPT(random.h)
|
||||
#include API_HEADER_CRYPT(sha.h)
|
||||
|
@ -33,18 +32,9 @@
|
|||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TR_CRYPTO_DH_SECRET_FALLBACK
|
||||
#define TR_CRYPTO_X509_FALLBACK
|
||||
#include "crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
|
||||
|
||||
struct tr_dh_ctx
|
||||
{
|
||||
DhKey dh;
|
||||
word32 key_length;
|
||||
uint8_t* private_key;
|
||||
word32 private_key_length;
|
||||
};
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -206,117 +196,6 @@ std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
|
|||
****
|
||||
***/
|
||||
|
||||
tr_dh_ctx_t tr_dh_new(
|
||||
uint8_t const* prime_num,
|
||||
size_t prime_num_length,
|
||||
uint8_t const* generator_num,
|
||||
size_t generator_num_length)
|
||||
{
|
||||
TR_ASSERT(prime_num != nullptr);
|
||||
TR_ASSERT(generator_num != nullptr);
|
||||
|
||||
struct tr_dh_ctx* handle = tr_new0(struct tr_dh_ctx, 1);
|
||||
|
||||
API(InitDhKey)(&handle->dh);
|
||||
|
||||
if (!check_result(API(DhSetKey)(&handle->dh, prime_num, prime_num_length, generator_num, generator_num_length)))
|
||||
{
|
||||
tr_free(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
handle->key_length = prime_num_length;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void tr_dh_free(tr_dh_ctx_t raw_handle)
|
||||
{
|
||||
auto* handle = static_cast<struct tr_dh_ctx*>(raw_handle);
|
||||
|
||||
if (handle == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
API(FreeDhKey)(&handle->dh);
|
||||
tr_free(handle->private_key);
|
||||
tr_free(handle);
|
||||
}
|
||||
|
||||
bool tr_dh_make_key(tr_dh_ctx_t raw_handle, size_t /*private_key_length*/, uint8_t* public_key, size_t* public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(public_key != nullptr);
|
||||
|
||||
auto* handle = static_cast<struct tr_dh_ctx*>(raw_handle);
|
||||
|
||||
if (handle->private_key == nullptr)
|
||||
{
|
||||
handle->private_key = static_cast<uint8_t*>(tr_malloc(handle->key_length));
|
||||
}
|
||||
|
||||
auto const lock = std::lock_guard(rng_mutex_);
|
||||
|
||||
auto my_private_key_length = handle->key_length;
|
||||
auto my_public_key_length = static_cast<word32>(*public_key_length);
|
||||
if (!check_result(API(DhGenerateKeyPair)(
|
||||
&handle->dh,
|
||||
get_rng(),
|
||||
handle->private_key,
|
||||
&my_private_key_length,
|
||||
public_key,
|
||||
&my_public_key_length)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tr_dh_align_key(public_key, my_public_key_length, handle->key_length);
|
||||
|
||||
handle->private_key_length = my_private_key_length;
|
||||
|
||||
if (public_key_length != nullptr)
|
||||
{
|
||||
*public_key_length = handle->key_length;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tr_dh_secret_t tr_dh_agree(tr_dh_ctx_t raw_handle, uint8_t const* other_public_key, size_t other_public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(other_public_key != nullptr);
|
||||
|
||||
auto* handle = static_cast<struct tr_dh_ctx*>(raw_handle);
|
||||
|
||||
tr_dh_secret* ret = tr_dh_secret_new(handle->key_length);
|
||||
|
||||
auto my_secret_key_length = word32{};
|
||||
if (check_result(API(DhAgree)(
|
||||
&handle->dh,
|
||||
ret->key,
|
||||
&my_secret_key_length,
|
||||
handle->private_key,
|
||||
handle->private_key_length,
|
||||
other_public_key,
|
||||
other_public_key_length)))
|
||||
{
|
||||
tr_dh_secret_align(ret, my_secret_key_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_dh_secret_free(ret);
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
bool tr_rand_buffer(void* buffer, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
|
|
|
@ -7,66 +7,15 @@
|
|||
implement missing (or duplicate) functionality without exposing internal
|
||||
details in header files. */
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h"
|
||||
#include "tr-assert.h"
|
||||
#include "tr-macros.h"
|
||||
#include "utils.h"
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
#ifdef TR_CRYPTO_DH_SECRET_FALLBACK
|
||||
|
||||
/* Most Diffie-Hellman backends handle secret key in the very same way: by
|
||||
manually allocating memory for it and storing the value in plain form. */
|
||||
|
||||
struct tr_dh_secret
|
||||
{
|
||||
size_t key_length;
|
||||
uint8_t key[];
|
||||
};
|
||||
|
||||
static struct tr_dh_secret* tr_dh_secret_new(size_t key_length)
|
||||
{
|
||||
auto* handle = static_cast<struct tr_dh_secret*>(tr_malloc(sizeof(struct tr_dh_secret) + key_length));
|
||||
handle->key_length = key_length;
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void tr_dh_secret_align(struct tr_dh_secret* handle, size_t current_key_length)
|
||||
{
|
||||
tr_dh_align_key(handle->key, current_key_length, handle->key_length);
|
||||
}
|
||||
|
||||
std::optional<tr_sha1_digest_t> tr_dh_secret_derive(
|
||||
tr_dh_secret_t raw_handle,
|
||||
void const* prepend_data,
|
||||
size_t prepend_data_size,
|
||||
void const* append_data,
|
||||
size_t append_data_size)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
|
||||
auto const* handle = static_cast<struct tr_dh_secret*>(raw_handle);
|
||||
|
||||
return tr_sha1(
|
||||
std::string_view{ static_cast<char const*>(prepend_data), prepend_data_size },
|
||||
std::string_view{ reinterpret_cast<char const*>(handle->key), handle->key_length },
|
||||
std::string_view{ static_cast<char const*>(append_data), append_data_size });
|
||||
}
|
||||
|
||||
void tr_dh_secret_free(tr_dh_secret_t handle)
|
||||
{
|
||||
tr_free(handle);
|
||||
}
|
||||
|
||||
#endif /* TR_CRYPTO_DH_SECRET_FALLBACK */
|
||||
|
||||
#ifdef TR_CRYPTO_X509_FALLBACK
|
||||
|
||||
tr_x509_store_t tr_ssl_get_x509_store(tr_ssl_ctx_t /*handle*/)
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/opensslv.h>
|
||||
|
@ -21,14 +19,12 @@
|
|||
#include <fmt/core.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h"
|
||||
#include "log.h"
|
||||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TR_CRYPTO_DH_SECRET_FALLBACK
|
||||
#include "crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -88,20 +84,6 @@ static bool check_openssl_result(int result, int expected_result, bool expected_
|
|||
#define check_result(result) check_openssl_result((result), 1, true, __FILE__, __LINE__)
|
||||
#define check_result_neq(result, x_result) check_openssl_result((result), (x_result), false, __FILE__, __LINE__)
|
||||
|
||||
static bool check_openssl_pointer(void const* pointer, char const* file, int line)
|
||||
{
|
||||
bool const ret = pointer != nullptr;
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
log_openssl_error(file, line);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define check_pointer(pointer) check_openssl_pointer((pointer), __FILE__, __LINE__)
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -236,167 +218,6 @@ static void openssl_evp_cipher_context_free(EVP_CIPHER_CTX* handle)
|
|||
****
|
||||
***/
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000 || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000)
|
||||
|
||||
static inline int DH_set0_pqg(DH* dh, BIGNUM* p, BIGNUM* q, BIGNUM* g)
|
||||
{
|
||||
/* If the fields p and g in d are nullptr, the corresponding input
|
||||
* parameters MUST be non-nullptr. q may remain nullptr.
|
||||
*/
|
||||
if ((dh->p == nullptr && p == nullptr) || (dh->g == nullptr && g == nullptr))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (p != nullptr)
|
||||
{
|
||||
BN_free(dh->p);
|
||||
dh->p = p;
|
||||
}
|
||||
|
||||
if (q != nullptr)
|
||||
{
|
||||
BN_free(dh->q);
|
||||
dh->q = q;
|
||||
}
|
||||
|
||||
if (g != nullptr)
|
||||
{
|
||||
BN_free(dh->g);
|
||||
dh->g = g;
|
||||
}
|
||||
|
||||
if (q != nullptr)
|
||||
{
|
||||
dh->length = BN_num_bits(q);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static constexpr int DH_set_length(DH* dh, long length)
|
||||
{
|
||||
dh->length = length;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static constexpr void DH_get0_key(DH const* dh, BIGNUM const** pub_key, BIGNUM const** priv_key)
|
||||
{
|
||||
if (pub_key != nullptr)
|
||||
{
|
||||
*pub_key = dh->pub_key;
|
||||
}
|
||||
|
||||
if (priv_key != nullptr)
|
||||
{
|
||||
*priv_key = dh->priv_key;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
tr_dh_ctx_t tr_dh_new(
|
||||
uint8_t const* prime_num,
|
||||
size_t prime_num_length,
|
||||
uint8_t const* generator_num,
|
||||
size_t generator_num_length)
|
||||
{
|
||||
TR_ASSERT(prime_num != nullptr);
|
||||
TR_ASSERT(generator_num != nullptr);
|
||||
|
||||
DH* handle = DH_new();
|
||||
|
||||
BIGNUM* const p = BN_bin2bn(prime_num, prime_num_length, nullptr);
|
||||
BIGNUM* const g = BN_bin2bn(generator_num, generator_num_length, nullptr);
|
||||
|
||||
if (!check_pointer(p) || !check_pointer(g) || DH_set0_pqg(handle, p, nullptr, g) == 0)
|
||||
{
|
||||
BN_free(p);
|
||||
BN_free(g);
|
||||
DH_free(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void tr_dh_free(tr_dh_ctx_t raw_handle)
|
||||
{
|
||||
auto* handle = static_cast<DH*>(raw_handle);
|
||||
|
||||
if (handle == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DH_free(handle);
|
||||
}
|
||||
|
||||
bool tr_dh_make_key(tr_dh_ctx_t raw_handle, size_t private_key_length, uint8_t* public_key, size_t* public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(public_key != nullptr);
|
||||
|
||||
auto* handle = static_cast<DH*>(raw_handle);
|
||||
|
||||
DH_set_length(handle, private_key_length * 8);
|
||||
|
||||
if (!check_result(DH_generate_key(handle)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BIGNUM const* my_public_key = nullptr;
|
||||
DH_get0_key(handle, &my_public_key, nullptr);
|
||||
|
||||
int const my_public_key_length = BN_bn2bin(my_public_key, public_key);
|
||||
int const dh_size = DH_size(handle);
|
||||
|
||||
tr_dh_align_key(public_key, my_public_key_length, dh_size);
|
||||
|
||||
if (public_key_length != nullptr)
|
||||
{
|
||||
*public_key_length = dh_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tr_dh_secret_t tr_dh_agree(tr_dh_ctx_t raw_handle, uint8_t const* other_public_key, size_t other_public_key_length)
|
||||
{
|
||||
auto* handle = static_cast<DH*>(raw_handle);
|
||||
|
||||
TR_ASSERT(handle != nullptr);
|
||||
TR_ASSERT(other_public_key != nullptr);
|
||||
|
||||
BIGNUM* const other_key = BN_bin2bn(other_public_key, other_public_key_length, nullptr);
|
||||
if (!check_pointer(other_key))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int const dh_size = DH_size(handle);
|
||||
tr_dh_secret* ret = tr_dh_secret_new(dh_size);
|
||||
int const secret_key_length = DH_compute_key(ret->key, other_key, handle);
|
||||
|
||||
if (check_result_neq(secret_key_length, -1))
|
||||
{
|
||||
tr_dh_secret_align(ret, secret_key_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_dh_secret_free(ret);
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
BN_free(other_key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
tr_x509_store_t tr_ssl_get_x509_store(tr_ssl_ctx_t handle)
|
||||
{
|
||||
if (handle == nullptr)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#include API_HEADER(base64.h)
|
||||
#include API_HEADER(ctr_drbg.h)
|
||||
#include API_HEADER(dhm.h)
|
||||
#include API_HEADER(error.h)
|
||||
#include API_HEADER(sha1.h)
|
||||
#include API_HEADER(sha256.h)
|
||||
|
@ -31,7 +30,6 @@
|
|||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define TR_CRYPTO_DH_SECRET_FALLBACK
|
||||
#define TR_CRYPTO_X509_FALLBACK
|
||||
#include "crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
|
||||
|
||||
|
@ -42,7 +40,6 @@
|
|||
using api_ctr_drbg_context = API(ctr_drbg_context);
|
||||
using api_sha1_context = API(sha1_context);
|
||||
using api_sha256_context = API(sha256_context);
|
||||
using api_dhm_context = API(dhm_context);
|
||||
|
||||
static void log_polarssl_error(int error_code, char const* file, int line)
|
||||
{
|
||||
|
@ -228,100 +225,6 @@ std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
|
|||
****
|
||||
***/
|
||||
|
||||
tr_dh_ctx_t tr_dh_new(
|
||||
uint8_t const* prime_num,
|
||||
size_t prime_num_length,
|
||||
uint8_t const* generator_num,
|
||||
size_t generator_num_length)
|
||||
{
|
||||
TR_ASSERT(prime_num != nullptr);
|
||||
TR_ASSERT(generator_num != nullptr);
|
||||
|
||||
api_dhm_context* handle = tr_new0(api_dhm_context, 1);
|
||||
|
||||
#if API_VERSION_NUMBER >= 0x01030800
|
||||
API(dhm_init)(handle);
|
||||
#endif
|
||||
|
||||
if (!check_result(API(mpi_read_binary)(&handle->P, prime_num, prime_num_length)) ||
|
||||
!check_result(API(mpi_read_binary)(&handle->G, generator_num, generator_num_length)))
|
||||
{
|
||||
API(dhm_free)(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
handle->len = prime_num_length;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void tr_dh_free(tr_dh_ctx_t raw_handle)
|
||||
{
|
||||
auto* handle = static_cast<api_dhm_context*>(raw_handle);
|
||||
|
||||
if (handle == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
API(dhm_free)(handle);
|
||||
}
|
||||
|
||||
bool tr_dh_make_key(tr_dh_ctx_t raw_handle, size_t private_key_length, uint8_t* public_key, size_t* public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(public_key != nullptr);
|
||||
|
||||
auto* handle = static_cast<api_dhm_context*>(raw_handle);
|
||||
|
||||
if (public_key_length != nullptr)
|
||||
{
|
||||
*public_key_length = handle->len;
|
||||
}
|
||||
|
||||
return check_result(API(dhm_make_public)(handle, private_key_length, public_key, handle->len, my_rand, nullptr));
|
||||
}
|
||||
|
||||
tr_dh_secret_t tr_dh_agree(tr_dh_ctx_t raw_handle, uint8_t const* other_public_key, size_t other_public_key_length)
|
||||
{
|
||||
TR_ASSERT(raw_handle != nullptr);
|
||||
TR_ASSERT(other_public_key != nullptr);
|
||||
|
||||
auto* handle = static_cast<api_dhm_context*>(raw_handle);
|
||||
|
||||
if (!check_result(API(dhm_read_public)(handle, other_public_key, other_public_key_length)))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tr_dh_secret* const ret = tr_dh_secret_new(handle->len);
|
||||
|
||||
size_t secret_key_length = handle->len;
|
||||
|
||||
#if API_VERSION_NUMBER >= 0x02000000
|
||||
|
||||
if (!check_result(API(dhm_calc_secret)(handle, ret->key, secret_key_length, &secret_key_length, my_rand, nullptr)))
|
||||
#elif API_VERSION_NUMBER >= 0x01030000
|
||||
|
||||
if (!check_result(API(dhm_calc_secret)(handle, ret->key, &secret_key_length, my_rand, nullptr)))
|
||||
#else
|
||||
|
||||
if (!check_result(API(dhm_calc_secret)(handle, ret->key, &secret_key_length)))
|
||||
#endif
|
||||
{
|
||||
tr_dh_secret_free(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tr_dh_secret_align(ret, secret_key_length);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
bool tr_rand_buffer(void* buffer, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
|
|
|
@ -33,25 +33,6 @@ using namespace std::literals;
|
|||
****
|
||||
***/
|
||||
|
||||
void tr_dh_align_key(uint8_t* key_buffer, size_t key_size, size_t buffer_size)
|
||||
{
|
||||
TR_ASSERT(key_size <= buffer_size);
|
||||
|
||||
/* DH can generate key sizes that are smaller than the size of
|
||||
key buffer with exponentially decreasing probability, in which case
|
||||
the msb's of key buffer need to be zeroed appropriately. */
|
||||
if (key_size < buffer_size)
|
||||
{
|
||||
size_t const offset = buffer_size - key_size;
|
||||
memmove(key_buffer + offset, key_buffer, key_size);
|
||||
memset(key_buffer, 0, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
int tr_rand_int(int upper_bound)
|
||||
{
|
||||
TR_ASSERT(upper_bound > 0);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#ifndef TR_CRYPTO_UTILS_H
|
||||
#define TR_CRYPTO_UTILS_H
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
@ -23,10 +22,6 @@
|
|||
using tr_sha1_ctx_t = void*;
|
||||
/** @brief Opaque SHA256 context type. */
|
||||
using tr_sha256_ctx_t = void*;
|
||||
/** @brief Opaque DH context type. */
|
||||
using tr_dh_ctx_t = void*;
|
||||
/** @brief Opaque DH secret key type. */
|
||||
using tr_dh_secret_t = void*;
|
||||
/** @brief Opaque SSL context type. */
|
||||
using tr_ssl_ctx_t = void*;
|
||||
/** @brief Opaque X509 certificate store type. */
|
||||
|
@ -110,51 +105,6 @@ std::optional<tr_sha256_digest_t> tr_sha256(T... args)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate and initialize new Diffie-Hellman (DH) key exchange context.
|
||||
*/
|
||||
tr_dh_ctx_t tr_dh_new(
|
||||
uint8_t const* prime_num,
|
||||
size_t prime_num_length,
|
||||
uint8_t const* generator_num,
|
||||
size_t generator_num_length);
|
||||
|
||||
/**
|
||||
* @brief Free DH key exchange context.
|
||||
*/
|
||||
void tr_dh_free(tr_dh_ctx_t handle);
|
||||
|
||||
/**
|
||||
* @brief Generate private and public DH keys, export public key.
|
||||
*/
|
||||
bool tr_dh_make_key(tr_dh_ctx_t handle, size_t private_key_length, uint8_t* public_key, size_t* public_key_length);
|
||||
|
||||
/**
|
||||
* @brief Perform DH key exchange, generate secret key.
|
||||
*/
|
||||
tr_dh_secret_t tr_dh_agree(tr_dh_ctx_t handle, uint8_t const* other_public_key, size_t other_public_key_length);
|
||||
|
||||
/**
|
||||
* @brief Calculate SHA1 hash of DH secret key, prepending and/or appending
|
||||
* given data to the key during calculation.
|
||||
*/
|
||||
std::optional<tr_sha1_digest_t> tr_dh_secret_derive(
|
||||
tr_dh_secret_t handle,
|
||||
void const* prepend_data,
|
||||
size_t prepend_data_size,
|
||||
void const* append_data,
|
||||
size_t append_data_size);
|
||||
|
||||
/**
|
||||
* @brief Free DH secret key returned by @ref tr_dh_agree.
|
||||
*/
|
||||
void tr_dh_secret_free(tr_dh_secret_t handle);
|
||||
|
||||
/**
|
||||
* @brief Align DH key (big-endian number) to required length (internal, do not use).
|
||||
*/
|
||||
void tr_dh_align_key(uint8_t* key_buffer, size_t key_size, size_t buffer_size);
|
||||
|
||||
/**
|
||||
* @brief Get X509 certificate store from SSL context.
|
||||
*/
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
// This file Copyright © 2007-2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <cstring> /* memcpy(), memmove(), memset() */
|
||||
|
||||
#include <arc4.h>
|
||||
|
||||
#include "transmission.h"
|
||||
#include "crypto.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
///
|
||||
|
||||
tr_crypto::tr_crypto(tr_sha1_digest_t const* torrent_hash, bool is_incoming)
|
||||
: is_incoming_{ is_incoming }
|
||||
{
|
||||
if (torrent_hash != nullptr)
|
||||
{
|
||||
this->torrent_hash_ = *torrent_hash;
|
||||
}
|
||||
}
|
||||
|
||||
tr_crypto::~tr_crypto()
|
||||
{
|
||||
tr_dh_secret_free(my_secret_);
|
||||
tr_dh_free(dh_);
|
||||
tr_free(enc_key_);
|
||||
tr_free(dec_key_);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
auto constexpr PrimeLen = size_t{ 96 };
|
||||
auto constexpr DhPrivkeyLen = size_t{ 20 };
|
||||
|
||||
uint8_t constexpr dh_P[PrimeLen] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, //
|
||||
0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, //
|
||||
0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, //
|
||||
0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, //
|
||||
0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, //
|
||||
0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, //
|
||||
0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, //
|
||||
0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63, //
|
||||
};
|
||||
|
||||
uint8_t constexpr dh_G[] = { 2 };
|
||||
|
||||
void init_rc4(tr_crypto const* crypto, struct arc4_context** setme, std::string_view key)
|
||||
{
|
||||
TR_ASSERT(crypto->torrentHash());
|
||||
|
||||
if (*setme == nullptr)
|
||||
{
|
||||
*setme = tr_new0(struct arc4_context, 1);
|
||||
}
|
||||
|
||||
auto const hash = crypto->torrentHash();
|
||||
auto const buf = hash ? crypto->secretKeySha1(std::data(key), std::size(key), std::data(*hash), std::size(*hash)) :
|
||||
std::nullopt;
|
||||
if (buf)
|
||||
{
|
||||
arc4_init(*setme, std::data(*buf), std::size(*buf));
|
||||
arc4_discard(*setme, 1024);
|
||||
}
|
||||
}
|
||||
|
||||
void crypt_rc4(struct arc4_context* key, size_t buf_len, void const* buf_in, void* buf_out)
|
||||
{
|
||||
if (key == nullptr)
|
||||
{
|
||||
if (buf_in != buf_out)
|
||||
{
|
||||
memmove(buf_out, buf_in, buf_len);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
arc4_process(key, buf_in, buf_out, buf_len);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
///
|
||||
|
||||
bool tr_crypto::computeSecret(void const* peer_public_key, size_t len)
|
||||
{
|
||||
ensureKeyExists();
|
||||
my_secret_ = tr_dh_agree(dh_, static_cast<uint8_t const*>(peer_public_key), len);
|
||||
return my_secret_ != nullptr;
|
||||
}
|
||||
|
||||
void tr_crypto::ensureKeyExists()
|
||||
{
|
||||
if (dh_ == nullptr)
|
||||
{
|
||||
size_t public_key_length = KEY_LEN;
|
||||
dh_ = tr_dh_new(dh_P, sizeof(dh_P), dh_G, sizeof(dh_G));
|
||||
tr_dh_make_key(dh_, DhPrivkeyLen, my_public_key_, &public_key_length);
|
||||
|
||||
TR_ASSERT(public_key_length == KEY_LEN);
|
||||
}
|
||||
}
|
||||
void tr_crypto::decryptInit()
|
||||
{
|
||||
init_rc4(this, &dec_key_, is_incoming_ ? "keyA" : "keyB"); // lgtm[cpp/weak-cryptographic-algorithm]
|
||||
}
|
||||
|
||||
void tr_crypto::decrypt(size_t buf_len, void const* buf_in, void* buf_out)
|
||||
{
|
||||
crypt_rc4(dec_key_, buf_len, buf_in, buf_out); // lgtm[cpp/weak-cryptographic-algorithm]
|
||||
}
|
||||
|
||||
void tr_crypto::encryptInit()
|
||||
{
|
||||
init_rc4(this, &enc_key_, is_incoming_ ? "keyB" : "keyA"); // lgtm[cpp/weak-cryptographic-algorithm]
|
||||
}
|
||||
|
||||
void tr_crypto::encrypt(size_t buf_len, void const* buf_in, void* buf_out)
|
||||
{
|
||||
crypt_rc4(enc_key_, buf_len, buf_in, buf_out); // lgtm[cpp/weak-cryptographic-algorithm]
|
||||
}
|
||||
|
||||
std::optional<tr_sha1_digest_t> tr_crypto::secretKeySha1(
|
||||
void const* prepend,
|
||||
size_t prepend_len,
|
||||
void const* append,
|
||||
size_t append_len) const
|
||||
{
|
||||
return tr_dh_secret_derive(my_secret_, prepend, prepend_len, append, append_len);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> tr_crypto::pad(size_t maxlen) const
|
||||
{
|
||||
auto const len = tr_rand_int(maxlen);
|
||||
auto ret = std::vector<uint8_t>{};
|
||||
ret.resize(len);
|
||||
tr_rand_buffer(std::data(ret), len);
|
||||
return ret;
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// This file Copyright © 2007-2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
// NB: crypto-test-ref.h needs this, so use it instead of #pragma once
|
||||
#ifndef TR_ENCRYPTION_H
|
||||
#define TR_ENCRYPTION_H
|
||||
|
||||
#ifndef __TRANSMISSION__
|
||||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto-utils.h"
|
||||
#include "tr-macros.h"
|
||||
|
||||
enum
|
||||
{
|
||||
KEY_LEN = 96
|
||||
};
|
||||
|
||||
/** @brief Holds state information for encrypted peer communications */
|
||||
struct tr_crypto
|
||||
{
|
||||
tr_crypto(tr_sha1_digest_t const* torrent_hash = nullptr, bool is_incoming = true);
|
||||
~tr_crypto();
|
||||
|
||||
tr_crypto& operator=(tr_crypto const&) = delete;
|
||||
tr_crypto& operator=(tr_crypto&&) = delete;
|
||||
tr_crypto(tr_crypto const&) = delete;
|
||||
tr_crypto(tr_crypto&&) = delete;
|
||||
|
||||
void setTorrentHash(tr_sha1_digest_t hash) noexcept
|
||||
{
|
||||
torrent_hash_ = hash;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto const& torrentHash() const noexcept
|
||||
{
|
||||
return torrent_hash_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string_view myPublicKey()
|
||||
{
|
||||
ensureKeyExists();
|
||||
return { reinterpret_cast<char const*>(my_public_key_), KEY_LEN };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool computeSecret(void const* peer_public_key, size_t len);
|
||||
|
||||
[[nodiscard]] std::optional<tr_sha1_digest_t> secretKeySha1(
|
||||
void const* prepend,
|
||||
size_t prepend_len,
|
||||
void const* append,
|
||||
size_t append_len) const;
|
||||
|
||||
[[nodiscard]] constexpr auto isIncoming() const noexcept
|
||||
{
|
||||
return is_incoming_;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual std::vector<uint8_t> pad(size_t maxlen) const;
|
||||
|
||||
void decryptInit();
|
||||
void decrypt(size_t buflen, void const* buf_in, void* buf_out);
|
||||
void encryptInit();
|
||||
void encrypt(size_t buflen, void const* buf_in, void* buf_out);
|
||||
|
||||
private:
|
||||
void ensureKeyExists();
|
||||
|
||||
std::optional<tr_sha1_digest_t> torrent_hash_;
|
||||
struct arc4_context* dec_key_ = nullptr;
|
||||
struct arc4_context* enc_key_ = nullptr;
|
||||
tr_dh_ctx_t dh_ = {};
|
||||
tr_dh_secret_t my_secret_ = {};
|
||||
uint8_t my_public_key_[KEY_LEN] = {};
|
||||
bool const is_incoming_;
|
||||
};
|
||||
|
||||
#endif // TR_ENCRYPTION_H
|
|
@ -17,6 +17,7 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "clients.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "handshake.h"
|
||||
#include "log.h"
|
||||
#include "peer-io.h"
|
||||
|
@ -47,10 +48,15 @@ static auto constexpr INCOMING_HANDSHAKE_LEN = int{ 48 };
|
|||
// encryption constants
|
||||
static auto constexpr PadA_MAXLEN = int{ 512 };
|
||||
static auto constexpr PadB_MAXLEN = int{ 512 };
|
||||
static auto constexpr VC_LENGTH = int{ 8 };
|
||||
static auto constexpr CRYPTO_PROVIDE_PLAINTEXT = int{ 1 };
|
||||
static auto constexpr CRYPTO_PROVIDE_CRYPTO = int{ 2 };
|
||||
|
||||
// "VC is a verification constant that is used to verify whether the
|
||||
// other side knows S and SKEY and thus defeats replay attacks of the
|
||||
// SKEY hash. As of this version VC is a String of 8 bytes set to 0x00.
|
||||
using vc_t = std::array<std::byte, 8>;
|
||||
static auto constexpr VC = vc_t{};
|
||||
|
||||
// how long to wait before giving up on a handshake
|
||||
static auto constexpr HANDSHAKE_TIMEOUT_SEC = int{ 30 };
|
||||
|
||||
|
@ -82,6 +88,8 @@ static auto constexpr HANDSHAKE_TIMEOUT_SEC = int{ 30 };
|
|||
***
|
||||
**/
|
||||
|
||||
using DH = tr_message_stream_encryption::DH;
|
||||
|
||||
enum handshake_state_t
|
||||
{
|
||||
/* incoming */
|
||||
|
@ -106,6 +114,7 @@ struct tr_handshake
|
|||
{
|
||||
tr_handshake(std::shared_ptr<tr_handshake_mediator> mediator_in)
|
||||
: mediator{ std::move(mediator_in) }
|
||||
, dh{ mediator->privateKey() }
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -129,7 +138,7 @@ struct tr_handshake
|
|||
bool haveReadAnythingFromPeer;
|
||||
bool haveSentBitTorrentHandshake;
|
||||
tr_peerIo* io;
|
||||
tr_crypto* crypto;
|
||||
DH dh;
|
||||
handshake_state_t state;
|
||||
tr_encryption_mode encryptionMode;
|
||||
uint16_t pad_c_len;
|
||||
|
@ -185,7 +194,7 @@ static void setReadState(tr_handshake* handshake, handshake_state_t state)
|
|||
|
||||
static bool buildHandshakeMessage(tr_handshake* handshake, uint8_t* buf)
|
||||
{
|
||||
auto const info_hash = handshake->crypto->torrentHash();
|
||||
auto const info_hash = handshake->io->torrentHash();
|
||||
auto const info = info_hash ? handshake->mediator->torrentInfo(*info_hash) : std::nullopt;
|
||||
if (!info)
|
||||
{
|
||||
|
@ -289,25 +298,23 @@ static handshake_parse_err_t parseHandshake(tr_handshake* handshake, struct evbu
|
|||
****
|
||||
***/
|
||||
|
||||
/* 1 A->B: Diffie Hellman Ya, PadA */
|
||||
template<size_t PadMax>
|
||||
static void sendPublicKeyAndPad(tr_handshake* handshake)
|
||||
{
|
||||
auto const public_key = handshake->dh.publicKey();
|
||||
auto outbuf = std::array<std::byte, std::size(public_key) + PadMax>{};
|
||||
auto const data = std::data(outbuf);
|
||||
auto walk = data;
|
||||
walk = std::copy(std::begin(public_key), std::end(public_key), walk);
|
||||
walk += handshake->mediator->pad(walk, PadMax);
|
||||
tr_peerIoWriteBytes(handshake->io, data, walk - data, false);
|
||||
}
|
||||
|
||||
// 1 A->B: our public key (Ya) and some padding (PadA)
|
||||
static void sendYa(tr_handshake* handshake)
|
||||
{
|
||||
/* add our public key (Ya) */
|
||||
|
||||
auto const public_key = handshake->crypto->myPublicKey();
|
||||
TR_ASSERT(std::size(public_key) == KEY_LEN);
|
||||
|
||||
char outbuf[KEY_LEN + PadA_MAXLEN];
|
||||
char* walk = outbuf;
|
||||
walk = std::copy(std::begin(public_key), std::end(public_key), walk);
|
||||
|
||||
// add some random padding
|
||||
auto const pad_a = handshake->crypto->pad(PadA_MAXLEN);
|
||||
walk = std::copy(std::begin(pad_a), std::end(pad_a), walk);
|
||||
|
||||
/* send it */
|
||||
sendPublicKeyAndPad<PadA_MAXLEN>(handshake);
|
||||
setReadState(handshake, AWAITING_YB);
|
||||
tr_peerIoWriteBytes(handshake->io, outbuf, walk - outbuf, false);
|
||||
}
|
||||
|
||||
static uint32_t getCryptoProvide(tr_handshake const* handshake)
|
||||
|
@ -362,14 +369,8 @@ static uint32_t getCryptoSelect(tr_handshake const* handshake, uint32_t crypto_p
|
|||
return 0;
|
||||
}
|
||||
|
||||
static auto computeRequestHash(tr_handshake const* handshake, std::string_view name)
|
||||
{
|
||||
return handshake->crypto->secretKeySha1(std::data(name), std::size(name), "", 0);
|
||||
}
|
||||
|
||||
static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
{
|
||||
uint8_t yb[KEY_LEN];
|
||||
size_t needlen = HANDSHAKE_NAME_LEN;
|
||||
|
||||
if (evbuffer_get_length(inbuf) < needlen)
|
||||
|
@ -379,9 +380,10 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
bool const isEncrypted = memcmp(evbuffer_pullup(inbuf, HANDSHAKE_NAME_LEN), HANDSHAKE_NAME, HANDSHAKE_NAME_LEN) != 0;
|
||||
|
||||
auto peer_public_key = DH::key_bigend_t{};
|
||||
if (isEncrypted)
|
||||
{
|
||||
needlen = KEY_LEN;
|
||||
needlen = std::size(peer_public_key);
|
||||
|
||||
if (evbuffer_get_length(inbuf) < needlen)
|
||||
{
|
||||
|
@ -391,8 +393,6 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
tr_logAddTraceHand(handshake, isEncrypted ? "got an encrypted handshake" : "got a plain handshake");
|
||||
|
||||
tr_peerIoSetEncryption(handshake->io, isEncrypted ? PEER_ENCRYPTION_RC4 : PEER_ENCRYPTION_NONE);
|
||||
|
||||
if (!isEncrypted)
|
||||
{
|
||||
setState(handshake, AWAITING_HANDSHAKE);
|
||||
|
@ -401,40 +401,36 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
handshake->haveReadAnythingFromPeer = true;
|
||||
|
||||
/* compute the secret */
|
||||
evbuffer_remove(inbuf, yb, KEY_LEN);
|
||||
|
||||
if (!handshake->crypto->computeSecret(yb, KEY_LEN))
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
// get the peer's public key
|
||||
evbuffer_remove(inbuf, std::data(peer_public_key), std::size(peer_public_key));
|
||||
handshake->dh.setPeerPublicKey(peer_public_key);
|
||||
|
||||
/* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S),
|
||||
* ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */
|
||||
evbuffer* const outbuf = evbuffer_new();
|
||||
|
||||
/* HASH('req1', S) */
|
||||
if (auto const req1 = tr_sha1("req1"sv, handshake->dh.secret()); req1)
|
||||
{
|
||||
auto const req1 = computeRequestHash(handshake, "req1"sv);
|
||||
if (!req1)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req1 hash after Yb");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
evbuffer_add(outbuf, std::data(*req1), std::size(*req1));
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req1 hash after Yb");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
auto const info_hash = handshake->io->torrentHash();
|
||||
if (!info_hash)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req2/req3 hash after Yb");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
/* HASH('req2', SKEY) xor HASH('req3', S) */
|
||||
{
|
||||
auto const hash = handshake->crypto->torrentHash();
|
||||
if (!hash)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req2/req3 hash after Yb");
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
auto const req2 = tr_sha1("req2"sv, *hash);
|
||||
auto const req3 = computeRequestHash(handshake, "req3"sv);
|
||||
auto const req2 = tr_sha1("req2"sv, *info_hash);
|
||||
auto const req3 = tr_sha1("req3"sv, handshake->dh.secret());
|
||||
if (!req2 || !req3)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req2/req3 hash after Yb");
|
||||
|
@ -453,35 +449,26 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
/* ENCRYPT(VC, crypto_provide, len(PadC), PadC
|
||||
* PadC is reserved for future extensions to the handshake...
|
||||
* standard practice at this time is for it to be zero-length */
|
||||
{
|
||||
uint8_t vc[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
tr_peerIoWriteBuf(handshake->io, outbuf, false);
|
||||
handshake->crypto->encryptInit();
|
||||
tr_peerIoSetEncryption(handshake->io, PEER_ENCRYPTION_RC4);
|
||||
|
||||
evbuffer_add(outbuf, vc, VC_LENGTH);
|
||||
evbuffer_add_uint32(outbuf, getCryptoProvide(handshake));
|
||||
evbuffer_add_uint16(outbuf, 0);
|
||||
}
|
||||
tr_peerIoWriteBuf(handshake->io, outbuf, false);
|
||||
handshake->io->encryptInit(handshake->io->isIncoming(), handshake->dh, *info_hash);
|
||||
evbuffer_add(outbuf, std::data(VC), std::size(VC));
|
||||
evbuffer_add_uint32(outbuf, getCryptoProvide(handshake));
|
||||
evbuffer_add_uint16(outbuf, 0);
|
||||
|
||||
/* ENCRYPT len(IA)), ENCRYPT(IA) */
|
||||
if (uint8_t msg[HANDSHAKE_SIZE]; buildHandshakeMessage(handshake, msg))
|
||||
{
|
||||
uint8_t msg[HANDSHAKE_SIZE];
|
||||
|
||||
if (!buildHandshakeMessage(handshake, msg))
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
evbuffer_add_uint16(outbuf, sizeof(msg));
|
||||
evbuffer_add(outbuf, msg, sizeof(msg));
|
||||
|
||||
handshake->haveSentBitTorrentHandshake = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
/* send it */
|
||||
handshake->crypto->decryptInit();
|
||||
handshake->io->decryptInit(handshake->io->isIncoming(), handshake->dh, *info_hash);
|
||||
setReadState(handshake, AWAITING_VC);
|
||||
tr_peerIoWriteBuf(handshake->io, outbuf, false);
|
||||
|
||||
|
@ -490,28 +477,27 @@ static ReadState readYb(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
return READ_LATER;
|
||||
}
|
||||
|
||||
// MSE spec: "Since the length of [PadB is] unknown,
|
||||
// A will be able to resynchronize on ENCRYPT(VC)"
|
||||
static ReadState readVC(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
{
|
||||
uint8_t tmp[VC_LENGTH];
|
||||
int const key_len = VC_LENGTH;
|
||||
uint8_t const key[VC_LENGTH] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
auto tmp = vc_t{};
|
||||
|
||||
/* note: this works w/o having to `unwind' the buffer if
|
||||
* we read too much, but it is pretty brute-force.
|
||||
* it would be nice to make this cleaner. */
|
||||
for (;;)
|
||||
{
|
||||
if (evbuffer_get_length(inbuf) < VC_LENGTH)
|
||||
if (evbuffer_get_length(inbuf) < std::size(tmp))
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "not enough bytes... returning read_more");
|
||||
return READ_LATER;
|
||||
}
|
||||
|
||||
memcpy(tmp, evbuffer_pullup(inbuf, key_len), key_len);
|
||||
handshake->crypto->decryptInit();
|
||||
handshake->crypto->decrypt(key_len, tmp, tmp);
|
||||
|
||||
if (memcmp(tmp, key, key_len) == 0)
|
||||
std::copy_n(reinterpret_cast<std::byte const*>(evbuffer_pullup(inbuf, std::size(tmp))), std::size(tmp), std::data(tmp));
|
||||
handshake->io->decryptInit(handshake->io->isIncoming(), handshake->dh, *handshake->io->torrentHash());
|
||||
handshake->io->decrypt(std::size(tmp), std::data(tmp));
|
||||
if (tmp == VC)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -520,7 +506,7 @@ static ReadState readVC(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
}
|
||||
|
||||
tr_logAddTraceHand(handshake, "got it!");
|
||||
evbuffer_drain(inbuf, key_len);
|
||||
evbuffer_drain(inbuf, std::size(VC));
|
||||
setState(handshake, AWAITING_CRYPTO_SELECT);
|
||||
return READ_NOW;
|
||||
}
|
||||
|
@ -574,8 +560,6 @@ static ReadState readPadD(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
tr_peerIoDrain(handshake->io, inbuf, needlen);
|
||||
|
||||
tr_peerIoSetEncryption(handshake->io, static_cast<tr_encryption_type>(handshake->crypto_select));
|
||||
|
||||
setState(handshake, AWAITING_HANDSHAKE);
|
||||
return READ_NOW;
|
||||
}
|
||||
|
@ -601,8 +585,6 @@ static ReadState readHandshake(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
if (pstrlen == 19) /* unencrypted */
|
||||
{
|
||||
tr_peerIoSetEncryption(handshake->io, PEER_ENCRYPTION_NONE);
|
||||
|
||||
if (handshake->encryptionMode == TR_ENCRYPTION_REQUIRED)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "peer is unencrypted, and we're disallowing that");
|
||||
|
@ -611,8 +593,6 @@ static ReadState readHandshake(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
}
|
||||
else /* encrypted or corrupt */
|
||||
{
|
||||
tr_peerIoSetEncryption(handshake->io, PEER_ENCRYPTION_RC4);
|
||||
|
||||
if (handshake->isIncoming())
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "I think peer is sending us an encrypted handshake...");
|
||||
|
@ -620,7 +600,7 @@ static ReadState readHandshake(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
return READ_NOW;
|
||||
}
|
||||
|
||||
handshake->crypto->decrypt(1, &pstrlen, &pstrlen);
|
||||
handshake->io->decrypt(1, &pstrlen);
|
||||
|
||||
if (pstrlen != 19)
|
||||
{
|
||||
|
@ -725,23 +705,21 @@ static ReadState readPeerId(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
|
||||
static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, fmt::format("in readYa... need {}, have {}", KEY_LEN, evbuffer_get_length(inbuf)));
|
||||
auto peer_public_key = DH::key_bigend_t{};
|
||||
tr_logAddTraceHand(
|
||||
handshake,
|
||||
fmt::format("in readYa... need {}, have {}", std::size(peer_public_key), evbuffer_get_length(inbuf)));
|
||||
|
||||
if (evbuffer_get_length(inbuf) < KEY_LEN)
|
||||
if (evbuffer_get_length(inbuf) < std::size(peer_public_key))
|
||||
{
|
||||
return READ_LATER;
|
||||
}
|
||||
|
||||
/* read the incoming peer's public key */
|
||||
uint8_t ya[KEY_LEN];
|
||||
evbuffer_remove(inbuf, ya, KEY_LEN);
|
||||
evbuffer_remove(inbuf, std::data(peer_public_key), std::size(peer_public_key));
|
||||
handshake->dh.setPeerPublicKey(peer_public_key);
|
||||
|
||||
if (!handshake->crypto->computeSecret(ya, KEY_LEN))
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
auto req1 = computeRequestHash(handshake, "req1"sv);
|
||||
auto req1 = tr_sha1("req1"sv, handshake->dh.secret());
|
||||
if (!req1)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req1 hash after Ya");
|
||||
|
@ -749,17 +727,11 @@ static ReadState readYa(tr_handshake* handshake, struct evbuffer* inbuf)
|
|||
}
|
||||
handshake->myReq1 = *req1;
|
||||
|
||||
/* send our public key to the peer */
|
||||
// send our public key to the peer
|
||||
tr_logAddTraceHand(handshake, "sending B->A: Diffie Hellman Yb, PadB");
|
||||
uint8_t outbuf[KEY_LEN + PadB_MAXLEN];
|
||||
uint8_t* walk = outbuf;
|
||||
auto const public_key = handshake->crypto->myPublicKey();
|
||||
walk = std::copy(std::begin(public_key), std::end(public_key), walk);
|
||||
auto const pad_b = handshake->crypto->pad(PadB_MAXLEN);
|
||||
walk = std::copy(std::begin(pad_b), std::end(pad_b), walk);
|
||||
sendPublicKeyAndPad<PadB_MAXLEN>(handshake);
|
||||
|
||||
setReadState(handshake, AWAITING_PAD_A);
|
||||
tr_peerIoWriteBytes(handshake->io, outbuf, walk - outbuf, false);
|
||||
return READ_NOW;
|
||||
}
|
||||
|
||||
|
@ -792,12 +764,11 @@ static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inb
|
|||
{
|
||||
/* HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC)) */
|
||||
|
||||
uint8_t vc_in[VC_LENGTH];
|
||||
uint16_t padc_len = 0;
|
||||
uint32_t crypto_provide = 0;
|
||||
size_t const needlen = SHA_DIGEST_LENGTH + /* HASH('req1', s) */
|
||||
SHA_DIGEST_LENGTH + /* HASH('req2', SKEY) xor HASH('req3', S) */
|
||||
VC_LENGTH + sizeof(crypto_provide) + sizeof(padc_len);
|
||||
std::size(VC) + sizeof(crypto_provide) + sizeof(padc_len);
|
||||
|
||||
if (evbuffer_get_length(inbuf) < needlen)
|
||||
{
|
||||
|
@ -814,7 +785,7 @@ static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inb
|
|||
auto req2 = tr_sha1_digest_t{};
|
||||
evbuffer_remove(inbuf, std::data(req2), std::size(req2));
|
||||
|
||||
auto const req3 = computeRequestHash(handshake, "req3"sv);
|
||||
auto const req3 = tr_sha1("req3"sv, handshake->dh.secret());
|
||||
if (!req3)
|
||||
{
|
||||
tr_logAddTraceHand(handshake, "error while computing req3 hash after req2");
|
||||
|
@ -848,9 +819,10 @@ static ReadState readCryptoProvide(tr_handshake* handshake, struct evbuffer* inb
|
|||
|
||||
/* next part: ENCRYPT(VC, crypto_provide, len(PadC), */
|
||||
|
||||
handshake->crypto->decryptInit();
|
||||
handshake->io->decryptInit(handshake->io->isIncoming(), handshake->dh, *handshake->io->torrentHash());
|
||||
|
||||
tr_peerIoReadBytes(handshake->io, inbuf, vc_in, VC_LENGTH);
|
||||
auto vc_in = vc_t{};
|
||||
tr_peerIoReadBytes(handshake->io, inbuf, std::data(vc_in), std::size(vc_in));
|
||||
|
||||
tr_peerIoReadUint32(handshake->io, inbuf, &crypto_provide);
|
||||
handshake->crypto_provide = crypto_provide;
|
||||
|
@ -900,16 +872,12 @@ static ReadState readIA(tr_handshake* handshake, struct evbuffer const* inbuf)
|
|||
*** B->A: ENCRYPT(VC, crypto_select, len(padD), padD), ENCRYPT2(Payload Stream)
|
||||
**/
|
||||
|
||||
handshake->crypto->encryptInit();
|
||||
handshake->io->encryptInit(handshake->io->isIncoming(), handshake->dh, *handshake->io->torrentHash());
|
||||
evbuffer* const outbuf = evbuffer_new();
|
||||
|
||||
{
|
||||
/* send VC */
|
||||
uint8_t vc[VC_LENGTH];
|
||||
memset(vc, 0, VC_LENGTH);
|
||||
evbuffer_add(outbuf, vc, VC_LENGTH);
|
||||
tr_logAddTraceHand(handshake, "sending vc");
|
||||
}
|
||||
// send VC
|
||||
tr_logAddTraceHand(handshake, "sending vc");
|
||||
evbuffer_add(outbuf, std::data(VC), std::size(VC));
|
||||
|
||||
/* send crypto_select */
|
||||
uint32_t const crypto_select = getCryptoSelect(handshake, handshake->crypto_provide);
|
||||
|
@ -940,23 +908,20 @@ static ReadState readIA(tr_handshake* handshake, struct evbuffer const* inbuf)
|
|||
if (crypto_select == CRYPTO_PROVIDE_PLAINTEXT)
|
||||
{
|
||||
tr_peerIoWriteBuf(handshake->io, outbuf, false);
|
||||
tr_peerIoSetEncryption(handshake->io, PEER_ENCRYPTION_NONE);
|
||||
}
|
||||
|
||||
tr_logAddTraceHand(handshake, "sending handshake");
|
||||
|
||||
/* send our handshake */
|
||||
if (uint8_t msg[HANDSHAKE_SIZE]; buildHandshakeMessage(handshake, msg))
|
||||
{
|
||||
uint8_t msg[HANDSHAKE_SIZE];
|
||||
|
||||
if (!buildHandshakeMessage(handshake, msg))
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
evbuffer_add(outbuf, msg, sizeof(msg));
|
||||
handshake->haveSentBitTorrentHandshake = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tr_handshakeDone(handshake, false);
|
||||
}
|
||||
|
||||
/* send it out */
|
||||
tr_peerIoWriteBuf(handshake->io, outbuf, false);
|
||||
|
@ -1196,7 +1161,6 @@ tr_handshake* tr_handshakeNew(
|
|||
{
|
||||
auto* const handshake = new tr_handshake{ std::move(mediator) };
|
||||
handshake->io = io;
|
||||
handshake->crypto = tr_peerIoGetCrypto(io);
|
||||
handshake->encryptionMode = encryptionMode;
|
||||
handshake->done_func = done_func;
|
||||
handshake->done_func_user_data = done_func_user_data;
|
||||
|
@ -1205,7 +1169,6 @@ tr_handshake* tr_handshakeNew(
|
|||
|
||||
tr_peerIoRef(io); /* balanced by the unref in ~tr_handshake() */
|
||||
tr_peerIoSetIOFuncs(handshake->io, canRead, nullptr, gotError, handshake);
|
||||
tr_peerIoSetEncryption(io, PEER_ENCRYPTION_NONE);
|
||||
|
||||
if (handshake->isIncoming())
|
||||
{
|
||||
|
|
|
@ -15,13 +15,14 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "net.h" // tr_address
|
||||
#include "peer-mse.h" // tr_message_stream_encryption::DH
|
||||
|
||||
/** @addtogroup peers Peers
|
||||
@{ */
|
||||
|
||||
class tr_peerIo;
|
||||
|
||||
/** @brief opaque struct holding hanshake state information.
|
||||
/** @brief opaque struct holding handshake state information.
|
||||
freed when the handshake is completed. */
|
||||
struct tr_handshake;
|
||||
struct event_base;
|
||||
|
@ -57,6 +58,13 @@ public:
|
|||
|
||||
[[nodiscard]] virtual bool isPeerKnownSeed(tr_torrent_id_t tor_id, tr_address addr) const = 0;
|
||||
|
||||
[[nodiscard]] virtual size_t pad(void* setme, size_t max_bytes) const = 0;
|
||||
|
||||
[[nodiscard]] virtual tr_message_stream_encryption::DH::private_key_bigend_t privateKey() const
|
||||
{
|
||||
return tr_message_stream_encryption::DH::randomPrivateKey();
|
||||
}
|
||||
|
||||
virtual void setUTPFailed(tr_sha1_digest_t const& info_hash, tr_address) = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -892,19 +892,7 @@ size_t tr_peerIoGetWriteBufferSpace(tr_peerIo const* io, uint64_t now)
|
|||
***
|
||||
**/
|
||||
|
||||
void tr_peerIoSetEncryption(tr_peerIo* io, tr_encryption_type encryption_type)
|
||||
{
|
||||
TR_ASSERT(tr_isPeerIo(io));
|
||||
TR_ASSERT(encryption_type == PEER_ENCRYPTION_NONE || encryption_type == PEER_ENCRYPTION_RC4);
|
||||
|
||||
io->encryption_type = encryption_type;
|
||||
}
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
|
||||
static inline void processBuffer(tr_crypto& crypto, evbuffer* buffer, size_t offset, size_t size)
|
||||
static inline void processBuffer(tr_peerIo& io, evbuffer* buffer, size_t offset, size_t size)
|
||||
{
|
||||
struct evbuffer_ptr pos;
|
||||
struct evbuffer_iovec iovec;
|
||||
|
@ -918,7 +906,7 @@ static inline void processBuffer(tr_crypto& crypto, evbuffer* buffer, size_t off
|
|||
break;
|
||||
}
|
||||
|
||||
crypto.encrypt(iovec.iov_len, iovec.iov_base, iovec.iov_base);
|
||||
io.encrypt(iovec.iov_len, iovec.iov_base);
|
||||
|
||||
TR_ASSERT(size >= iovec.iov_len);
|
||||
size -= iovec.iov_len;
|
||||
|
@ -931,9 +919,9 @@ void tr_peerIoWriteBuf(tr_peerIo* io, struct evbuffer* buf, bool isPieceData)
|
|||
{
|
||||
size_t const byteCount = evbuffer_get_length(buf);
|
||||
|
||||
if (io->encryption_type == PEER_ENCRYPTION_RC4)
|
||||
if (io->isEncrypted())
|
||||
{
|
||||
processBuffer(io->crypto, buf, 0, byteCount);
|
||||
processBuffer(*io, buf, 0, byteCount);
|
||||
}
|
||||
|
||||
evbuffer_add_buffer(io->outbuf.get(), buf);
|
||||
|
@ -947,13 +935,11 @@ void tr_peerIoWriteBytes(tr_peerIo* io, void const* bytes, size_t byteCount, boo
|
|||
|
||||
iovec.iov_len = byteCount;
|
||||
|
||||
if (io->encryption_type == PEER_ENCRYPTION_RC4)
|
||||
memcpy(iovec.iov_base, bytes, iovec.iov_len);
|
||||
|
||||
if (io->isEncrypted())
|
||||
{
|
||||
io->crypto.encrypt(iovec.iov_len, bytes, iovec.iov_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(iovec.iov_base, bytes, iovec.iov_len);
|
||||
io->encrypt(iovec.iov_len, iovec.iov_base);
|
||||
}
|
||||
|
||||
evbuffer_commit_space(io->outbuf.get(), &iovec, 1);
|
||||
|
@ -997,19 +983,11 @@ void tr_peerIoReadBytes(tr_peerIo* io, struct evbuffer* inbuf, void* bytes, size
|
|||
TR_ASSERT(tr_isPeerIo(io));
|
||||
TR_ASSERT(evbuffer_get_length(inbuf) >= byteCount);
|
||||
|
||||
switch (io->encryption_type)
|
||||
evbuffer_remove(inbuf, bytes, byteCount);
|
||||
|
||||
if (io->isEncrypted())
|
||||
{
|
||||
case PEER_ENCRYPTION_NONE:
|
||||
evbuffer_remove(inbuf, bytes, byteCount);
|
||||
break;
|
||||
|
||||
case PEER_ENCRYPTION_RC4:
|
||||
evbuffer_remove(inbuf, bytes, byteCount);
|
||||
io->crypto.decrypt(byteCount, bytes, bytes);
|
||||
break;
|
||||
|
||||
default:
|
||||
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unhandled encryption type {:d}"), io->encryption_type));
|
||||
io->decrypt(byteCount, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "bandwidth.h"
|
||||
#include "crypto.h"
|
||||
#include "net.h" // tr_address
|
||||
#include "peer-mse.h"
|
||||
#include "peer-socket.h"
|
||||
#include "tr-assert.h"
|
||||
|
||||
|
@ -48,13 +48,6 @@ enum ReadState
|
|||
READ_ERR
|
||||
};
|
||||
|
||||
enum tr_encryption_type
|
||||
{
|
||||
/* these match the values in MSE's crypto_select */
|
||||
PEER_ENCRYPTION_NONE = (1 << 0),
|
||||
PEER_ENCRYPTION_RC4 = (1 << 1)
|
||||
};
|
||||
|
||||
using tr_can_read_cb = ReadState (*)(tr_peerIo* io, void* user_data, size_t* setme_piece_byte_count);
|
||||
|
||||
using tr_did_write_cb = void (*)(tr_peerIo* io, size_t bytesWritten, bool wasPieceData, void* userData);
|
||||
|
@ -75,6 +68,9 @@ using tr_evbuffer_ptr = std::unique_ptr<evbuffer, evbuffer_deleter>;
|
|||
|
||||
class tr_peerIo
|
||||
{
|
||||
using DH = tr_message_stream_encryption::DH;
|
||||
using Filter = tr_message_stream_encryption::Filter;
|
||||
|
||||
public:
|
||||
tr_peerIo(
|
||||
tr_session* session_in,
|
||||
|
@ -85,14 +81,18 @@ public:
|
|||
bool is_seed,
|
||||
time_t current_time,
|
||||
tr_bandwidth* parent_bandwidth)
|
||||
: crypto{ torrent_hash, is_incoming }
|
||||
, session{ session_in }
|
||||
: session{ session_in }
|
||||
, time_created{ current_time }
|
||||
, bandwidth_{ parent_bandwidth }
|
||||
, addr_{ addr }
|
||||
, port_{ port }
|
||||
, is_seed_{ is_seed }
|
||||
, is_incoming_{ is_incoming }
|
||||
{
|
||||
if (torrent_hash != nullptr)
|
||||
{
|
||||
torrent_hash_ = *torrent_hash;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr tr_address const& address() const noexcept
|
||||
|
@ -177,21 +177,19 @@ public:
|
|||
bandwidth_.setParent(parent);
|
||||
}
|
||||
|
||||
tr_crypto crypto;
|
||||
|
||||
[[nodiscard]] constexpr auto isIncoming() noexcept
|
||||
{
|
||||
return crypto.isIncoming();
|
||||
return is_incoming_;
|
||||
}
|
||||
|
||||
void setTorrentHash(tr_sha1_digest_t hash) noexcept
|
||||
{
|
||||
crypto.setTorrentHash(hash);
|
||||
torrent_hash_ = hash;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto const& torrentHash() const noexcept
|
||||
{
|
||||
return crypto.torrentHash();
|
||||
return torrent_hash_;
|
||||
}
|
||||
|
||||
// TODO(ckerr): yikes, unlike other class' magic_numbers it looks
|
||||
|
@ -219,9 +217,6 @@ public:
|
|||
struct event* event_read = nullptr;
|
||||
struct event* event_write = nullptr;
|
||||
|
||||
// TODO(ckerr): this could be narrowed to 1 byte
|
||||
tr_encryption_type encryption_type = PEER_ENCRYPTION_NONE;
|
||||
|
||||
// TODO: use std::shared_ptr instead of manual refcounting?
|
||||
int refCount = 1;
|
||||
|
||||
|
@ -231,13 +226,53 @@ public:
|
|||
|
||||
bool utp_supported_ = false;
|
||||
|
||||
void decryptInit(bool is_incoming, DH const& dh, tr_sha1_digest_t const& info_hash)
|
||||
{
|
||||
filter().decryptInit(is_incoming, dh, info_hash);
|
||||
}
|
||||
|
||||
void decrypt(size_t buflen, void* buf)
|
||||
{
|
||||
filter().decrypt(buflen, buf);
|
||||
}
|
||||
|
||||
void encryptInit(bool is_incoming, DH const& dh, tr_sha1_digest_t const& info_hash)
|
||||
{
|
||||
filter().encryptInit(is_incoming, dh, info_hash);
|
||||
}
|
||||
|
||||
void encrypt(size_t buflen, void* buf)
|
||||
{
|
||||
filter().encrypt(buflen, buf);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isEncrypted() const noexcept
|
||||
{
|
||||
return filter_.get() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
tr_bandwidth bandwidth_;
|
||||
|
||||
std::unique_ptr<tr_message_stream_encryption::Filter> filter_;
|
||||
|
||||
Filter& filter()
|
||||
{
|
||||
if (!filter_)
|
||||
{
|
||||
filter_ = std::make_unique<Filter>();
|
||||
}
|
||||
|
||||
return *filter_;
|
||||
}
|
||||
|
||||
std::optional<tr_sha1_digest_t> torrent_hash_;
|
||||
|
||||
tr_address const addr_;
|
||||
tr_port const port_;
|
||||
|
||||
bool const is_seed_;
|
||||
bool const is_incoming_;
|
||||
|
||||
bool dht_supported_ = false;
|
||||
bool extended_protocol_supported_ = false;
|
||||
|
@ -317,18 +352,6 @@ void tr_peerIoWriteBuf(tr_peerIo* io, struct evbuffer* buf, bool isPieceData);
|
|||
***
|
||||
**/
|
||||
|
||||
constexpr tr_crypto* tr_peerIoGetCrypto(tr_peerIo* io)
|
||||
{
|
||||
return &io->crypto;
|
||||
}
|
||||
|
||||
void tr_peerIoSetEncryption(tr_peerIo* io, tr_encryption_type encryption_type);
|
||||
|
||||
constexpr bool tr_peerIoIsEncrypted(tr_peerIo const* io)
|
||||
{
|
||||
return io != nullptr && io->encryption_type == PEER_ENCRYPTION_RC4;
|
||||
}
|
||||
|
||||
void evbuffer_add_uint8(struct evbuffer* outbuf, uint8_t byte);
|
||||
void evbuffer_add_uint16(struct evbuffer* outbuf, uint16_t hs);
|
||||
void evbuffer_add_uint32(struct evbuffer* outbuf, uint32_t hl);
|
||||
|
|
|
@ -127,6 +127,13 @@ public:
|
|||
return session_.event_base;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t pad(void* setme, size_t maxlen) const override
|
||||
{
|
||||
auto const len = tr_rand_int(maxlen);
|
||||
tr_rand_buffer(setme, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
|
174
libtransmission/peer-mse.cc
Normal file
174
libtransmission/peer-mse.cc
Normal file
|
@ -0,0 +1,174 @@
|
|||
// This file Copyright © 2007-2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <arc4.h>
|
||||
|
||||
#include <math/wide_integer/uintwide_t.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h" // tr_sha1
|
||||
#include "net.h" // includes the headers for htonl()
|
||||
#include "peer-mse.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
// source: https://stackoverflow.com/a/1001330/6568470
|
||||
// nb: when we bump to std=C++20, use `std::endian`
|
||||
static bool is_big_endian()
|
||||
{
|
||||
return htonl(47) == 47;
|
||||
}
|
||||
|
||||
namespace wi
|
||||
{
|
||||
using key_t = math::wide_integer::uintwide_t<
|
||||
tr_message_stream_encryption::DH::KeySize * std::numeric_limits<unsigned char>::digits>;
|
||||
|
||||
using private_key_t = math::wide_integer::uintwide_t<
|
||||
tr_message_stream_encryption::DH::PrivateKeySize * std::numeric_limits<unsigned char>::digits>;
|
||||
|
||||
template<typename UIntWide>
|
||||
auto import_bits(std::array<std::byte, UIntWide::my_width2 / std::numeric_limits<uint8_t>::digits> const& bigend_bin)
|
||||
{
|
||||
auto ret = UIntWide{};
|
||||
|
||||
if (is_big_endian())
|
||||
{
|
||||
for (auto walk = std::rbegin(bigend_bin), end = std::rend(bigend_bin); walk != end; ++walk)
|
||||
{
|
||||
ret <<= 8;
|
||||
ret += static_cast<uint8_t>(*walk);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const walk : bigend_bin)
|
||||
{
|
||||
ret <<= 8;
|
||||
ret += static_cast<uint8_t>(walk);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename UIntWide>
|
||||
auto export_bits(UIntWide i)
|
||||
{
|
||||
auto ret = std::array<std::byte, UIntWide::my_width2 / std::numeric_limits<uint8_t>::digits>{};
|
||||
|
||||
if (is_big_endian())
|
||||
{
|
||||
for (auto& walk : ret)
|
||||
{
|
||||
walk = std::byte(static_cast<uint8_t>(i & 0xFF));
|
||||
i >>= 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto walk = std::rbegin(ret), end = std::rend(ret); walk != end; ++walk)
|
||||
{
|
||||
*walk = std::byte(static_cast<uint8_t>(i & 0xFF));
|
||||
i >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto WIDE_INTEGER_CONSTEXPR const G = wi::key_t{ "2" };
|
||||
auto WIDE_INTEGER_CONSTEXPR const P = wi::key_t{
|
||||
"0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563"
|
||||
};
|
||||
|
||||
} // namespace wi
|
||||
|
||||
namespace tr_message_stream_encryption
|
||||
{
|
||||
|
||||
/// DH
|
||||
|
||||
[[nodiscard]] DH::private_key_bigend_t DH::randomPrivateKey() noexcept
|
||||
{
|
||||
auto key = DH::private_key_bigend_t{};
|
||||
tr_rand_buffer(std::data(key), std::size(key));
|
||||
return key;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto generatePublicKey(DH::private_key_bigend_t const& private_key) noexcept
|
||||
{
|
||||
auto const private_key_wi = wi::import_bits<wi::private_key_t>(private_key);
|
||||
auto const public_key_wi = math::wide_integer::powm(wi::G, private_key_wi, wi::P);
|
||||
return wi::export_bits(public_key_wi);
|
||||
}
|
||||
|
||||
DH::DH(private_key_bigend_t const& private_key) noexcept
|
||||
: private_key_{ private_key }
|
||||
{
|
||||
}
|
||||
|
||||
DH::key_bigend_t DH::publicKey() noexcept
|
||||
{
|
||||
if (public_key_ == key_bigend_t{})
|
||||
{
|
||||
public_key_ = generatePublicKey(private_key_);
|
||||
}
|
||||
|
||||
return public_key_;
|
||||
}
|
||||
|
||||
void DH::setPeerPublicKey(key_bigend_t const& peer_public_key)
|
||||
{
|
||||
auto const secret = math::wide_integer::powm(
|
||||
wi::import_bits<wi::key_t>(peer_public_key),
|
||||
wi::import_bits<wi::private_key_t>(private_key_),
|
||||
wi::P);
|
||||
secret_ = wi::export_bits(secret);
|
||||
}
|
||||
|
||||
/// Filter
|
||||
|
||||
void Filter::decryptInit(bool is_incoming, DH const& dh, tr_sha1_digest_t const& info_hash)
|
||||
{
|
||||
auto const key = is_incoming ? "keyA"sv : "keyB"sv;
|
||||
|
||||
dec_key_ = std::make_shared<struct arc4_context>();
|
||||
auto const buf = tr_sha1(key, dh.secret(), info_hash);
|
||||
arc4_init(dec_key_.get(), std::data(*buf), std::size(*buf));
|
||||
arc4_discard(dec_key_.get(), 1024);
|
||||
}
|
||||
|
||||
void Filter::decrypt(size_t buf_len, void* buf)
|
||||
{
|
||||
if (dec_key_)
|
||||
{
|
||||
arc4_process(dec_key_.get(), buf, buf, buf_len);
|
||||
}
|
||||
}
|
||||
|
||||
void Filter::encryptInit(bool is_incoming, DH const& dh, tr_sha1_digest_t const& info_hash)
|
||||
{
|
||||
auto const key = is_incoming ? "keyB"sv : "keyA"sv;
|
||||
|
||||
enc_key_ = std::make_shared<struct arc4_context>();
|
||||
auto const buf = tr_sha1(key, dh.secret(), info_hash);
|
||||
arc4_init(enc_key_.get(), std::data(*buf), std::size(*buf));
|
||||
arc4_discard(enc_key_.get(), 1024);
|
||||
}
|
||||
|
||||
void Filter::encrypt(size_t buf_len, void* buf)
|
||||
{
|
||||
if (enc_key_)
|
||||
{
|
||||
arc4_process(enc_key_.get(), buf, buf, buf_len);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tr_message_stream_encryption
|
89
libtransmission/peer-mse.h
Normal file
89
libtransmission/peer-mse.h
Normal file
|
@ -0,0 +1,89 @@
|
|||
// This file Copyright © 2007-2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
// NB: crypto-test-ref.h needs this, so use it instead of #pragma once
|
||||
#ifndef TR_ENCRYPTION_H
|
||||
#define TR_ENCRYPTION_H
|
||||
|
||||
#ifndef __TRANSMISSION__
|
||||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#include <cstddef> // size_t, std::byte
|
||||
#include <memory>
|
||||
|
||||
#include "tr-macros.h" // tr_sha1_digest_t
|
||||
#include "tr-assert.h"
|
||||
|
||||
struct arc4_context;
|
||||
|
||||
// Spec: https://wiki.vuze.com/w/Message_Stream_Encryption
|
||||
namespace tr_message_stream_encryption
|
||||
{
|
||||
|
||||
/**
|
||||
* Holds state for the Diffie-Hellman key exchange that takes place
|
||||
* during encrypted peer handshakes
|
||||
*/
|
||||
class DH
|
||||
{
|
||||
public:
|
||||
// MSE spec: "Minimum length [for the private key] is 128 bit.
|
||||
// Anything beyond 180 bit is not believed to add any further
|
||||
// security and only increases the necessary calculation time.
|
||||
// You should use a length of 160bits whenever possible[.]
|
||||
static auto constexpr PrivateKeySize = size_t{ 20 };
|
||||
|
||||
// MSE spec: "P, S [the shared secret], Ya and Yb
|
||||
// [the public keys] are 768bits long[.]"
|
||||
static auto constexpr KeySize = size_t{ 96 };
|
||||
|
||||
// big-endian byte arrays holding the keys and shared secret.
|
||||
// MSE spec: "The entire handshake is in big-endian."
|
||||
using private_key_bigend_t = std::array<std::byte, PrivateKeySize>;
|
||||
using key_bigend_t = std::array<std::byte, KeySize>;
|
||||
|
||||
// By default, a private key is randomly generated.
|
||||
// Providing a predefined one is useful for reproducible unit tests.
|
||||
DH(private_key_bigend_t const& private_key = randomPrivateKey()) noexcept;
|
||||
|
||||
// Returns our own public key to be shared with a peer.
|
||||
[[nodiscard]] key_bigend_t publicKey() noexcept;
|
||||
|
||||
// Compute the shared secret from our private key and the peer's public key.
|
||||
void setPeerPublicKey(key_bigend_t const& peer_public_key);
|
||||
|
||||
// Returns the shared secret.
|
||||
[[nodiscard]] auto secret() const noexcept
|
||||
{
|
||||
TR_ASSERT(secret_ != key_bigend_t{});
|
||||
return secret_;
|
||||
}
|
||||
|
||||
[[nodiscard]] static private_key_bigend_t randomPrivateKey() noexcept;
|
||||
|
||||
private:
|
||||
private_key_bigend_t const private_key_;
|
||||
key_bigend_t public_key_ = {};
|
||||
key_bigend_t secret_ = {};
|
||||
};
|
||||
|
||||
/// arc4 encryption for both incoming and outgoing stream
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
void decryptInit(bool is_incoming, DH const&, tr_sha1_digest_t const& info_hash);
|
||||
void decrypt(size_t buflen, void* buf);
|
||||
void encryptInit(bool is_incoming, DH const&, tr_sha1_digest_t const& info_hash);
|
||||
void encrypt(size_t buflen, void* buf);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct arc4_context> dec_key_;
|
||||
std::shared_ptr<struct arc4_context> enc_key_;
|
||||
};
|
||||
|
||||
} // namespace tr_message_stream_encryption
|
||||
|
||||
#endif // TR_ENCRYPTION_H
|
|
@ -22,6 +22,7 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "cache.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "completion.h"
|
||||
#include "file.h"
|
||||
#include "log.h"
|
||||
|
@ -382,7 +383,7 @@ public:
|
|||
|
||||
[[nodiscard]] bool is_encrypted() const override
|
||||
{
|
||||
return tr_peerIoIsEncrypted(io);
|
||||
return io->isEncrypted();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_incoming_connection() const override
|
||||
|
|
|
@ -32,8 +32,7 @@
|
|||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto-utils.h" /* tr_rand_buffer() */
|
||||
#include "crypto.h" /* tr_ssha1_matches() */
|
||||
#include "crypto-utils.h" /* tr_rand_buffer(), tr_ssha1_matches() */
|
||||
#include "error.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <cerrno>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
|
|
@ -52,6 +52,7 @@ target_include_directories(libtransmission-test
|
|||
|
||||
target_include_directories(libtransmission-test SYSTEM
|
||||
PRIVATE
|
||||
${WIDE_INTEGER_INCLUDE_DIRS}
|
||||
${B64_INCLUDE_DIRS}
|
||||
${CURL_INCLUDE_DIRS}
|
||||
${EVENT2_INCLUDE_DIRS})
|
||||
|
|
|
@ -10,26 +10,14 @@
|
|||
|
||||
#ifdef CRYPTO_REFERENCE_CHECK
|
||||
|
||||
#define KEY_LEN KEY_LEN_
|
||||
|
||||
#define tr_sha1_ctx_t tr_sha1_ctx_t_
|
||||
#define tr_dh_ctx_t tr_dh_ctx_t_
|
||||
#define tr_dh_secret_t tr_dh_secret_t_
|
||||
#define tr_ssl_ctx_t tr_ssl_ctx_t_
|
||||
#define tr_x509_store_t tr_x509_store_t_
|
||||
#define tr_x509_cert_t tr_x509_cert_t_
|
||||
#define tr_crypto tr_crypto_
|
||||
#define tr_sha1 tr_sha1_
|
||||
#define tr_sha1_init tr_sha1_init_
|
||||
#define tr_sha1_update tr_sha1_update_
|
||||
#define tr_sha1_final tr_sha1_final_
|
||||
#define tr_dh_new tr_dh_new_
|
||||
#define tr_dh_free tr_dh_free_
|
||||
#define tr_dh_make_key tr_dh_make_key_
|
||||
#define tr_dh_agree tr_dh_agree_
|
||||
#define tr_dh_secret_derive tr_dh_secret_derive_
|
||||
#define tr_dh_secret_free tr_dh_secret_free_
|
||||
#define tr_dh_align_key tr_dh_align_key_
|
||||
#define tr_ssl_get_x509_store tr_ssl_get_x509_store_
|
||||
#define tr_x509_store_add tr_x509_store_add_
|
||||
#define tr_x509_cert_new tr_x509_cert_new_
|
||||
|
@ -57,32 +45,18 @@
|
|||
#undef TR_ENCRYPTION_H
|
||||
#undef TR_CRYPTO_UTILS_H
|
||||
|
||||
#include "crypto.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "crypto.cc"
|
||||
#include "crypto-utils.cc"
|
||||
#include "crypto-utils-openssl.cc"
|
||||
|
||||
#undef KEY_LEN_
|
||||
|
||||
#undef tr_sha1_ctx_t
|
||||
#undef tr_dh_ctx_t
|
||||
#undef tr_dh_secret_t
|
||||
#undef tr_ssl_ctx_t
|
||||
#undef tr_x509_store_t
|
||||
#undef tr_x509_cert_t
|
||||
#undef tr_crypto
|
||||
#undef tr_sha1
|
||||
#undef tr_sha1_init
|
||||
#undef tr_sha1_update
|
||||
#undef tr_sha1_final
|
||||
#undef tr_dh_new
|
||||
#undef tr_dh_free
|
||||
#undef tr_dh_make_key
|
||||
#undef tr_dh_agree
|
||||
#undef tr_dh_secret_derive
|
||||
#undef tr_dh_secret_free
|
||||
#undef tr_dh_align_key
|
||||
#undef tr_ssl_get_x509_store
|
||||
#undef tr_x509_store_add
|
||||
#undef tr_x509_cert_new
|
||||
|
@ -109,26 +83,14 @@
|
|||
|
||||
#else /* CRYPTO_REFERENCE_CHECK */
|
||||
|
||||
#define KEY_LEN_ KEY_LEN
|
||||
|
||||
#define tr_sha1_ctx_t_ tr_sha1_ctx_t
|
||||
#define tr_dh_ctx_t_ tr_dh_ctx_t
|
||||
#define tr_dh_secret_t_ tr_dh_secret_t
|
||||
#define tr_ssl_ctx_t_ tr_ssl_ctx_t
|
||||
#define tr_x509_store_t_ tr_x509_store_t
|
||||
#define tr_x509_cert_t_ tr_x509_cert_t
|
||||
#define tr_crypto_ tr_crypto
|
||||
#define tr_sha1_ tr_sha1
|
||||
#define tr_sha1_init_ tr_sha1_init
|
||||
#define tr_sha1_update_ tr_sha1_update
|
||||
#define tr_sha1_final_ tr_sha1_final
|
||||
#define tr_dh_new_ tr_dh_new
|
||||
#define tr_dh_free_ tr_dh_free
|
||||
#define tr_dh_make_key_ tr_dh_make_key
|
||||
#define tr_dh_agree_ tr_dh_agree
|
||||
#define tr_dh_secret_derive_ tr_dh_secret_derive
|
||||
#define tr_dh_secret_free_ tr_dh_secret_free
|
||||
#define tr_dh_align_key_ tr_dh_align_key
|
||||
#define tr_ssl_get_x509_store_ tr_ssl_get_x509_store
|
||||
#define tr_x509_store_add_ tr_x509_store_add
|
||||
#define tr_x509_cert_new_ tr_x509_cert_new
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "crypto.h"
|
||||
#include "peer-mse.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
@ -31,52 +33,71 @@ auto constexpr SomeHash = tr_sha1_digest_t{
|
|||
std::byte{ 14 }, std::byte{ 15 }, std::byte{ 16 }, std::byte{ 17 }, std::byte{ 18 }, std::byte{ 19 },
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
std::string toString(std::array<std::byte, N> const& array)
|
||||
{
|
||||
auto ostr = std::ostringstream{};
|
||||
ostr << '[';
|
||||
for (auto const b : array)
|
||||
{
|
||||
ostr << static_cast<unsigned>(b) << ' ';
|
||||
}
|
||||
ostr << ']';
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Crypto, torrentHash)
|
||||
TEST(Crypto, DH)
|
||||
{
|
||||
auto a = tr_message_stream_encryption::DH{};
|
||||
auto b = tr_message_stream_encryption::DH{};
|
||||
|
||||
auto a = tr_crypto{};
|
||||
EXPECT_FALSE(a.torrentHash());
|
||||
a.setPeerPublicKey(b.publicKey());
|
||||
b.setPeerPublicKey(a.publicKey());
|
||||
EXPECT_EQ(toString(a.secret()), toString(b.secret()));
|
||||
EXPECT_EQ(a.secret(), b.secret());
|
||||
EXPECT_EQ(96, std::size(a.secret()));
|
||||
|
||||
a.setTorrentHash(SomeHash);
|
||||
EXPECT_TRUE(a.torrentHash());
|
||||
EXPECT_EQ(SomeHash, *a.torrentHash());
|
||||
|
||||
auto b = tr_crypto{ &SomeHash, false };
|
||||
EXPECT_TRUE(b.torrentHash());
|
||||
EXPECT_EQ(SomeHash, *b.torrentHash());
|
||||
auto c = tr_message_stream_encryption::DH{};
|
||||
c.setPeerPublicKey(b.publicKey());
|
||||
EXPECT_NE(a.secret(), c.secret());
|
||||
EXPECT_NE(toString(a.secret()), toString(c.secret()));
|
||||
}
|
||||
|
||||
TEST(Crypto, encryptDecrypt)
|
||||
{
|
||||
auto a = tr_crypto{ &SomeHash, false };
|
||||
auto b = tr_crypto_{ &SomeHash, true };
|
||||
auto a_dh = tr_message_stream_encryption::DH{};
|
||||
auto b_dh = tr_message_stream_encryption::DH{};
|
||||
|
||||
auto public_key = b.myPublicKey();
|
||||
EXPECT_TRUE(a.computeSecret(std::data(public_key), std::size(public_key)));
|
||||
public_key = a.myPublicKey();
|
||||
EXPECT_TRUE(b.computeSecret(std::data(public_key), std::size(public_key)));
|
||||
a_dh.setPeerPublicKey(b_dh.publicKey());
|
||||
b_dh.setPeerPublicKey(a_dh.publicKey());
|
||||
|
||||
auto constexpr Input1 = "test1"sv;
|
||||
auto encrypted1 = std::array<char, 128>{};
|
||||
auto decrypted1 = std::array<char, 128>{};
|
||||
|
||||
a.encryptInit();
|
||||
a.encrypt(std::size(Input1), std::data(Input1), std::data(encrypted1));
|
||||
b.decryptInit();
|
||||
b.decrypt(std::size(Input1), std::data(encrypted1), std::data(decrypted1));
|
||||
EXPECT_EQ(Input1, std::data(decrypted1));
|
||||
auto a = tr_message_stream_encryption::Filter{};
|
||||
a.encryptInit(false, a_dh, SomeHash);
|
||||
std::copy_n(std::begin(Input1), std::size(Input1), std::begin(encrypted1));
|
||||
a.encrypt(std::size(Input1), std::data(encrypted1));
|
||||
auto b = tr_message_stream_encryption::Filter{};
|
||||
b.decryptInit(true, b_dh, SomeHash);
|
||||
std::copy_n(std::begin(encrypted1), std::size(Input1), std::begin(decrypted1));
|
||||
b.decrypt(std::size(Input1), std::data(decrypted1));
|
||||
EXPECT_EQ(Input1, std::data(decrypted1)) << "Input1 " << Input1 << " decrypted1 " << std::data(decrypted1);
|
||||
|
||||
auto constexpr Input2 = "@#)C$@)#(*%bvkdjfhwbc039bc4603756VB3)"sv;
|
||||
auto encrypted2 = std::array<char, 128>{};
|
||||
auto decrypted2 = std::array<char, 128>{};
|
||||
|
||||
b.encryptInit();
|
||||
b.encrypt(std::size(Input2), std::data(Input2), std::data(encrypted2));
|
||||
a.decryptInit();
|
||||
a.decrypt(std::size(Input2), std::data(encrypted2), std::data(decrypted2));
|
||||
EXPECT_EQ(Input2, std::data(decrypted2));
|
||||
b.encryptInit(true, b_dh, SomeHash);
|
||||
std::copy_n(std::begin(Input2), std::size(Input2), std::begin(encrypted2));
|
||||
b.encrypt(std::size(Input2), std::data(encrypted2));
|
||||
a.decryptInit(false, a_dh, SomeHash);
|
||||
std::copy_n(std::begin(encrypted2), std::size(Input2), std::begin(decrypted2));
|
||||
a.decrypt(std::size(Input2), std::data(decrypted2));
|
||||
EXPECT_EQ(Input2, std::data(decrypted2)) << "Input2 " << Input2 << " decrypted2 " << std::data(decrypted2);
|
||||
}
|
||||
|
||||
TEST(Crypto, sha1)
|
||||
|
|
1
third-party/wide-integer
vendored
Submodule
1
third-party/wide-integer
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 4de0b52ea939bada26fae7aef55a4d98eb1d8abb
|
Loading…
Reference in a new issue