]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h1: generate WebSocket key on response if needed
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 11 Dec 2020 16:53:03 +0000 (17:53 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Jan 2021 15:37:14 +0000 (16:37 +0100)
Add the Sec-Websocket-Accept header on a websocket handshake response.
This header may be missing if a h2 server is used with a h1 client.

The response key is calculated following the rfc6455. For this, the
handshake request key must be stored in the h1 session, as a new field
name ws_key. Note that this is only done if the message has been
prealably identified as a Websocket handshake request.

include/haproxy/h1.h
src/h1.c
src/mux_h1.c

index 32d6caf8bf2e4059233b9e953e1a9f8fc7ef0b86..fe78e8d97881ebeeaf153150a2069e5ea0870bd3 100644 (file)
@@ -150,6 +150,8 @@ void h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value);
 void h1_parse_connection_header(struct h1m *h1m, struct ist *value);
 void h1_parse_upgrade_header(struct h1m *h1m, struct ist value);
 
+void h1_calculate_ws_output_key(const char *key, char *result);
+
 /* for debugging, reports the HTTP/1 message state name */
 static inline const char *h1m_state_str(enum h1m_state msg_state)
 {
index ca39d97a4a8a825cb7f74e4ac71e6b13ab5253df..95bcc1f0e4632115cc64bd98f78ede177be0aec6 100644 (file)
--- a/src/h1.c
+++ b/src/h1.c
  */
 
 #include <ctype.h>
+
+#include <import/sha1.h>
+
 #include <haproxy/api.h>
+#include <haproxy/base64.h>
 #include <haproxy/h1.h>
 #include <haproxy/http-hdr.h>
 
@@ -1051,3 +1055,28 @@ int h1_measure_trailers(const struct buffer *buf, unsigned int ofs, unsigned int
        }
        return count - ofs;
 }
+
+#define H1_WS_KEY_SUFFIX_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+/*
+ * Calculate the WebSocket handshake response key from <key_in>. Following the
+ * rfc6455, <key_in> must be 24 bytes longs. The result is  stored in <key_out>
+ * as a 29 bytes long string.
+ */
+void h1_calculate_ws_output_key(const char *key, char *result)
+{
+       blk_SHA_CTX sha1_ctx;
+       char hash_in[60], hash_out[20];
+
+       /* concatenate the key with a fixed suffix */
+       memcpy(hash_in, key, 24);
+       memcpy(&hash_in[24], H1_WS_KEY_SUFFIX_GUID, 36);
+
+       /* sha1 the result */
+       blk_SHA1_Init(&sha1_ctx);
+       blk_SHA1_Update(&sha1_ctx, hash_in, 60);
+       blk_SHA1_Final((unsigned char *)hash_out, &sha1_ctx);
+
+       /* encode in base64 the hash */
+       a2base64(hash_out, 20, result, 29);
+}
index 413ed09bec06e9ab19c3eddf93543aab00652203..b4b5eba2f6c658403270c11507c60a43a8089c3e 100644 (file)
@@ -124,6 +124,8 @@ struct h1s {
 
        enum http_meth_t meth; /* HTTP request method  */
        uint16_t status;       /* HTTP response status */
+
+       char ws_key[25];       /* websocket handshake key */
 };
 
 /* Map of headers used to convert outgoing headers */
@@ -647,6 +649,7 @@ static struct h1s *h1s_new(struct h1c *h1c)
        h1s->flags = H1S_F_WANT_KAL;
        h1s->subs = NULL;
        h1s->rxbuf = BUF_NULL;
+       memset(h1s->ws_key, 0, sizeof(h1s->ws_key));
 
        h1m_init_req(&h1s->req);
        h1s->req.flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
@@ -1300,13 +1303,17 @@ static void h1_set_tunnel_mode(struct h1s *h1s)
 
 /* Search for a websocket key header. The message should have been identified
  * as a valid websocket handshake.
+ *
+ * On the request side, if found the key is stored in the session. It might be
+ * needed to calculate response key if the server side is using http/2.
+ *
  * Returns 0 if no key found
  */
 static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx *htx)
 {
        struct htx_blk *blk;
        enum htx_blk_type type;
-       struct ist n;
+       struct ist n, v;
        int ws_key_found = 0, idx;
 
        idx = htx_get_head(htx); // returns the SL that we skip
@@ -1321,9 +1328,16 @@ static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx
                        break;
 
                n = htx_get_blk_name(htx, blk);
+               v = htx_get_blk_value(htx, blk);
 
-               if (isteqi(n, ist("sec-websocket-key")) &&
+               /* Websocket key is base64 encoded of 16 bytes */
+               if (isteqi(n, ist("sec-websocket-key")) && v.len == 24 &&
                    !(h1m->flags & H1_MF_RESP)) {
+                       /* Copy the key on request side
+                        * we might need it if the server is using h2 and does
+                        * not provide the response
+                        */
+                       memcpy(h1s->ws_key, v.ptr, 24);
                        ws_key_found = 1;
                        break;
                }
@@ -1702,6 +1716,7 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
        struct buffer tmp;
        size_t total = 0;
        int last_data = 0;
+       int ws_key_found = 0;
 
        if (!count)
                goto end;
@@ -1905,6 +1920,13 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                        if (!v.len)
                                                goto skip_hdr;
                                }
+                               else if (isteq(n, ist("upgrade"))) {
+                                       h1_parse_upgrade_header(h1m, v);
+                               }
+                               else if (isteq(n, ist("sec-websocket-accept")) &&
+                                         h1m->flags & H1_MF_RESP) {
+                                       ws_key_found = 1;
+                               }
 
                                /* Skip header if same name is used to add the server name */
                                if (!(h1m->flags & H1_MF_RESP) && h1c->px->server_id_hdr_name &&
@@ -1984,6 +2006,21 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                        h1s->flags |= H1S_F_HAVE_SRV_NAME;
                                }
 
+                               /* Add websocket handshake key if needed */
+                               if ((h1m->flags & (H1_MF_CONN_UPG|H1_MF_UPG_WEBSOCKET)) == (H1_MF_CONN_UPG|H1_MF_UPG_WEBSOCKET) &&
+                                   !ws_key_found) {
+                                       if (h1m->flags & H1_MF_RESP) {
+                                               /* add the response header key */
+                                               char key[29];
+                                               h1_calculate_ws_output_key(h1s->ws_key, key);
+                                               if (!h1_format_htx_hdr(ist("Sec-Websocket-Accept"),
+                                                                      ist(key),
+                                                                      &tmp)) {
+                                                       goto full;
+                                               }
+                                       }
+                               }
+
                                TRACE_PROTO((!(h1m->flags & H1_MF_RESP) ? "H1 request headers xferred" : "H1 response headers xferred"),
                                            H1_EV_TX_DATA|H1_EV_TX_HDRS, h1c->conn, h1s);