Skip to content

Commit

Permalink
* Add TLS certificate verification support.
Browse files Browse the repository at this point in the history
You can disable it if the version of Windows you are running Discord Messenger on does not support modern encryption standards for root certificates.

This checks BOTH HTTP requests AND WebSocket connections.

Fixes #45.

Version 1.01 coming today!!
  • Loading branch information
iProgramMC committed May 16, 2024
1 parent 7a52236 commit b975c75
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 79 deletions.
4 changes: 2 additions & 2 deletions src/discord/Frontend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class Frontend

// Called by WebSocketClient, dispatches to relevant places including DiscordInstance
virtual void OnWebsocketMessage(int gatewayID, const std::string& payload) = 0;
virtual void OnWebsocketClose(int gatewayID, int errorCode) = 0;
virtual void OnWebsocketFail(int gatewayID, int errorCode) = 0;
virtual void OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) = 0;
virtual void OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError) = 0;

// Heartbeat interval
virtual void SetHeartbeatInterval(int timeMs) = 0;
Expand Down
4 changes: 4 additions & 0 deletions src/discord/LocalSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ bool LocalSettings::Load()
if (j.contains("StartMaximized"))
m_bStartMaximized = j["StartMaximized"];

if (j.contains("EnableTLSVerification"))
m_bEnableTLSVerification = j["EnableTLSVerification"];

