Add basic support for v2 hashes in transmission-show (#3380)

* Add basic support for v2 hashes in transmission-show

* Add sha256 for more libraries

* Fix issue with sha256 digest length
* Add sha256 for polarssl
    Note: Bumping miniumum PolarSSL version to 1.3 because of
    sha2->sha256 name change.
* Add sha256 for CommonCrypto/ccrypto
* Add sha256 for cyassl
This commit is contained in:
Colin B
2022-07-01 07:49:33 -07:00
committed by GitHub
parent 85a7d0e09d
commit 3237323b8e
22 changed files with 399 additions and 7 deletions

View File

@@ -124,7 +124,7 @@ set(GLIB_MINIMUM 2.50.1)
set(GTK_MINIMUM 3.24.0)
set(LIBAPPINDICATOR_MINIMUM 0.4.90)
set(OPENSSL_MINIMUM 0.9.7)
set(POLARSSL_MINIMUM 1.2)
set(POLARSSL_MINIMUM 1.3)
set(QT_MINIMUM 5.6)
if(WIN32)

View File

@@ -179,6 +179,45 @@ std::optional<tr_sha1_digest_t> tr_sha1_final(tr_sha1_ctx_t raw_handle)
****
***/
tr_sha256_ctx_t tr_sha256_init(void)
{
auto* handle = new CC_SHA256_CTX();
CC_SHA256_Init(handle);
return handle;
}
bool tr_sha256_update(tr_sha256_ctx_t handle, void const* data, size_t data_length)
{
TR_ASSERT(handle != nullptr);
if (data_length == 0)
{
return true;
}
TR_ASSERT(data != nullptr);
CC_SHA256_Update(static_cast<CC_SHA256_CTX*>(handle), data, data_length);
return true;
}
std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
{
TR_ASSERT(raw_handle != nullptr);
auto* handle = static_cast<CC_SHA256_CTX*>(raw_handle);
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
CC_SHA256_Final(digest_as_uchar, handle);
delete handle;
return digest;
}
/***
****
***/
namespace
{

View File

@@ -22,6 +22,7 @@
#include API_HEADER_CRYPT(error-crypt.h)
#include API_HEADER_CRYPT(random.h)
#include API_HEADER_CRYPT(sha.h)
#include API_HEADER_CRYPT(sha256.h)
#include API_HEADER(version.h)
#include <fmt/core.h>
@@ -160,6 +161,51 @@ std::optional<tr_sha1_digest_t> tr_sha1_final(tr_sha1_ctx_t raw_handle)
****
***/
tr_sha256_ctx_t tr_sha256_init(void)
{
Sha256* handle = tr_new(Sha256, 1);
if (check_result(API(InitSha256)(handle)))
{
return handle;
}
tr_free(handle);
return nullptr;
}
bool tr_sha256_update(tr_sha256_ctx_t raw_handle, void const* data, size_t data_length)
{
auto* handle = static_cast<Sha256*>(raw_handle);
TR_ASSERT(handle != nullptr);
if (data_length == 0)
{
return true;
}
TR_ASSERT(data != nullptr);
return check_result(API(Sha256Update)(handle, static_cast<byte const*>(data), data_length));
}
std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
{
auto* handle = static_cast<Sha256*>(raw_handle);
TR_ASSERT(handle != nullptr);
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
auto const ok = check_result(API(Sha256Final)(handle, digest_as_uchar));
tr_free(handle);
return ok ? std::make_optional(digest) : std::nullopt;
}
/***
****
***/
tr_dh_ctx_t tr_dh_new(
uint8_t const* prime_num,
size_t prime_num_length,

View File

@@ -154,6 +154,54 @@ std::optional<tr_sha1_digest_t> tr_sha1_final(tr_sha1_ctx_t raw_handle)
****
***/
tr_sha256_ctx_t tr_sha256_init()
{
EVP_MD_CTX* handle = EVP_MD_CTX_create();
if (check_result(EVP_DigestInit_ex(handle, EVP_sha256(), nullptr)))
{
return handle;
}
EVP_MD_CTX_destroy(handle);
return nullptr;
}
bool tr_sha256_update(tr_sha256_ctx_t raw_handle, void const* data, size_t data_length)
{
auto* const handle = static_cast<EVP_MD_CTX*>(raw_handle);
TR_ASSERT(handle != nullptr);
if (data_length == 0)
{
return true;
}
TR_ASSERT(data != nullptr);
return check_result(EVP_DigestUpdate(handle, data, data_length));
}
std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha1_ctx_t raw_handle)
{
auto* handle = static_cast<EVP_MD_CTX*>(raw_handle);
TR_ASSERT(handle != nullptr);
unsigned int hash_length = 0;
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
bool const ok = check_result(EVP_DigestFinal_ex(handle, digest_as_uchar, &hash_length));
TR_ASSERT(!ok || hash_length == std::size(digest));
EVP_MD_CTX_destroy(handle);
return ok ? std::make_optional(digest) : std::nullopt;
}
/***
****
***/
#if OPENSSL_VERSION_NUMBER < 0x0090802fL
static EVP_CIPHER_CTX* openssl_evp_cipher_context_new(void)

View File

@@ -20,6 +20,7 @@
#include API_HEADER(dhm.h)
#include API_HEADER(error.h)
#include API_HEADER(sha1.h)
#include API_HEADER(sha256.h)
#include API_HEADER(version.h)
#include <fmt/core.h>
@@ -40,6 +41,7 @@
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)
@@ -178,6 +180,54 @@ std::optional<tr_sha1_digest_t> tr_sha1_final(tr_sha1_ctx_t raw_handle)
****
***/
tr_sha256_ctx_t tr_sha256_init(void)
{
api_sha256_context* handle = tr_new0(api_sha256_context, 1);
#if API_VERSION_NUMBER >= 0x01030800
API(sha256_init)(handle);
#endif
API(sha256_starts)(handle, 0);
return handle;
}
bool tr_sha256_update(tr_sha256_ctx_t raw_handle, void const* data, size_t data_length)
{
auto* handle = static_cast<api_sha256_context*>(raw_handle);
TR_ASSERT(handle != nullptr);
if (data_length == 0)
{
return true;
}
TR_ASSERT(data != nullptr);
API(sha256_update)(handle, static_cast<unsigned char const*>(data), data_length);
return true;
}
std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t raw_handle)
{
auto* handle = static_cast<api_sha256_context*>(raw_handle);
TR_ASSERT(handle != nullptr);
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
API(sha256_finish)(handle, digest_as_uchar);
#if API_VERSION_NUMBER >= 0x01030800
API(sha256_free)(handle);
#endif
tr_free(handle);
return digest;
}
/***
****
***/
tr_dh_ctx_t tr_dh_new(
uint8_t const* prime_num,
size_t prime_num_length,

View File

@@ -205,6 +205,13 @@ std::string tr_sha1_to_string(tr_sha1_digest_t const& digest)
return str;
}
std::string tr_sha256_to_string(tr_sha256_digest_t const& digest)
{
auto str = std::string(std::size(digest) * 2, '?');
tr_binary_to_hex(std::data(digest), std::data(str), std::size(digest));
return str;
}
static void tr_hex_to_binary(char const* input, void* voutput, size_t byte_length)
{
static char constexpr Hex[] = "0123456789abcdef";
@@ -235,3 +242,20 @@ std::optional<tr_sha1_digest_t> tr_sha1_from_string(std::string_view hex)
tr_hex_to_binary(std::data(hex), std::data(digest), std::size(digest));
return digest;
}
std::optional<tr_sha256_digest_t> tr_sha256_from_string(std::string_view hex)
{
if (std::size(hex) != TR_SHA256_DIGEST_STRLEN)
{
return {};
}
if (!std::all_of(std::begin(hex), std::end(hex), [](unsigned char ch) { return isxdigit(ch); }))
{
return {};
}
auto digest = tr_sha256_digest_t{};
tr_hex_to_binary(std::data(hex), std::data(digest), std::size(digest));
return digest;
}

