changeset 4000:37edf7ac9d32

Add SUDP, encrypted active search results feature
author eMTee <emtee11@gmail.com>
date Fri, 14 Jun 2024 10:33:05 +0200
parents 2cfa78679f8e
children caac2855dec2
files changelog.txt dcpp/AdcHub.cpp dcpp/AdcHub.h dcpp/Client.h dcpp/ClientManager.cpp dcpp/ClientManager.h dcpp/CryptoManager.cpp dcpp/CryptoManager.h dcpp/NmdcHub.cpp dcpp/NmdcHub.h dcpp/SearchManager.cpp dcpp/SearchManager.h dcpp/SettingsManager.cpp dcpp/SettingsManager.h help/settings_certs.html win32/CertificatesPage.cpp
diffstat 16 files changed, 198 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/changelog.txt	Sat May 25 13:59:07 2024 +0200
+++ b/changelog.txt	Fri Jun 14 10:33:05 2024 +0200
@@ -1,3 +1,4 @@
+* [L#2066921] [ADC] Add encrypted active search results feature (SUDP) (emtee, iceman50, based on code from AirDC++)
 * Improve find text in chats, add search context menu commands to various windows with text boxes (emtee)
 * Improve randomization (iceman50)
 * Add option to disable window previews in the taskbar (this will disable taskbar overlay icons) (iceman50)
--- a/dcpp/AdcHub.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/AdcHub.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -54,6 +54,7 @@
 const string AdcHub::NAT0_FEATURE("NAT0");
 const string AdcHub::SEGA_FEATURE("SEGA");
 const string AdcHub::CCPM_FEATURE("CCPM");
+const string AdcHub::SUDP_FEATURE("SUDP");
 const string AdcHub::BASE_SUPPORT("ADBASE");
 const string AdcHub::BAS0_SUPPORT("ADBAS0");
 const string AdcHub::TIGR_SUPPORT("ADTIGR");
@@ -778,7 +779,7 @@
 	return ret;
 }
 
-void AdcHub::search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList) {
+void AdcHub::search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey) {
 	if(state != STATE_NORMAL)
 		return;
 
@@ -881,6 +882,11 @@
 			c.addParam("EX", i);
 	}
 
+	// Verify that it is an active ADCS hub
+	if(SETTING(ENABLE_SUDP) && !aKey.empty() && ClientManager::getInstance()->isActive() && isSecure()) {
+		c.addParam("KY", aKey);
+	}
+
 	sendSearch(c);
 }
 
@@ -1026,6 +1032,10 @@
 		addParam(lastInfoMap, c, "KP", CryptoManager::getInstance()->keyprintToString(kp));
 	}
 
+	if(SETTING(ENABLE_SUDP) && isSecure()) {
+		su += "," + SUDP_FEATURE;
+	}
+
 	bool addV4 = !sock->isV6Valid() || (get(HubSettings::Connection) != SettingsManager::INCOMING_DISABLED);
 	bool addV6 = sock->isV6Valid() || (get(HubSettings::Connection6) != SettingsManager::INCOMING_DISABLED);
 	
--- a/dcpp/AdcHub.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/AdcHub.h	Fri Jun 14 10:33:05 2024 +0200
@@ -39,7 +39,7 @@
 	virtual void hubMessage(const string& aMessage, bool thirdPerson = false);
 	virtual void privateMessage(const OnlineUser& user, const string& aMessage, bool thirdPerson = false);
 	virtual void sendUserCmd(const UserCommand& command, const ParamMap& params);
-	virtual void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList);
+	virtual void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey = Util::emptyString);
 	virtual void password(const string& pwd);
 	virtual void infoImpl();
 
@@ -71,6 +71,7 @@
 	static const string UCM0_SUPPORT;
 	static const string BLO0_SUPPORT;
 	static const string ZLIF_SUPPORT;
+	static const string SUDP_FEATURE;
 
 private:
 	friend class ClientManager;
