refactor: remove tr_dh code (#3443)

Refactor the MSE handshake Diffie-Hellman key code.
This commit is contained in:
Charles Kerr 2022-07-14 19:54:10 -05:00 committed by GitHub
parent c3db52e310
commit 2bcab6be7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 517 additions and 1196 deletions

3
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
set(WIDE_INTEGER_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third-party/wide-integer)

View File

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

View File

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

View File

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

View File

@ -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*/)

View File

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

View File

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

View File

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

View File

@ -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.
*/

View File

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

View File

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

View File

@ -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())
{

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
#include <cerrno>
#include <future>
#include <mutex>
#include <string_view>
#include <string>
#include <thread>
#include <fmt/core.h>

View File

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

View File

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

View File

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

@ -0,0 +1 @@
Subproject commit 4de0b52ea939bada26fae7aef55a4d98eb1d8abb