]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_http_websocket: Add payload masking to the websocket client
authorMoises Silva <moises.silva@gmail.com>
Sat, 13 Jun 2020 16:29:13 +0000 (16:29 +0000)
committerFriendly Automation <jenkins2@gerrit.asterisk.org>
Mon, 22 Jun 2020 13:24:02 +0000 (08:24 -0500)
ASTERISK-28949

Change-Id: Id465030f2b1997b83d408933fdbabe01827469ca

res/res_http_websocket.c

index 63fccdda0e58a01221ce6c054d09567c84fa5296..5d14ac68893c2d5b7a245d3733ef88225fcd261f 100644 (file)
@@ -283,35 +283,63 @@ int AST_OPTIONAL_API_NAME(ast_websocket_server_remove_protocol)(struct ast_webso
        return 0;
 }
 
+/*! \brief Perform payload masking for client sessions */
+static void websocket_mask_payload(struct ast_websocket *session, char *frame, char *payload, uint64_t payload_size)
+{
+       /* RFC 6455 5.1 - clients MUST mask frame data */
+       if (session->client) {
+               uint64_t i;
+               uint8_t mask_key_idx;
+               uint32_t mask_key = ast_random();
+               uint8_t length = frame[1] & 0x7f;
+               frame[1] |= 0x80; /* set mask bit to 1 */
+               /* The mask key octet position depends on the length */
+               mask_key_idx = length == 126 ? 4 : length == 127 ? 10 : 2;
+               put_unaligned_uint32(&frame[mask_key_idx], mask_key);
+               for (i = 0; i < payload_size; i++) {
+                       payload[i] ^= ((char *)&mask_key)[i % 4];
+               }
+       }
+}
+
+
 /*! \brief Close function for websocket session */
 int AST_OPTIONAL_API_NAME(ast_websocket_close)(struct ast_websocket *session, uint16_t reason)
 {
        enum ast_websocket_opcode opcode = AST_WEBSOCKET_OPCODE_CLOSE;
-       char frame[4] = { 0, }; /* The header is 2 bytes and the reason code takes up another 2 bytes */
-       int res;
+       /* The header is either 2 or 6 bytes and the
+        * reason code takes up another 2 bytes */
+       char frame[8] = { 0, };
+       int header_size, fsize, res;
 
        if (session->close_sent) {
                return 0;
        }
 
+       /* clients need space for an additional 4 byte masking key */
+       header_size = session->client ? 6 : 2;
+       fsize = header_size + 2;
+
        frame[0] = opcode | 0x80;
        frame[1] = 2; /* The reason code is always 2 bytes */
 
        /* If no reason has been specified assume 1000 which is normal closure */
-       put_unaligned_uint16(&frame[2], htons(reason ? reason : 1000));
+       put_unaligned_uint16(&frame[header_size], htons(reason ? reason : 1000));
+
+       websocket_mask_payload(session, frame, &frame[header_size], 2);
 
        session->closing = 1;
        session->close_sent = 1;
 
        ao2_lock(session);
        ast_iostream_set_timeout_inactivity(session->stream, session->timeout);
-       res = ast_iostream_write(session->stream, frame, sizeof(frame));
+       res = ast_iostream_write(session->stream, frame, fsize);
        ast_iostream_set_timeout_disable(session->stream);
 
        /* If an error occurred when trying to close this connection explicitly terminate it now.
         * Doing so will cause the thread polling on it to wake up and terminate.
         */
-       if (res != sizeof(frame)) {
+       if (res != fsize) {
                ast_iostream_close(session->stream);
                session->stream = NULL;
                ast_verb(2, "WebSocket connection %s '%s' forcefully closed due to fatal write error\n",
@@ -364,6 +392,11 @@ int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, en
                header_size += 8;
        }
 
+       if (session->client) {
+               /* Additional 4 bytes for the client masking key */
+               header_size += 4;
+       }
+
        frame_size = header_size + payload_size;
 
        frame = ast_alloca(frame_size + 1);
@@ -381,6 +414,8 @@ int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, en
 
        memcpy(&frame[header_size], payload, payload_size);
 
+       websocket_mask_payload(session, frame, &frame[header_size], payload_size);
+
        ao2_lock(session);
        if (session->closing) {
                ao2_unlock(session);