Add support for SSL torrents

The 'SSL torrent' feature is not standardized. I.e. there are no BEP (BitTorrent Enhancement Proposals) associated with it, so we do not greatly encourage its usage as it will only work with libtorrent clients and derivatives. It will not work with other torrent clients that do not support the libtorrent specific implementation.
This PR aims to provide minimal support for those who need SSL torrents. Furthermore, it is intended that there will be no UI support (nor indication) of adding/creating SSL torrents.

* Prerequisites:
  I omit the instructions of creating those files as the intended audience (experts & advanced users) should have no problem with it. All files are as follow:
  1. Root (torrent publisher) certificate
  2. Root private key
  3. A .torrent file created with root certificate 
  5. Peer certificate (signed by the root certificate)
  6. Peer private key
  7. Diffie-Hellman parameters file
  
  All files are stored in .pem format.

* Enable SSL torrent protocol in qbt
  There are 2 hidden keys to put in qbt config file, under `[BitTorrent]` section:
  1. `Session\SSL\Enabled`: set it to `true`.
  2. `Session\SSL\Port`: set it to some unused port or omit the key entirely to let qbt pick one for you.
* Add an SSL torrent to qbt
  The only way of adding an SSL torrent is via WebAPI. The `/api/v2/torrents/add` endpoint will support 3 additional parameters. You must provide them for an SSL torrent.
  1. `ssl_certificate`: Contents of the peer certificate file (in PEM format).
  2. `ssl_private_key`: Contents of the peer private key file.
  3. `ssl_dh_params`: Contents of the Diffie-Hellman parameters file.

* Change the SSL parameters to a torrent
  In case you provided wrong SSL parameters when adding a torrent, there is a new endpoint `/api/v2/torrents/setSSLParameters` that you can update the SSL parameters. The parameters (`ssl_*`) are the same as `/api/v2/torrents/add` endpoint.

* Query the SSL parameters of a torrent
  There is a new endpoint `/api/v2/torrents/SSLParameters` that you can query the SSL parameters of a torrent.

References:
* https://www.libtorrent.org/manual-ref.html#ssl-torrents
* https://blog.libtorrent.org/2012/01/bittorrent-over-ssl/

PR #20338.
---------

Co-authored-by: Radu Carpa <radu.carpa@cern.ch>
This commit is contained in:
Chocobo1 2024-02-25 19:58:58 +08:00 committed by GitHub
parent c6ee0ff017
commit cffd74b62a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 482 additions and 101 deletions

View file

