From 8422ab4d3054ae0f70891f78bee19df05f8e37ba Mon Sep 17 00:00:00 2001 From: xCynDev <181435937+xCynDev@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:30:56 -0800 Subject: [PATCH 1/2] Add support for permessage-deflate. --- src/GLua.cpp | 32 ++++++++++++++++++++++++++++++++ src/GWSocket.cpp | 12 +++++++++++- src/GWSocket.h | 5 +++++ src/SSLWebSocket.h | 14 ++++++++++++-- src/WebSocket.h | 12 +++++++++++- 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/GLua.cpp b/src/GLua.cpp index c19a27b8..141ffad1 100644 --- a/src/GLua.cpp +++ b/src/GLua.cpp @@ -219,6 +219,34 @@ LUA_FUNCTION(socketSetHeader) return 0; } +LUA_FUNCTION(socketSetMessageCompression) +{ + GWSocket* socket = getCppObject(LUA); + if (socket->state != STATE_DISCONNECTED) + { + LUA->ThrowError("Cannot set message compression for an already connected websocket"); + } + + LUA->CheckType(2, Type::BOOL); + socket->setPerMessageDeflate(LUA->GetBool(2)); + + return 0; +} + +LUA_FUNCTION(socketSetDisableContextTakeover) +{ + GWSocket* socket = getCppObject(LUA); + if (socket->state != STATE_DISCONNECTED) + { + LUA->ThrowError("Cannot set compression takeover for an already connected websocket"); + } + + LUA->CheckType(2, Type::BOOL); + socket->setDisableContextTakeover(LUA->GetBool(2)); + + return 0; +} + LUA_FUNCTION(socketIsConnected) { GWSocket* socket = getCppObject(LUA); @@ -422,6 +450,10 @@ GMOD_MODULE_OPEN() LUA->SetField(-2, "setCookie"); LUA->PushCFunction(socketSetHeader); LUA->SetField(-2, "setHeader"); + LUA->PushCFunction(socketSetMessageCompression); + LUA->SetField(-2, "setMessageCompression"); + LUA->PushCFunction(socketSetDisableContextTakeover); + LUA->SetField(-2, "setDisableContextTakeover"); LUA->PushCFunction(socketIsConnected); LUA->SetField(-2, "isConnected"); LUA->PushCFunction(socketClearQueue); diff --git a/src/GWSocket.cpp b/src/GWSocket.cpp index e5fd00f6..c37b21c5 100644 --- a/src/GWSocket.cpp +++ b/src/GWSocket.cpp @@ -327,4 +327,14 @@ bool GWSocket::setHeader(std::string key, std::string value) } this->headers[key] = value; return true; -} \ No newline at end of file +} + +void GWSocket::setPerMessageDeflate(bool value) +{ + perMessageDeflate = value; +} + +void GWSocket::setDisableContextTakeover(bool value) +{ + disableContextTakeover = value; +} diff --git a/src/GWSocket.h b/src/GWSocket.h index 6cf1cdff..6fcd3606 100755 --- a/src/GWSocket.h +++ b/src/GWSocket.h @@ -70,6 +70,9 @@ class GWSocket void write(std::string message); bool setCookie(std::string key, std::string value); bool setHeader(std::string key, std::string value); + void setPerMessageDeflate(bool value); + void setDisableContextTakeover(bool value); + BlockingQueue messageQueue; bool isConnected() { return state == STATE_CONNECTED; }; bool canBeDeleted() { return state == STATE_DISCONNECTED; }; @@ -78,6 +81,8 @@ class GWSocket std::string host; unsigned int port; std::atomic state{ STATE_DISCONNECTED }; + bool perMessageDeflate; + bool disableContextTakeover; static std::unique_ptr ioc; //Needs to be initialized on module load protected: diff --git a/src/SSLWebSocket.h b/src/SSLWebSocket.h index 9b0de352..f57b5ea5 100644 --- a/src/SSLWebSocket.h +++ b/src/SSLWebSocket.h @@ -28,7 +28,7 @@ class SSLWebSocket : public GWSocket void closeSocket(); void sslHandshakeComplete(const boost::system::error_code& ec, std::string host, std::string path, std::function decorator); bool verifyCertificate(bool preverified, boost::asio::ssl::verify_context& ctx); - std::atomic>*> ws{ nullptr }; + std::atomic, true>*> ws{ nullptr }; //This is not an atomic function, it only ensures visibility. //Callers have to make sure that atomicity is not required/ensured otherwise void resetWS() @@ -38,8 +38,18 @@ class SSLWebSocket : public GWSocket { delete ws; } - ws = new websocket::stream>(*ioc, *sslContext); + + auto newWS = new websocket::stream, true>(*ioc, *sslContext); + + // Set permessage-deflate options for message compression if requested. + websocket::permessage_deflate opts; + opts.client_enable = perMessageDeflate; + opts.client_no_context_takeover = disableContextTakeover; + newWS->set_option(opts); + + ws = newWS; } + websocket::stream>* getWS() { return this->ws.load(); diff --git a/src/WebSocket.h b/src/WebSocket.h index 795d3af5..98668fa6 100644 --- a/src/WebSocket.h +++ b/src/WebSocket.h @@ -32,8 +32,18 @@ class WebSocket : public GWSocket { delete ws; } - ws = new websocket::stream(*ioc); + + auto newWS = new websocket::stream(*ioc); + + // Set permessage-deflate options for message compression if requested. + websocket::permessage_deflate opts; + opts.client_enable = perMessageDeflate; + opts.client_no_context_takeover = disableContextTakeover; + newWS->set_option(opts); + + ws = newWS; } + websocket::stream* getWS() { return this->ws.load(); From 836679f81d4e72444e7e010f720f9e8114279ca0 Mon Sep 17 00:00:00 2001 From: xCynDev <181435937+xCynDev@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:36:01 -0800 Subject: [PATCH 2/2] Document new functions, warn about CRIME/BREACH. --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 6e39dccf..a3860f71 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,23 @@ require("gwsockets") GWSockets.addVerifyPath( "/etc/ssl/certs" ) ``` +* If you would like to enable the `permessage-deflate` extension which allows you to send and receive compressed messages, you can enable it with the following functions: + + ```LUA + -- Do note this will only be enabled if the websocket server supports permessage-deflate and enables it during handshake. + WEBSOCKET:setMessageCompression(true) + ``` + +* You can also disable context takeover during compression, which will prevent re-using the same compression context over multiple messages. + This will decrease the memory usage at the cost of a worse compression ratio. + + ```LUA + WEBSOCKET:setDisableContextTakeover(true) + ``` + + *WARNING:* Enabling compression over encrypted connections (`WSS://`) may make you vulnerable to [CRIME](https://en.wikipedia.org/wiki/CRIME)/[BREACH](https://en.wikipedia.org/wiki/BREACH) attacks. + Make sure you know what you are doing, or avoid sending sensitive information over websocket messages. + * Next add any cookies or headers you would like to send with the initial request (Optional) ```LUA