--- a/dcpp/Client.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/Client.h	Fri Jun 14 10:33:05 2024 +0200
@@ -52,7 +52,7 @@
 	virtual void hubMessage(const string& aMessage, bool thirdPerson = false) = 0;
 	virtual void privateMessage(const OnlineUser& user, const string& aMessage, bool thirdPerson = false) = 0;
 	virtual void sendUserCmd(const UserCommand& command, const ParamMap& params) = 0;
-	virtual void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList) = 0;
+	virtual void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey) = 0;
 	virtual void password(const string& pwd) = 0;
 	/** Send new information about oneself. Thread-safe. */
 	void info();
--- a/dcpp/ClientManager.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/ClientManager.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -445,7 +445,7 @@
 	ou->getClient().sendUserCmd(uc, params);
 }
 
-void ClientManager::sendUDP(AdcCommand& cmd, const OnlineUser& user) {
+void ClientManager::sendUDP(AdcCommand& cmd, const OnlineUser& user, const string& aKey) {
 	dcassert(cmd.getType() == AdcCommand::TYPE_UDP);
 	if(!user.getIdentity().isUdpActive()) {
 		cmd.setType(AdcCommand::TYPE_DIRECT);
@@ -453,16 +453,24 @@
 		const_cast<Client&>(user.getClient()).send(cmd);
 	} else {
 		auto v6Status = ConnectivityManager::getInstance()->getConnectivityStatus(true);
-		sendUDP(v6Status ? user.getIdentity().getIp() : user.getIdentity().getIp4(), v6Status ? user.getIdentity().getUdpPort() : user.getIdentity().getUdp4Port(), cmd.toString(getMe()->getCID()));
+		sendUDP(v6Status ? user.getIdentity().getIp() : user.getIdentity().getIp4(), v6Status ? user.getIdentity().getUdpPort() : user.getIdentity().getUdp4Port(), cmd.toString(getMe()->getCID()), aKey);
 	}
 }
 
-void ClientManager::sendUDP(const string& ip, const string& port, const string& data) {
+void ClientManager::sendUDP(const string& ip, const string& port, const string& data, const string& aKey) {
 	if(PluginManager::getInstance()->onUDP(true, ip, port, data))
 		return;
 
+	string encryptedData;
+
+	if(SETTING(ENABLE_SUDP) && !aKey.empty() && Encoder::isBase32(aKey.c_str())) {
+		uint8_t keyChar[16];
+		Encoder::fromBase32(aKey.c_str(), keyChar, 16);
+		encryptedData = CryptoManager::getInstance()->encryptSUDP(keyChar, data);
+	}
+
 	try {
-		udp.writeTo(ip, port, data);
+		udp.writeTo(ip, port, encryptedData.empty() ? data : encryptedData);
 	} catch(const SocketException&) {
 		dcdebug("Socket exception when sending UDP data to %s:%s\n", ip.c_str(), port.c_str());
 	}
@@ -527,23 +535,23 @@
 	SearchManager::getInstance()->respond(cmd, from);
 }
 
-void ClientManager::search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken) {
+void ClientManager::search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const string& aKey) {
 	Lock l(cs);
 
 	for(auto i: clients) {
 		if(i->isConnected()) {
-			i->search(aSizeMode, aSize, aFileType, aString, aToken, StringList() /*ExtList*/);
+			i->search(aSizeMode, aSize, aFileType, aString, aToken, StringList() /*ExtList*/, aKey);
 		}
 	}
 }
 
-void ClientManager::search(StringList& who, int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList) {
+void ClientManager::search(StringList& who, int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey) {
 	Lock l(cs);
 
 	for(auto& client: who) {
 		for(auto c: clients) {
 			if(c->isConnected() && c->getHubUrl() == client) {
-				c->search(aSizeMode, aSize, aFileType, aString, aToken, aExtList);
+				c->search(aSizeMode, aSize, aFileType, aString, aToken, aExtList, aKey);
 			}
 		}
 	}
--- a/dcpp/ClientManager.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/ClientManager.h	Fri Jun 14 10:33:05 2024 +0200
@@ -79,8 +79,8 @@
 	bool isConnected(const string& aUrl) const;
 	bool isHubConnected(const string& aUrl) const;
 
-	void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken);
-	void search(StringList& who, int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList);
+	void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const string& aKey);
+	void search(StringList& who, int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey);
 	void infoUpdated();
 
 	UserPtr getUser(const string& aNick, const string& aHubUrl) noexcept;
@@ -121,7 +121,7 @@
 
 	UserPtr& getMe();
 
-	void sendUDP(AdcCommand& cmd, const OnlineUser& user);
+	void sendUDP(AdcCommand& cmd, const OnlineUser& user, const string& aKey = Util::emptyString);
 
 	void connect(const HintedUser& user, const string& token, ConnectionType type = CONNECTION_TYPE_LAST);
 	void privateMessage(const HintedUser& user, const string& msg, bool thirdPerson);
@@ -183,7 +183,7 @@
 	*/
 	OnlineUser* findOnlineUserHint(const CID& cid, const string& hintUrl, OnlinePairC& p) const;
 
-	void sendUDP(const string& ip, const string& port, const string& data);
+	void sendUDP(const string& ip, const string& port, const string& data, const string& aKey = Util::emptyString);
 
 	string getUsersFile() const { return Util::getPath(Util::PATH_USER_LOCAL) + "Users.xml"; }
 
--- a/dcpp/CryptoManager.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/CryptoManager.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -557,6 +557,85 @@
 	}
 }
 