View File

@@ -21,6 +21,8 @@
/** @brief Opaque SHA1 context type. */
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. */
@@ -70,6 +72,44 @@ std::optional<tr_sha1_digest_t> tr_sha1(T... args)
return std::nullopt;
}
/**
* @brief Allocate and initialize new SHA256 hasher context.
*/
tr_sha256_ctx_t tr_sha256_init(void);
/**
* @brief Update SHA256 hash.
*/
bool tr_sha256_update(tr_sha256_ctx_t handle, void const* data, size_t data_length);
/**
* @brief Finalize and export SHA256 hash, free hasher context.
*/
std::optional<tr_sha256_digest_t> tr_sha256_final(tr_sha256_ctx_t handle);
/**
* @brief generate a SHA256 hash from some memory
*/
template<typename... T>
std::optional<tr_sha256_digest_t> tr_sha256(T... args)
{
auto ctx = tr_sha256_init();
if (ctx == nullptr)
{
return std::nullopt;
}
if ((tr_sha256_update(ctx, std::data(args), std::size(args)) && ...))
{
return tr_sha256_final(ctx);
}
// one of the update() calls failed so we will return nullopt,
// but we need to call final() first to ensure ctx is released
tr_sha256_final(ctx);
return std::nullopt;
}
/**
* @brief Allocate and initialize new Diffie-Hellman (DH) key exchange context.
*/
@@ -186,10 +226,20 @@ std::string tr_base64_decode(std::string_view input);
std::string tr_sha1_to_string(tr_sha1_digest_t const&);
/**
* @brief Generate a sha1 digest from a hex string.
* @brief Generate a sha256 digest from a hex string.
*/
std::optional<tr_sha1_digest_t> tr_sha1_from_string(std::string_view hex);
/**
* @brief Generate an ascii hex string for a sha256 digest.
*/
std::string tr_sha256_to_string(tr_sha256_digest_t const&);
/**
* @brief Generate a sha256 digest from a hex string.
*/
std::optional<tr_sha256_digest_t> tr_sha256_from_string(std::string_view hex);
/** @} */
#endif /* TR_CRYPTO_UTILS_H */