if (m_bSaveWindowSize)
{
if (j.contains("WindowWidth"))
Expand Down Expand Up @@ -112,6 +115,7 @@ bool LocalSettings::Save()
j["StartMaximized"] = m_bStartMaximized;
j["SaveWindowSize"] = m_bSaveWindowSize;
j["CheckUpdates"] = m_bCheckUpdates;
j["EnableTLSVerification"] = m_bEnableTLSVerification;
j["RemindUpdateCheckOn"] = (long long)(m_remindUpdatesOn);
if (m_bSaveWindowSize) {
j["WindowWidth"] = m_width;
Expand Down
7 changes: 7 additions & 0 deletions src/discord/LocalSettings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ class LocalSettings
bool AskToCheckUpdates() const {
return m_bAskToCheckUpdates;
}
bool EnableTLSVerification() const {
return m_bEnableTLSVerification;
}
void SetEnableTLSVerification(bool b) {
m_bEnableTLSVerification = b;
}
void StopUpdateCheckTemporarily();

private:
Expand All @@ -99,6 +105,7 @@ class LocalSettings
bool m_bIsFirstStart = false;
bool m_bCheckUpdates = false;
bool m_bAskToCheckUpdates = false;
bool m_bEnableTLSVerification = true;
time_t m_remindUpdatesOn = 0;
int m_width = 1000;
int m_height = 700;
Expand Down
66 changes: 60 additions & 6 deletions src/discord/WebsocketClient.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#include "WebsocketClient.hpp"
#include "Frontend.hpp"
#include "Util.hpp"
#include "LocalSettings.hpp"

#include <asio/ssl/context.hpp>

static WebsocketClient g_WSCSingleton;

// Doing the same thing as cpp-httplib
void LoadSystemCertsOnWindows(asio::ssl::context& ctx);

WebsocketClient* GetWebsocketClient()
{
return &g_WSCSingleton;
Expand All @@ -25,8 +31,30 @@ void WSConnectionMetadata::OnFail(WSClient* c, websocketpp::connection_hdl hdl)
m_server = pConn->get_response_header("Server");
m_errorReason = pConn->get_ec().message();

DbgPrintF("Failed to connect: %s (server %s)\n", m_errorReason.c_str(), m_server.c_str());
GetFrontend()->OnWebsocketFail(m_id, pConn->get_remote_close_code());
auto xportEc = pConn->get_transport_ec();

DbgPrintF("Failed to connect: %s (server '%s'). Transport error code 0x%x, message %s\n",
m_errorReason.c_str(), m_server.c_str(), xportEc.value(), xportEc.message().c_str());

std::string guiMessage = m_errorReason;
if (!m_server.empty())
guiMessage += " (server: " + m_server + ")";
if (xportEc)
guiMessage += " (transport error " + xportEc.message() + ")";

namespace SocketErrors = websocketpp::transport::asio::socket::error;
bool isTLSError = false;
switch (pConn->get_ec().value()) {
case SocketErrors::missing_tls_init_handler:
case SocketErrors::tls_failed_sni_hostname:
case SocketErrors::tls_handshake_timeout:
case SocketErrors::tls_handshake_failed:
case SocketErrors::invalid_tls_context:
case SocketErrors::security:
isTLSError = true;
}

GetFrontend()->OnWebsocketFail(m_id, pConn->get_ec().value(), guiMessage, isTLSError);
}

void WSConnectionMetadata::OnClose(WSClient* c, websocketpp::connection_hdl hdl)
Expand All @@ -43,7 +71,7 @@ void WSConnectionMetadata::OnClose(WSClient* c, websocketpp::connection_hdl hdl)

m_errorReason = s.str();

GetFrontend()->OnWebsocketClose(m_id, pConn->get_remote_close_code());
GetFrontend()->OnWebsocketClose(m_id, pConn->get_remote_close_code(), s.str());
}

void WSConnectionMetadata::OnMessage(websocketpp::connection_hdl hdl, WSClient::message_ptr msg)
Expand Down Expand Up @@ -72,16 +100,22 @@ WebsocketClient::~WebsocketClient()
AsioSslContextSharedPtr WebsocketClient::HandleTLSInit(websocketpp::connection_hdl hdl)
{
// establishes a SSL connection
AsioSslContextSharedPtr ctx = std::make_shared<AsioSslContext>(AsioSslContext::sslv23);
AsioSslContextSharedPtr ctx = std::make_shared<AsioSslContext>(AsioSslContext::tls);

try
{
ctx->set_options(
websocketpp::lib::asio::ssl::context::default_workarounds |
websocketpp::lib::asio::ssl::context::no_sslv2 |
websocketpp::lib::asio::ssl::context::no_sslv3 |
websocketpp::lib::asio::ssl::context::single_dh_use
);

if (GetLocalSettings()->EnableTLSVerification())
{
ctx->set_default_verify_paths();
#ifdef _WIN32
LoadSystemCertsOnWindows(*ctx);
#endif
}
}
catch (std::exception& e)
{
Expand All @@ -91,6 +125,20 @@ AsioSslContextSharedPtr WebsocketClient::HandleTLSInit(websocketpp::connection_h
return ctx;
}

void WebsocketClient::HandleSocketInit(websocketpp::connection_hdl hdl, AsioSocketType& socket)
{
WSClient::connection_ptr pConn = m_endpoint.get_con_from_hdl(hdl);

if (GetLocalSettings()->EnableTLSVerification())
{
socket.set_verify_mode(websocketpp::lib::asio::ssl::verify_peer);

if (!SSL_set_tlsext_host_name(reinterpret_cast<SSL*>(socket.native_handle()), "gateway.discord.gg")) {
DbgPrintF("Failed to set SNI host name... this might go awry");
}
}
}

void WebsocketClient::Init()
{
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
Expand All @@ -102,6 +150,12 @@ void WebsocketClient::Init()
this,
websocketpp::lib::placeholders::_1
));
m_endpoint.set_socket_init_handler(websocketpp::lib::bind(
&WebsocketClient::HandleSocketInit,
this,
websocketpp::lib::placeholders::_1,
websocketpp::lib::placeholders::_2
));
m_thread.reset(new websocketpp::lib::thread(&WSClient::run, &m_endpoint));
m_bKilled = false;
}
Expand Down
6 changes: 5 additions & 1 deletion src/discord/WebsocketClient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ typedef websocketpp::client<websocketpp::config::asio_tls_client> WSClient;
typedef websocketpp::lib::shared_ptr<websocketpp::lib::thread> WSThreadSharedPtr;
typedef websocketpp::lib::asio::ssl::context AsioSslContext;
typedef websocketpp::lib::shared_ptr<AsioSslContext> AsioSslContextSharedPtr;
typedef websocketpp::transport::asio::tls_socket::connection::socket_type AsioSocketType;

class WSConnectionMetadata
{
Expand All @@ -54,7 +55,7 @@ class WSConnectionMetadata
void OnFail(WSClient* c, websocketpp::connection_hdl hdl);
void OnClose(WSClient* c, websocketpp::connection_hdl hdl);
void OnMessage(websocketpp::connection_hdl hdl, WSClient::message_ptr msg);

websocketpp::connection_hdl GetHDL() const
{
return m_hdl;
Expand Down Expand Up @@ -118,6 +119,9 @@ class WebsocketClient

// Handle TLS initialization.
AsioSslContextSharedPtr HandleTLSInit(websocketpp::connection_hdl hdl);

// Handle socket initialization.
void HandleSocketInit(websocketpp::connection_hdl hdl, AsioSocketType& socketType);
};

WebsocketClient* GetWebsocketClient();
Expand Down
7 changes: 6 additions & 1 deletion src/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@
#define IDC_UPDATE 867
#define IDC_ICON_WARN_CONNECT 868
#define IDC_ICON_WARN_WEBSOCKETSTUFF 869
#define IDC_ICON_WARN_WEBSOCKETSTUFF2 870
#define IDC_CHECK1 871
#define IDC_ENABLE_TLS_CHECKS 871
#define IDC_CHECK2 872
#define IDC_CHECK_UPDATES 872
#define ID_FILE_PREFERENCES 1001
#define ID_FILE_STOPALLSPEECH 1002
#define ID_FILE_EXIT 1003
Expand Down Expand Up @@ -351,7 +356,7 @@
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 71
#define _APS_NEXT_COMMAND_VALUE 1069
#define _APS_NEXT_CONTROL_VALUE 869
#define _APS_NEXT_CONTROL_VALUE 873
#define _APS_NEXT_SYMED_VALUE 40000
#endif
#endif
27 changes: 18 additions & 9 deletions src/resource.rc
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ BEGIN
CONTROL "",IDC_STATIC_PROFILE_IMAGE,"Static",SS_BITMAP | SS_CENTERIMAGE,12,18,24,22
END

IDD_DIALOG_APPEARANCE DIALOGEX 0, 0, 260, 200
IDD_DIALOG_APPEARANCE DIALOGEX 0, 0, 260, 242
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
Expand Down Expand Up @@ -527,21 +527,30 @@ BEGIN
GROUPBOX "Search Results",IDC_QUICK_GROUP,7,38,294,99
END

IDD_DIALOG_CONNECTION DIALOGEX 0, 0, 260, 200
IDD_DIALOG_CONNECTION DIALOGEX 0, 0, 260, 242
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x0
BEGIN
GROUPBOX "Discord API && CDN",IDC_STATIC,6,7,245,133
GROUPBOX "Discord API && CDN",IDC_STATIC,6,7,245,128
LTEXT "Discord API URL:",IDC_STATIC,12,20,55,8
LTEXT "Discord CDN URL:",IDC_STATIC,12,35,58,8
EDITTEXT IDC_EDIT_DISCORDAPI,72,17,174,14,ES_AUTOHSCROLL
EDITTEXT IDC_EDIT_DISCORDCDN,72,33,174,14,ES_AUTOHSCROLL
PUSHBUTTON "Revert to official Discord backend",IDC_REVERTTODEFAULT,11,120,118,14
PUSHBUTTON "Update",IDC_UPDATE,197,120,50,14
ICON IDI_WARNING_IC,IDC_ICON_WARN_CONNECT,11,55,21,20,SS_REALSIZEIMAGE
PUSHBUTTON "Revert to official Discord backend",IDC_REVERTTODEFAULT,11,116,118,14
PUSHBUTTON "Update",IDC_UPDATE,197,116,50,14
ICON IDI_WARNING_IC,IDC_ICON_WARN_CONNECT,11,55,20,20,SS_REALSIZEIMAGE
LTEXT "For security reasons, when you update the URLs that Discord Messenger uses to interface with Discord's backend, you will be logged out. (your token is forgotten but not revoked)",IDC_STATIC,30,54,214,31
ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF,12,87,21,20,SS_REALSIZEIMAGE
LTEXT "Wondering where the websocket gateway URL is located? Well, Discord Messenger issues a request to API_URL/gateway, which tells it the websocket URL to use.",IDC_STATIC,30,87,214,31
ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF,12,87,20,20,SS_REALSIZEIMAGE
LTEXT "Wondering where the websocket gateway URL is located? Well, Discord Messenger issues a request to API_URL/gateway, which tells it the websocket URL to use.",IDC_STATIC,30,87,214,29
GROUPBOX "Security Settings",IDC_STATIC,6,139,245,48
ICON IDI_WARNING_IC,IDC_ICON_WARN_WEBSOCKETSTUFF2,12,166,20,20,SS_REALSIZEIMAGE
LTEXT "WARNING: Disabling TLS certificate verification could expose you to MITM (man-in-the-middle) attacks via certificate spoofing!",IDC_STATIC,30,165,214,19
CONTROL "Enable TLS certificate verification",IDC_ENABLE_TLS_CHECKS,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,11,151,230,10
GROUPBOX "Check for Updates",IDC_STATIC,6,194,245,40
CONTROL "Check for updates using the GitHub API",IDC_CHECK_UPDATES,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,204,232,10
LTEXT "iProgramInCpp cannot see your IP address via the update service.",IDC_STATIC,12,219,231,10
END


Expand Down Expand Up @@ -643,7 +652,7 @@ BEGIN
LEFTMARGIN, 6
RIGHTMARGIN, 251
TOPMARGIN, 7
BOTTOMMARGIN, 192
BOTTOMMARGIN, 234
END
END
#endif // APSTUDIO_INVOKED
Expand Down
38 changes: 35 additions & 3 deletions src/windows/Frontend_Win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ void Frontend_Win32::OnWebsocketMessage(int gatewayID, const std::string& payloa
SendMessage(g_Hwnd, WM_WEBSOCKETMESSAGE, 0, (LPARAM) pParm);
}

void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode)
void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode, const std::string& message)
{
if (GetDiscordInstance()->GetGatewayID() == gatewayID)
GetDiscordInstance()->GatewayClosed(errorCode);
Expand All @@ -306,9 +306,41 @@ void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode)
DbgPrintW("Unknown gateway connection %d closed: %d", gatewayID, errorCode);
}