+string CryptoManager::encryptSUDP(const uint8_t* aKey, const string& aCmd) {
+	string inData = aCmd;
+	uint8_t ivd[16] = { };
+
+	// prepend 16 random bytes to message
+	RAND_bytes(ivd, 16);
+	inData.insert(0, (char*)ivd, 16);
+
+	// use PKCS#5 padding to align the message length to the cipher block size (16)
+	uint8_t pad = 16 - (aCmd.length() & 15);
+	inData.append(pad, (char)pad);
+
+	// encrypt it
+	boost::scoped_array<uint8_t> out(new uint8_t[inData.length()]);
+	memset(ivd, 0, 16);
+	auto commandLength = inData.length();
+
+#define CHECK(n) if(!(n)) { dcassert(0); }
+
+	int len, tmpLen;
+	auto ctx = EVP_CIPHER_CTX_new();
+	CHECK(EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, aKey, ivd, 1));
+	CHECK(EVP_CIPHER_CTX_set_padding(ctx, 0));
+	CHECK(EVP_EncryptUpdate(ctx, out.get(), &len, (unsigned char*)inData.c_str(), inData.length()));
+	CHECK(EVP_EncryptFinal_ex(ctx, out.get() + len, &tmpLen));
+	EVP_CIPHER_CTX_free(ctx);
+
+	dcassert((commandLength & 15) == 0);
+
+#undef CHECK
+
+	inData.clear();
+	inData.insert(0, (char*)out.get(), commandLength);
+	return inData;
+}
+
+bool CryptoManager::decryptSUDP(const uint8_t* aKey, const uint8_t* aData, size_t aDataLen, string& result_) {
+	boost::scoped_array<uint8_t> out(new uint8_t[aDataLen]);
+
+	uint8_t ivd[16] = { };
+
+	auto ctx = EVP_CIPHER_CTX_new();
+
+#define CHECK(n) if(!(n)) { dcassert(0); }
+
+	int len;
+	CHECK(EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, aKey, ivd, 0));
+	CHECK(EVP_CIPHER_CTX_set_padding(ctx, 0));
+	CHECK(EVP_DecryptUpdate(ctx, out.get(), &len, aData, aDataLen));
+	CHECK(EVP_DecryptFinal_ex(ctx, out.get() + len, &len));
+	EVP_CIPHER_CTX_free(ctx);
+
+#undef CHECK
+
+	// Validate padding and replace with 0-bytes.
+	int padlen = out[aDataLen - 1];
+	if (padlen < 1 || padlen > 16) {
+		return false;
+	}
+
+	bool valid = true;
+	for (auto r = 0; r < padlen; r++) {
+		if (out[aDataLen - padlen + r] != padlen) {
+			valid = false;
+			break;
+		} else {
+			out[aDataLen - padlen + r] = 0;
+		}
+	}
+
+	if (valid) {
+		result_ = (char*)&out[0] + 16;
+		return true;
+	}
+
+	return false;
+}
+
+
 string CryptoManager::keySubst(const uint8_t* aKey, size_t len, size_t n) {
 	boost::scoped_array<uint8_t> temp(new uint8_t[len + n * 10]);
 
--- a/dcpp/CryptoManager.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/CryptoManager.h	Fri Jun 14 10:33:05 2024 +0200
@@ -51,6 +51,9 @@
 
 	void decodeBZ2(const uint8_t* is, size_t sz, string& os);
 
+	string encryptSUDP(const uint8_t* aKey, const string& aCmd);
+	bool decryptSUDP(const uint8_t* aKey, const uint8_t* aData, size_t aDataLen, string& result_);
+
 	SSL_CTX* getSSLContext(SSLContext wanted);
 
 	void loadCertificates() noexcept;
--- a/dcpp/NmdcHub.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/NmdcHub.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -840,7 +840,7 @@
 	}
 }
 