View File

@@ -151,6 +151,19 @@ std::optional<tr_sha1_digest_t> parseHash(std::string_view sv)
return {};
}
std::optional<tr_sha256_digest_t> parseHash2(std::string_view sv)
{
// http://bittorrent.org/beps/bep_0009.html
// Is the info-hash v2 hex encoded and tag removed, for a total of 64 characters.
if (auto const hash = tr_sha256_from_string(sv); hash)
{
return hash;
}
return {};
}
} // namespace
/***
@@ -237,12 +250,22 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
}
else if (static auto constexpr ValPrefix = "urn:btih:"sv; key == "xt"sv && tr_strvStartsWith(value, ValPrefix))
{
// v1 info-hash
if (auto const hash = parseHash(value.substr(std::size(ValPrefix))); hash)
{
this->info_hash_ = *hash;
got_hash = true;
}
}
else if (static auto constexpr ValPrefix2 = "urn:btmh:1220"sv; key == "xt"sv && tr_strvStartsWith(value, ValPrefix2))
{
// v2 info-hash
// The 1220 tag identifies the hash as sha256, removing tag before sending to parseHash2
if (auto const hash = parseHash2(value.substr(std::size(ValPrefix2))); hash)
{
this->info_hash2_ = *hash;
}
}
}
info_hash_str_ = tr_sha1_to_string(this->infoHash());

View File

@@ -62,6 +62,11 @@ public:
return info_hash_str_;
}
[[nodiscard]] constexpr std::string const& infoHash2String() const noexcept
{
return info_hash2_str_;
}
void setName(std::string_view name)
{
name_ = name;
@@ -73,6 +78,8 @@ protected:
tr_announce_list announce_list_;
std::vector<std::string> webseed_urls_;
tr_sha1_digest_t info_hash_ = {};
tr_sha256_digest_t info_hash2_ = {};
std::string info_hash_str_;
std::string info_hash2_str_;
std::string name_;
};

View File

@@ -344,6 +344,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{
// currently unused. TODO support for bittorrent v2
// TODO https://github.com/transmission/transmission/issues/458
tm_.is_v2_ = value == 2;
}
else if (
pathIs(DurationKey) || //
@@ -582,6 +583,7 @@ private:
char const* const end = &context.raw().back() + 1;
auto const info_dict_benc = std::string_view{ begin, size_t(end - begin) };
auto const hash = tr_sha1(info_dict_benc);
auto const hash2 = tr_sha256(info_dict_benc);
if (!hash)
{
tr_error_set(context.error, EINVAL, "bad info_dict checksum");
@@ -590,6 +592,8 @@ private:
tm_.info_hash_ = *hash;
tm_.info_hash_str_ = tr_sha1_to_string(tm_.info_hash_);
tm_.info_hash2_ = *hash2;
tm_.info_hash2_str_ = tr_sha256_to_string(tm_.info_hash2_);
tm_.info_dict_size_ = std::size(info_dict_benc);
return true;
}

View File

@@ -130,6 +130,18 @@ public:
[[nodiscard]] tr_sha1_digest_t const& pieceHash(tr_piece_index_t piece) const;
[[nodiscard]] bool hasV1Metadata() const
{
// need 'pieces' field and 'files' or 'length'
// TODO check for 'files' or 'length'
return pieces_.size() > 0;
}
[[nodiscard]] bool hasV2Metadata() const
{
return is_v2_;
}
[[nodiscard]] constexpr auto const& dateCreated() const noexcept
{
return date_created_;
@@ -231,4 +243,5 @@ private:
uint64_t pieces_offset_ = 0;
bool is_private_ = false;
bool is_v2_ = false;
};

View File

@@ -103,3 +103,8 @@ auto inline constexpr TR_SHA1_DIGEST_LEN = size_t{ 20 };
auto inline constexpr TR_SHA1_DIGEST_STRLEN = size_t{ 40 };
using tr_sha1_digest_t = std::array<std::byte, TR_SHA1_DIGEST_LEN>;
using tr_sha1_digest_string_t = std::array<char, TR_SHA1_DIGEST_STRLEN + 1>; // +1 for '\0'
auto inline constexpr TR_SHA256_DIGEST_LEN = size_t{ 32 };
auto inline constexpr TR_SHA256_DIGEST_STRLEN = size_t{ 64 };
using tr_sha256_digest_t = std::array<std::byte, TR_SHA256_DIGEST_LEN>;
using tr_sha256_digest_string_t = std::array<char, TR_SHA256_DIGEST_STRLEN + 1>; // +1 for '\0'

View File

@@ -58,6 +58,13 @@
#define tr_base64_decode_impl tr_base64_decode_impl_
#define tr_sha1_to_string tr_sha1_to_string_
#define tr_sha1_from_string tr_sha1_from_string_
#define tr_sha256 tr_sha256_
#define tr_sha256_ctx_t tr_sha256_ctx_t_
#define tr_sha256_final tr_sha256_final_
#define tr_sha256_from_string tr_sha256_from_string_
#define tr_sha256_init tr_sha256_init_
#define tr_sha256_to_string tr_sha256_to_string_
#define tr_sha256_update tr_sha256_update_
#undef TR_ENCRYPTION_H
#undef TR_CRYPTO_UTILS_H
@@ -116,6 +123,13 @@
#undef tr_base64_decode_impl
#undef tr_sha1_to_string
#undef tr_sha1_from_string
#undef tr_sha256
#undef tr_sha256_ctx_t
#undef tr_sha256_final
#undef tr_sha256_from_string
#undef tr_sha256_init
#undef tr_sha256_to_string
#undef tr_sha256_update
#else /* CRYPTO_REFERENCE_CHECK */
@@ -167,6 +181,13 @@
#define tr_base64_decode_impl_ tr_base64_decode_impl
#define tr_sha1_to_string_ tr_sha1_to_string
#define tr_sha1_from_string_ tr_sha1_from_string
#define tr_sha256_ tr_sha256
#define tr_sha256_ctx_t_ tr_sha256_ctx_t
#define tr_sha256_final_ tr_sha256_final
#define tr_sha256_from_string_ tr_sha256_from_string
#define tr_sha256_init_ tr_sha256_init
#define tr_sha256_to_string_ tr_sha256_to_string
#define tr_sha256_update_ tr_sha256_update
#endif /* CRYPTO_REFERENCE_CHECK */