void Frontend_Win32::OnWebsocketFail(int gatewayID, int errorCode)
void Frontend_Win32::OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError)
{
DbgPrintW("TODO: Frontend_Win32::OnWebsocketFail: GatewayID %d ErrorCode %d", gatewayID, errorCode);
static TCHAR buffer[8192];
LPTSTR msg = ConvertCppStringToTString(message);
WAsnprintf(
buffer,
_countof(buffer),
TEXT("ERROR: Could not connect to the websocket gateway.\nConnection was closed with code: %d\n\nMessage: %s"),
errorCode,
msg
);

if (isTLSError) {
_tcscat(
buffer,
TEXT("WARNING: Your connection may not be private\n\nDiscord Messenger could not verify that it is connecting to a Websocket service ")
TEXT("required to use Discord Messenger in real time.")
TEXT("\n\nThis could be because:")
TEXT("\no Your computer does not trust the certificate that the gateway has declared because it is wrong,")
TEXT("\no Your computer does not trust the certificate that the gateway has declared because it doesn't trust the root certificate,")
TEXT("\no Your computer does not trust the certificate that the gateway has declared because your date and time are incorrect,")
TEXT("\no The website reported a protocol which the client is outdated for and cannot handle, or")
TEXT("\no SOMEONE IS EAVESDROPPING ON YOU RIGHT NOW! (man-in-the-middle attack)")
TEXT("\n\nIt is not recommended to proceed if you see this error. Please ensure you can connect to Discord using a ")
TEXT("capable device on the same network as this one. If you are able to connect, export the root certificate from ")
TEXT("discord.com on that device and import it into Windows, if the OS supports it.")
TEXT("\n\nYou may also disable SSL server verification, and then restart the application. However, you have to ")
TEXT("take into account the risks that this entails.")
);
}

free(msg);

MessageBox(g_Hwnd, buffer, TmGetTString(IDS_PROGRAM_NAME), (isTLSError ? MB_ICONWARNING : MB_ICONERROR) | MB_OK);
SendMessage(g_Hwnd, WM_CONNECTERROR, 0, 0);
}

void Frontend_Win32::RequestQuit()
Expand Down
4 changes: 2 additions & 2 deletions src/windows/Frontend_Win32.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class Frontend_Win32 : public Frontend
void RefreshMembers(const std::set<Snowflake>& members) override;
void JumpToMessage(Snowflake messageInCurrentChannel) override;
void OnWebsocketMessage(int gatewayID, const std::string& payload) override;
void OnWebsocketClose(int gatewayID, int errorCode) override;
void OnWebsocketFail(int gatewayID, int errorCode) override;
void OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) override;
void OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError) override;
void SetHeartbeatInterval(int timeMs) override;
void LaunchURL(const std::string& url) override;
void RegisterIcon(Snowflake sf, const std::string& avatarlnk) override;
Expand Down
Loading

0 comments on commit b975c75

Please sign in to comment.