@ -479,6 +479,8 @@ SessionImpl::SessionImpl(QObject *parent)
, m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false)
, m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60)
, m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1)
, m_sslEnabled(BITTORRENT_SESSION_KEY(u"SSL/Enabled"_s), false)
, m_sslPort(BITTORRENT_SESSION_KEY(u"SSL/Port"_s), -1)
, m_networkInterface(BITTORRENT_SESSION_KEY(u"Interface"_s))
, m_networkInterfaceName(BITTORRENT_SESSION_KEY(u"InterfaceName"_s))
, m_networkInterfaceAddress(BITTORRENT_SESSION_KEY(u"InterfaceAddress"_s))
@ -529,6 +531,12 @@ SessionImpl::SessionImpl(QObject *parent)
if (port() < 0)
m_port = Utils::Random::rand(1024, 65535);
if (sslPort() < 0)
{
m_sslPort = Utils::Random::rand(1024, 65535);
while (m_sslPort == port())
m_sslPort = Utils::Random::rand(1024, 65535);
}
m_recentErroredTorrentsTimer->setSingleShot(true);
m_recentErroredTorrentsTimer->setInterval(1s);
@ -2022,7 +2030,9 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack
QStringList endpoints;
QStringList outgoingInterfaces;
const QString portString = u':' + QString::number(port());
QStringList portStrings = {u':' + QString::number(port())};
if (isSSLEnabled())
portStrings.append(u':' + QString::number(sslPort()) + u's');
for (const QString &ip : asConst(getListeningIPs()))
{
@ -2034,7 +2044,8 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack
? Utils::Net::canonicalIPv6Addr(addr).toString()
: addr.toString();
endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
for (const QString &portString : asConst(portStrings))
endpoints << ((isIPv6 ? (u'[' + ip + u']') : ip) + portString);
if ((ip != u"0.0.0.0") && (ip != u"::"))
outgoingInterfaces << ip;
@ -2049,7 +2060,8 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack
const QString guid = convertIfaceNameToGuid(ip);
if (!guid.isEmpty())
{
endpoints << (guid + portString);
for (const QString &portString : asConst(portStrings))
endpoints << (guid + portString);
outgoingInterfaces << guid;
}
else
@ -2057,11 +2069,13 @@ void SessionImpl::applyNetworkInterfacesSettings(lt::settings_pack &settingsPack
LogMsg(tr("Could not find GUID of network interface. Interface: \"%1\"").arg(ip), Log::WARNING);
// Since we can't get the GUID, we'll pass the interface name instead.
// Otherwise an empty string will be passed to outgoing_interface which will cause IP leak.
endpoints << (ip + portString);
for (const QString &portString : asConst(portStrings))
endpoints << (ip + portString);
outgoingInterfaces << ip;
}
#else
endpoints << (ip + portString);
for (const QString &portString : asConst(portStrings))
endpoints << (ip + portString);
outgoingInterfaces << ip;
#endif
}
@ -2640,6 +2654,7 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add
loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
loadTorrentParams.inactiveSeedingTimeLimit = addTorrentParams.inactiveSeedingTimeLimit;
loadTorrentParams.sslParameters = addTorrentParams.sslParameters;
const QString category = addTorrentParams.category;
if (!category.isEmpty() && !m_categories.contains(category) && !addCategory(category))
@ -3525,6 +3540,40 @@ void SessionImpl::setPort(const int port)
}
}
bool SessionImpl::isSSLEnabled() const
{
return m_sslEnabled;
}
void SessionImpl::setSSLEnabled(const bool enabled)
{
if (enabled == isSSLEnabled())
return;
m_sslEnabled = enabled;
configureListeningInterface();
if (isReannounceWhenAddressChangedEnabled())
reannounceToAllTrackers();
}
int SessionImpl::sslPort() const
{
return m_sslPort;
}
void SessionImpl::setSSLPort(const int port)
{
if (port == sslPort())
return;
m_sslPort = port;
configureListeningInterface();
if (isReannounceWhenAddressChangedEnabled())
reannounceToAllTrackers();
}
QString SessionImpl::networkInterface() const
{
return m_networkInterface;
@ -5449,6 +5498,9 @@ void SessionImpl::handleAlert(const lt::alert *a)
case lt::torrent_delete_failed_alert::alert_type:
handleTorrentDeleteFailedAlert(static_cast<const lt::torrent_delete_failed_alert *>(a));
break;
case lt::torrent_need_cert_alert::alert_type:
handleTorrentNeedCertAlert(static_cast<const lt::torrent_need_cert_alert *>(a));
break;
case lt::portmap_error_alert::alert_type:
handlePortmapWarningAlert(static_cast<const lt::portmap_error_alert *>(a));
break;
@ -5652,6 +5704,26 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
m_removingTorrents.erase(removingTorrentDataIter);
}
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *a)
{
#ifdef QBT_USES_LIBTORRENT2
const InfoHash infoHash {a->handle.info_hashes()};
#else
const InfoHash infoHash {a->handle.info_hash()};
#endif
const auto torrentID = TorrentID::fromInfoHash(infoHash);
TorrentImpl *const torrent = m_torrents.value(torrentID);
if (!torrent) [[unlikely]]
return;
if (!torrent->applySSLParameters())
{
LogMsg(tr("Torrent is missing SSL parameters. Torrent: \"%1\". Message: \"%2\"").arg(torrent->name(), QString::fromStdString(a->message()))
, Log::WARNING);
}
}
void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
{
const TorrentID torrentID {p->handle.info_hash()};