-void NmdcHub::search(int aSizeType, int64_t aSize, int aFileType, const string& aString, const string&, const StringList&) {
+void NmdcHub::search(int aSizeType, int64_t aSize, int aFileType, const string& aString, const string&, const StringList&, const string&) {
 	checkstate();
 	char c1 = (aSizeType == SearchManager::SIZE_DONTCARE) ? 'F' : 'T';
 	char c2 = (aSizeType == SearchManager::SIZE_ATLEAST) ? 'F' : 'T';
--- a/dcpp/NmdcHub.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/NmdcHub.h	Fri Jun 14 10:33:05 2024 +0200
@@ -43,7 +43,7 @@
 	virtual void hubMessage(const string& aMessage, bool /*thirdPerson*/ = false);
 	virtual void privateMessage(const OnlineUser& aUser, const string& aMessage, bool /*thirdPerson*/ = false);
 	virtual void sendUserCmd(const UserCommand& command, const ParamMap& params);
-	virtual void search(int aSizeType, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList);
+	virtual void search(int aSizeType, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, const string& aKey = Util::emptyString);
 	virtual void password(const string& aPass) { send("$MyPass " + fromUtf8(aPass) + "|"); }
 	virtual void infoImpl() { myInfo(false); }
 
--- a/dcpp/SearchManager.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/SearchManager.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -18,9 +18,12 @@
 #include "stdinc.h"
 #include "SearchManager.h"
 
+#include <boost/range/adaptors.hpp>
 #include <boost/range/algorithm/find_if.hpp>
 #include <boost/scoped_array.hpp>
 
+#include <openssl/rand.h>
+
 #include "ClientManager.h"
 #include "ConnectivityManager.h"
 #include "format.h"
@@ -28,6 +31,7 @@
 #include "PluginManager.h"
 #include "SearchResult.h"
 #include "ShareManager.h"
+#include "CryptoManager.h"
 
 namespace dcpp {
 
@@ -50,7 +54,7 @@
 	stop(false),
 	lastSearch(GET_TICK())
 {
-
+	TimerManager::getInstance()->addListener(this);
 }
 
 SearchManager::~SearchManager() {
@@ -61,6 +65,9 @@
 		join();
 #endif
 	}
+
+	TimerManager::getInstance()->removeListener(this); 
+	searchKeys.clear();
 }
 
 string SearchManager::normalizeWhitespace(const string& aString){
@@ -75,14 +82,18 @@
 
 void SearchManager::search(const string& aName, int64_t aSize, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */) {
 	if(okToSearch()) {
-		ClientManager::getInstance()->search(aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken);
+		string aKey;
+		genSUDPKey(aKey);
+		ClientManager::getInstance()->search(aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken, aKey);
 		lastSearch = GET_TICK();
 	}
 }
 
 void SearchManager::search(StringList& who, const string& aName, int64_t aSize /* = 0 */, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */, const StringList& aExtList) {
 	if(okToSearch()) {
-		ClientManager::getInstance()->search(who, aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken, aExtList);
+		string aKey;
+		genSUDPKey(aKey);
+		ClientManager::getInstance()->search(who, aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken, aExtList, aKey);
 		lastSearch = GET_TICK();
 	}
 }
@@ -131,6 +142,10 @@
 			if((len = socket->read(&buf[0], BUFSIZE, remoteAddr)) > 0) {
 				string data(reinterpret_cast<char*>(&buf[0]), len);
 
+				if(SETTING(ENABLE_SUDP) && len >= 32 && ((len & 15) == 0)) {
+					decryptPacket(data, len, buf.get());
+				}
+
 				if(PluginManager::getInstance()->onUDP(false, remoteAddr, port, data))
 					continue;
 
@@ -387,6 +402,32 @@
 		type, 0, freeSlots, size, file, hubName, remoteIp, TTHValue(tth), token, style)));
 }
 
