From 2bcab6be7e39920247dbf874a58c6d6a2f2d59fe Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 14 Jul 2022 19:54:10 -0500 Subject: [PATCH] refactor: remove tr_dh code (#3443) Refactor the MSE handshake Diffie-Hellman key code. --- .gitmodules | 3 + CMakeLists.txt | 1 + Transmission.xcodeproj/project.pbxproj | 25 ++- cmake/FindWideInteger.cmake | 1 + libtransmission/CMakeLists.txt | 7 +- libtransmission/crypto-utils-ccrypto.cc | 171 ------------------ libtransmission/crypto-utils-cyassl.cc | 121 ------------- libtransmission/crypto-utils-fallback.cc | 51 ------ libtransmission/crypto-utils-openssl.cc | 181 +------------------ libtransmission/crypto-utils-polarssl.cc | 97 ---------- libtransmission/crypto-utils.cc | 19 -- libtransmission/crypto-utils.h | 50 ------ libtransmission/crypto.cc | 148 --------------- libtransmission/crypto.h | 87 --------- libtransmission/handshake.cc | 219 ++++++++++------------- libtransmission/handshake.h | 10 +- libtransmission/peer-io.cc | 46 ++--- libtransmission/peer-io.h | 83 +++++---- libtransmission/peer-mgr.cc | 7 + libtransmission/peer-mse.cc | 174 ++++++++++++++++++ libtransmission/peer-mse.h | 89 +++++++++ libtransmission/peer-msgs.cc | 3 +- libtransmission/rpc-server.cc | 3 +- libtransmission/upnp.cc | 2 +- tests/libtransmission/CMakeLists.txt | 1 + tests/libtransmission/crypto-test-ref.h | 38 ---- tests/libtransmission/crypto-test.cc | 75 +++++--- third-party/wide-integer | 1 + 28 files changed, 517 insertions(+), 1196 deletions(-) create mode 100644 cmake/FindWideInteger.cmake delete mode 100644 libtransmission/crypto.cc delete mode 100644 libtransmission/crypto.h create mode 100644 libtransmission/peer-mse.cc create mode 100644 libtransmission/peer-mse.h create mode 160000 third-party/wide-integer diff --git a/.gitmodules b/.gitmodules index b0e6cff04..7d6488d18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c87bb36b..887908310 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 4c53ea03c..c68841da7 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -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 = ""; }; 4D364D9F091FBB2C00377D12 /* TorrentTableView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentTableView.mm; sourceTree = ""; }; - 4D36BA600CA2F00800A63CA5 /* crypto.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = crypto.cc; sourceTree = ""; }; - 4D36BA610CA2F00800A63CA5 /* crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = ""; }; + 4D36BA600CA2F00800A63CA5 /* peer-mse.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = peer-mse.cc; sourceTree = ""; }; + 4D36BA610CA2F00800A63CA5 /* peer-mse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = peer-mse.h; sourceTree = ""; }; 4D36BA630CA2F00800A63CA5 /* handshake.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = handshake.cc; sourceTree = ""; }; 4D36BA640CA2F00800A63CA5 /* handshake.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = handshake.h; sourceTree = ""; }; 4D36BA650CA2F00800A63CA5 /* peer-io.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-io.cc"; sourceTree = ""; }; @@ -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", diff --git a/cmake/FindWideInteger.cmake b/cmake/FindWideInteger.cmake new file mode 100644 index 000000000..a1cdc0658 --- /dev/null +++ b/cmake/FindWideInteger.cmake @@ -0,0 +1 @@ +set(WIDE_INTEGER_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third-party/wide-integer) diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index bb25a5e6c..33d3b1dfe 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -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} diff --git a/libtransmission/crypto-utils-ccrypto.cc b/libtransmission/crypto-utils-ccrypto.cc index e7e8ffa8a..13640667f 100644 --- a/libtransmission/crypto-utils-ccrypto.cc +++ b/libtransmission/crypto-utils-ccrypto.cc @@ -6,9 +6,6 @@ #include #include -#ifdef HAVE_COMMONCRYPTO_COMMONBIGNUM_H -#include -#endif #include #include @@ -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_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, 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(); - 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(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(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(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_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) diff --git a/libtransmission/crypto-utils-cyassl.cc b/libtransmission/crypto-utils-cyassl.cc index b5312a9c9..357c833a4 100644 --- a/libtransmission/crypto-utils-cyassl.cc +++ b/libtransmission/crypto-utils-cyassl.cc @@ -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_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(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(raw_handle); - - if (handle->private_key == nullptr) - { - handle->private_key = static_cast(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(*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(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) diff --git a/libtransmission/crypto-utils-fallback.cc b/libtransmission/crypto-utils-fallback.cc index ef7354c1e..e2282de39 100644 --- a/libtransmission/crypto-utils-fallback.cc +++ b/libtransmission/crypto-utils-fallback.cc @@ -7,66 +7,15 @@ implement missing (or duplicate) functionality without exposing internal details in header files. */ -#include - #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(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_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(raw_handle); - - return tr_sha1( - std::string_view{ static_cast(prepend_data), prepend_data_size }, - std::string_view{ reinterpret_cast(handle->key), handle->key_length }, - std::string_view{ static_cast(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*/) diff --git a/libtransmission/crypto-utils-openssl.cc b/libtransmission/crypto-utils-openssl.cc index 23dbc76ef..ec86d0efe 100644 --- a/libtransmission/crypto-utils-openssl.cc +++ b/libtransmission/crypto-utils-openssl.cc @@ -8,9 +8,7 @@ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif -#include #include -#include #include #include #include @@ -21,14 +19,12 @@ #include #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(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(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(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) diff --git a/libtransmission/crypto-utils-polarssl.cc b/libtransmission/crypto-utils-polarssl.cc index 62190cf5d..f8bb629cf 100644 --- a/libtransmission/crypto-utils-polarssl.cc +++ b/libtransmission/crypto-utils-polarssl.cc @@ -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_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(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(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(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) diff --git a/libtransmission/crypto-utils.cc b/libtransmission/crypto-utils.cc index 0d7b7ab3f..253a3015b 100644 --- a/libtransmission/crypto-utils.cc +++ b/libtransmission/crypto-utils.cc @@ -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); diff --git a/libtransmission/crypto-utils.h b/libtransmission/crypto-utils.h index e0b624a70..7d87f9a68 100644 --- a/libtransmission/crypto-utils.h +++ b/libtransmission/crypto-utils.h @@ -6,7 +6,6 @@ #ifndef TR_CRYPTO_UTILS_H #define TR_CRYPTO_UTILS_H -#include // uint8_t #include // size_t #include #include @@ -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(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_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. */ diff --git a/libtransmission/crypto.cc b/libtransmission/crypto.cc deleted file mode 100644 index cb11d1e42..000000000 --- a/libtransmission/crypto.cc +++ /dev/null @@ -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 /* memcpy(), memmove(), memset() */ - -#include - -#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(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_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 tr_crypto::pad(size_t maxlen) const -{ - auto const len = tr_rand_int(maxlen); - auto ret = std::vector{}; - ret.resize(len); - tr_rand_buffer(std::data(ret), len); - return ret; -} diff --git a/libtransmission/crypto.h b/libtransmission/crypto.h deleted file mode 100644 index 32707b8f6..000000000 --- a/libtransmission/crypto.h +++ /dev/null @@ -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 // size_t -#include // uint8_t -#include -#include -#include - -#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(my_public_key_), KEY_LEN }; - } - - [[nodiscard]] bool computeSecret(void const* peer_public_key, size_t len); - - [[nodiscard]] std::optional 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 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 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 diff --git a/libtransmission/handshake.cc b/libtransmission/handshake.cc index ea5679226..979c80947 100644 --- a/libtransmission/handshake.cc +++ b/libtransmission/handshake.cc @@ -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; +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 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 +static void sendPublicKeyAndPad(tr_handshake* handshake) +{ + auto const public_key = handshake->dh.publicKey(); + auto outbuf = std::array{}; + 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(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(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(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(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()) { diff --git a/libtransmission/handshake.h b/libtransmission/handshake.h index d80aed9b5..b40767e3f 100644 --- a/libtransmission/handshake.h +++ b/libtransmission/handshake.h @@ -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; }; diff --git a/libtransmission/peer-io.cc b/libtransmission/peer-io.cc index 595820eb2..09e6a1294 100644 --- a/libtransmission/peer-io.cc +++ b/libtransmission/peer-io.cc @@ -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); } } diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index 76beb067a..470664b4d 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -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; 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 filter_; + + Filter& filter() + { + if (!filter_) + { + filter_ = std::make_unique(); + } + + return *filter_; + } + + std::optional 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); diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 123fa37cd..608a52bb3 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -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_; }; diff --git a/libtransmission/peer-mse.cc b/libtransmission/peer-mse.cc new file mode 100644 index 000000000..94d281b2b --- /dev/null +++ b/libtransmission/peer-mse.cc @@ -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 +#include + +#include + +#include + +#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::digits>; + +using private_key_t = math::wide_integer::uintwide_t< + tr_message_stream_encryption::DH::PrivateKeySize * std::numeric_limits::digits>; + +template +auto import_bits(std::array::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(*walk); + } + } + else + { + for (auto const walk : bigend_bin) + { + ret <<= 8; + ret += static_cast(walk); + } + } + + return ret; +} + +template +auto export_bits(UIntWide i) +{ + auto ret = std::array::digits>{}; + + if (is_big_endian()) + { + for (auto& walk : ret) + { + walk = std::byte(static_cast(i & 0xFF)); + i >>= 8; + } + } + else + { + for (auto walk = std::rbegin(ret), end = std::rend(ret); walk != end; ++walk) + { + *walk = std::byte(static_cast(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(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(peer_public_key), + wi::import_bits(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(); + 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(); + 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 diff --git a/libtransmission/peer-mse.h b/libtransmission/peer-mse.h new file mode 100644 index 000000000..a041277d7 --- /dev/null +++ b/libtransmission/peer-mse.h @@ -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 // size_t, std::byte +#include + +#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; + using key_bigend_t = std::array; + + // 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 dec_key_; + std::shared_ptr enc_key_; +}; + +} // namespace tr_message_stream_encryption + +#endif // TR_ENCRYPTION_H diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 3809b1cf0..d397c36ca 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -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 diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index 44ee3529e..9eab37c49 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -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" diff --git a/libtransmission/upnp.cc b/libtransmission/upnp.cc index 64e784b8a..7cfe5b99f 100644 --- a/libtransmission/upnp.cc +++ b/libtransmission/upnp.cc @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 48bc3615a..f4ba81c61 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -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}) diff --git a/tests/libtransmission/crypto-test-ref.h b/tests/libtransmission/crypto-test-ref.h index 8f94e931d..74ba76fc1 100644 --- a/tests/libtransmission/crypto-test-ref.h +++ b/tests/libtransmission/crypto-test-ref.h @@ -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 diff --git a/tests/libtransmission/crypto-test.cc b/tests/libtransmission/crypto-test.cc index 387e55b87..15b387b74 100644 --- a/tests/libtransmission/crypto-test.cc +++ b/tests/libtransmission/crypto-test.cc @@ -5,14 +5,16 @@ #include #include +#include #include +#include #include #include #include #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 +std::string toString(std::array const& array) +{ + auto ostr = std::ostringstream{}; + ostr << '['; + for (auto const b : array) + { + ostr << static_cast(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{}; auto decrypted1 = std::array{}; - 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{}; auto decrypted2 = std::array{}; - 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) diff --git a/third-party/wide-integer b/third-party/wide-integer new file mode 160000 index 000000000..4de0b52ea --- /dev/null +++ b/third-party/wide-integer @@ -0,0 +1 @@ +Subproject commit 4de0b52ea939bada26fae7aef55a4d98eb1d8abb