View File

@@ -199,6 +199,30 @@ TEST(Crypto, sha1FromString)
EXPECT_EQ(*lc, *uc);
}
TEST(Crypto, sha256FromString)
{
// bad lengths
EXPECT_FALSE(tr_sha256_from_string(""));
EXPECT_FALSE(tr_sha256_from_string("a94a8fe5ccb19ba61c4c0873d391e987982fbbd"sv));
EXPECT_FALSE(tr_sha256_from_string("a94a8fe5ccb19ba61c4c0873d391e987982fbbd33"sv));
EXPECT_FALSE(tr_sha256_from_string("05d58dfd14ed21d33add137eb7a2c5d4ef5aaa4a945e654363d32b7c4bf5c92"sv));
EXPECT_FALSE(tr_sha256_from_string("05d58dfd14ed21d33add137eb7a2c5d4ef5aaa4a945e654363d32b7c4bf5c9299"sv));
// nonhex
EXPECT_FALSE(tr_sha256_from_string("a94a8fe5ccb19ba61c4cz873d391e987982fbbd3aaaaaaaaaaaaaaaaaaaaaaa"sv));
EXPECT_FALSE(tr_sha256_from_string("05 8dfd14ed21d33add137eb7a2c5d4ef5aaa4a945e654363d32b7c4bf5c92"sv));
// lowercase hex
auto const baseline = "05d58dfd14ed21d33add137eb7a2c5d4ef5aaa4a945e654363d32b7c4bf5c929"sv;
auto const lc = tr_sha256_from_string(baseline);
EXPECT_TRUE(lc);
EXPECT_EQ(baseline, tr_sha256_to_string(*lc));
// uppercase hex should yield the same result
auto const uc = tr_sha256_from_string(tr_strupper(baseline));
EXPECT_TRUE(uc);
EXPECT_EQ(*lc, *uc);
}
TEST(Crypto, random)
{
/* test that tr_rand_int() stays in-bounds */

View File

@@ -11,6 +11,7 @@ function(AddShowTest name file_basename)
endfunction()
AddShowTest(transmission-show-bittorrent-v2 bittorrent-v2-test)
AddShowTest(transmission-show-bittorrent-v2-hybrid-test bittorrent-v2-hybrid-test)
AddShowTest(transmission-show-inner-sanctum Inner_Sanctum_movie_archive)
AddShowTest(transmission-show-thor Thor_and_the_Amazon_Women.avi)
AddShowTest(transmission-show-ubuntu ubuntu-20.04.3-desktop-amd64.iso)

View File

@@ -4,7 +4,7 @@ File: assets/Inner_Sanctum_movie_archive.torrent
GENERAL
Name: Inner_Sanctum_movie
Hash: f735d5be075bdb8832e29c96e80e53fdace6178b
Hash v1: f735d5be075bdb8832e29c96e80e53fdace6178b
Created by: ia_make_torrent
Created on: Sat Oct 27 00:56:24 2018

View File

@@ -4,7 +4,7 @@ File: assets/Thor_and_the_Amazon_Women.avi.torrent
GENERAL
Name: Thor_and_the_Amazon_Women.avi
Hash: 43fdd3e72588e9119c9c94c54332ee533cf80bd6
Hash v1: 43fdd3e72588e9119c9c94c54332ee533cf80bd6
Created by: Azureus/2.5.0.2
Created on: Mon Jan 22 00:57:20 2007

View File

@@ -0,0 +1,30 @@
Name: bittorrent-v1-v2-hybrid-test
File: assets/bittorrent-v2-hybrid-test.torrent
GENERAL
Name: bittorrent-v1-v2-hybrid-test
Hash v1: 631a31dd0a46257d5078c0dee4e66e26f73e42ac
Hash v2: d8dd32ac93357c368556af3ac1d95c9d76bd0dff6fa9833ecdac3d53134efabb
Created by: libtorrent
Created on: Wed Jun 03 08:45:06 2020
Piece Count: 1709
Piece Size: 512.0 KiB
Total Size: 895.5 MB
Privacy: Public torrent
TRACKERS
FILES
bittorrent-v1-v2-hybrid-test/Darkroom (Stellar, 1994, Amiga ECS) HQ.mp4 (6.54 MB)
bittorrent-v1-v2-hybrid-test/Spaceballs-StateOfTheArt.avi (20.51 MB)
bittorrent-v1-v2-hybrid-test/cncd_fairlight-ceasefire_(all_falls_down)-1080p.mp4 (342.2 MB)
bittorrent-v1-v2-hybrid-test/eld-dust.mkv (61.64 MB)
bittorrent-v1-v2-hybrid-test/fairlight_cncd-agenda_circling_forth-1080p30lq.mp4 (277.9 MB)
bittorrent-v1-v2-hybrid-test/meet the deadline - Still _ Evoke 2014.mp4 (44.58 MB)
bittorrent-v1-v2-hybrid-test/readme.txt (0.06 kB)
bittorrent-v1-v2-hybrid-test/tbl-goa.avi (26.30 MB)
bittorrent-v1-v2-hybrid-test/tbl-tint.mpg (115.9 MB)

Binary file not shown.

View File

@@ -4,7 +4,7 @@ File: assets/bittorrent-v2-test.torrent
GENERAL
Name: bittorrent-v2-test
Hash: f987ab6bb50f831a861c3754ecd1b47dc2cf2e30
Hash v2: caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e
Created by: libtorrent
Created on: Thu May 21 21:40:57 2020

View File

@@ -4,7 +4,7 @@ File: assets/ubuntu-20.04.3-desktop-amd64.iso.torrent
GENERAL
Name: ubuntu-20.04.3-desktop-amd64.iso
Hash: b26c81363ac1a236765385a702aec107a49581b5
Hash v1: b26c81363ac1a236765385a702aec107a49581b5
Created by: mktorrent 1.1
Created on: Thu Aug 26 09:42:51 2021

View File

@@ -193,7 +193,14 @@ void showInfo(app_opts const& opts, tr_torrent_metainfo const& metainfo)
{
printf("GENERAL\n\n");
printf(" Name: %s\n", metainfo.name().c_str());
printf(" Hash: %" TR_PRIsv "\n", TR_PRIsv_ARG(metainfo.infoHashString()));
if (metainfo.hasV1Metadata())
{
printf(" Hash v1: %" TR_PRIsv "\n", TR_PRIsv_ARG(metainfo.infoHashString()));
}
if (metainfo.hasV2Metadata())
{
printf(" Hash v2: %" TR_PRIsv "\n", TR_PRIsv_ARG(metainfo.infoHash2String()));
}
printf(" Created by: %s\n", std::empty(metainfo.creator()) ? "Unknown" : metainfo.creator().c_str());
printf(" Created on: %s\n\n", toString(metainfo.dateCreated()).c_str());