+void SearchManager::genSUDPKey(string& aKey) {
+	string keyStr = Util::emptyString;
+	if(SETTING(ENABLE_SUDP)) {
+		auto key = std::make_unique<uint8_t[]>(16);
+		RAND_bytes(key.get(), 16);
+		keyStr = Encoder::toBase32(key.get(), 16);
+		{
+			Lock l(cs);
+			searchKeys.emplace_back(move(key), GET_TICK());
+		}
+	}
+	aKey = keyStr;
+}
+
+bool SearchManager::decryptPacket(string& x, size_t aLen, const uint8_t* aBuf) {
+	Lock l(cs);
+
+	for (const auto& i: searchKeys | boost::adaptors::reversed) {
+		if(CryptoManager::getInstance()->decryptSUDP(i.first.get(), aBuf, aLen, x)) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
 void SearchManager::respond(const AdcCommand& cmd, const OnlineUser& user) {
 	// Filter own searches
 	if(user.getUser() == ClientManager::getInstance()->getMe())
@@ -396,14 +437,27 @@
 	if(results.empty())
 		return;
 
-	string token;
+	string token, key;
 	cmd.getParam("TO", 0, token);
+	cmd.getParam("KY", 0, key);
 
 	for(auto& i: results) {
 		AdcCommand res = i->toRES(AdcCommand::TYPE_UDP);
 		if(!token.empty())
 			res.addParam("TO", token);
-		ClientManager::getInstance()->sendUDP(res, user);
+		ClientManager::getInstance()->sendUDP(res, user, key);
+	}
+}
+
+void SearchManager::on(TimerManagerListener::Minute, uint64_t aTick) noexcept {
+	Lock l(cs);
+	for (auto i = searchKeys.begin(); i != searchKeys.end();) {
+		if (i->second + 1000 * 60 * 5 < aTick) {
+			searchKeys.erase(i);
+			i = searchKeys.begin();
+		} else {
+			++i;
+		}
 	}
 }
 
--- a/dcpp/SearchManager.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/SearchManager.h	Fri Jun 14 10:33:05 2024 +0200
@@ -33,7 +33,7 @@
 class SearchManager;
 class SocketException;
 
-class SearchManager : public Speaker<SearchManagerListener>, public Singleton<SearchManager>, public Thread, private CommandHandler<SearchManager>
+class SearchManager : public Speaker<SearchManagerListener>, public Singleton<SearchManager>, public Thread, private CommandHandler<SearchManager>, private TimerManagerListener
 {
 public:
 	enum SizeModes {
@@ -88,6 +88,9 @@
 		return timeToSearch() <= 0;
 	}
 
+	void genSUDPKey(string& aKey);
+	bool decryptPacket(string& x, size_t aLen, const uint8_t* aBuf);
+
 private:
 	friend class CommandHandler<SearchManager>;
 
@@ -108,6 +111,13 @@
 	virtual int run();
 
 	virtual ~SearchManager();
+
+	vector<pair<std::unique_ptr<uint8_t[]>, uint64_t>> searchKeys;
+	
+	CriticalSection cs;
+
+	// TimerManagerListener
+	virtual void on(Minute, uint64_t aTick) noexcept; 
 };
 
 } // namespace dcpp
--- a/dcpp/SettingsManager.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/SettingsManager.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -114,6 +114,7 @@
 	"ToggleActiveTab", "UrlHandler", "UseCTRLForLineHistory", "UseSystemIcons",
 	"UsersFilterFavorite", "UsersFilterOnline", "UsersFilterQueue", "UsersFilterWaiting",
 	"RegisterSystemStartup", "DontLogCCPMChat", "AboutCfgDisclaimer", "EnableTaskbarPreview",
+	"EnableSUDP", 
 	"SENTRY",
 	// Int64
 	"TotalUpload", "TotalDownload", "SharingSkiplistMinSize", "SharingSkiplistMaxSize",
@@ -373,6 +374,7 @@
 	setDefault(MAX_EXTRA_SLOTS, 3);
 	setDefault(TESTING_STATUS, TESTING_ENABLED);
 	setDefault(WHITELIST_OPEN_URIS, "http:;https:;www;mailto:");
+	setDefault(ENABLE_SUDP, true);
 	setDefault(AC_DISCLAIM, true);
 
 	setSearchTypeDefaults();
--- a/dcpp/SettingsManager.h	Sat May 25 13:59:07 2024 +0200
+++ b/dcpp/SettingsManager.h	Fri Jun 14 10:33:05 2024 +0200
@@ -149,7 +149,7 @@
 		STATUS_IN_CHAT, TIME_DEPENDENT_THROTTLE, TIME_STAMPS,
 		TOGGLE_ACTIVE_WINDOW, URL_HANDLER, USE_CTRL_FOR_LINE_HISTORY, USE_SYSTEM_ICONS,
 		USERS_FILTER_FAVORITE, USERS_FILTER_ONLINE, USERS_FILTER_QUEUE, USERS_FILTER_WAITING,
-		REGISTER_SYSTEM_STARTUP, DONT_LOG_CCPM, AC_DISCLAIM, ENABLE_TASKBAR_PREVIEW,
+		REGISTER_SYSTEM_STARTUP, DONT_LOG_CCPM, AC_DISCLAIM, ENABLE_TASKBAR_PREVIEW, ENABLE_SUDP,
 		BOOL_LAST };
 
 	enum Int64Setting { INT64_FIRST = BOOL_LAST + 1,
--- a/help/settings_certs.html	Sat May 25 13:59:07 2024 +0200
+++ b/help/settings_certs.html	Fri Jun 14 10:33:05 2024 +0200
@@ -38,12 +38,13 @@
   Try to initiate a peer-to-peer connection whenever the user in private message windows shows up.
   <a href="window_pm.html#ccpm">Check the PM window help for more information.</a>
   </dd>
+  <dt>Enable encrypted active search results in ADCS hubs</dt>
+  <dd cshelp="IDH_SETTINGS_ADVANCED_ENABLE_SUDP">
+  Enable support of sending and processing encrypted search results when they're coming directly from other clients with support of this feature. Note that this setting has only effect searching secure ADCS hubs and provides full security only if all other active clients in the hub has support of and enable the feature.
+  </dd>
 </dl>
 <p>
-To see effects, please restart DC++.
-</p>
-<p>
-Note: TLS support is an experimental feature and doesn't imply that DC++ is secure in any way.
+  For some of these settings to take effect, you have to restart DC++.
 </p>
 </body>
 </html>
--- a/win32/CertificatesPage.cpp	Sat May 25 13:59:07 2024 +0200
+++ b/win32/CertificatesPage.cpp	Fri Jun 14 10:33:05 2024 +0200
@@ -38,6 +38,7 @@
 	{ SettingsManager::ALLOW_UNTRUSTED_CLIENTS, N_("Allow TLS connections to clients without trusted certificate"), IDH_SETTINGS_CERTIFICATES_ALLOW_UNTRUSTED_CLIENTS },
 	{ SettingsManager::ENABLE_CCPM, N_("Support direct encrypted private message channels"), IDH_SETTINGS_CERTIFICATES_ENABLE_CCPM },
 	{ SettingsManager::ALWAYS_CCPM, N_("Always attempt to establish direct encrypted private message channels"), IDH_SETTINGS_CERTIFICATES_ALWAYS_CCPM },
+	{ SettingsManager::ENABLE_SUDP, N_("Enable encrypted active search results in ADCS hubs"), IDH_SETTINGS_ADVANCED_ENABLE_SUDP }, 
 	{ 0, 0 }
 };