]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h1: add a WebSocket key on handshake if needed
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 11 Dec 2020 16:53:07 +0000 (17:53 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 28 Jan 2021 15:37:14 +0000 (16:37 +0100)
Add the header Sec-Websocket-Key when generating a h1 handshake websocket
without this header. This is the case when doing h2-h1 conversion.

The key is randomly generated and base64 encoded. It is stored on the session
side to be able to verify response key and reject it if not valid.

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

index fe78e8d97881ebeeaf153150a2069e5ea0870bd3..3c085801bb2e76f21cd07a25d0796087ba91ebe5 100644 (file)
@@ -150,6 +150,7 @@ 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_generate_random_ws_input_key(char key_out[25]);
 void h1_calculate_ws_output_key(const char *key, char *result);
 
 /* for debugging, reports the HTTP/1 message state name */
index 95bcc1f0e4632115cc64bd98f78ede177be0aec6..3a6c1c3309d677a54564dfd2c91adbb090e574c2 100644 (file)
--- a/src/h1.c
+++ b/src/h1.c
@@ -18,6 +18,7 @@
 #include <haproxy/base64.h>
 #include <haproxy/h1.h>
 #include <haproxy/http-hdr.h>
+#include <haproxy/tools.h>
 
 /* Parse the Content-Length header field of an HTTP/1 request. The function
  * checks all possible occurrences of a comma-delimited value, and verifies
@@ -1056,6 +1057,21 @@ int h1_measure_trailers(const struct buffer *buf, unsigned int ofs, unsigned int
        return count - ofs;
 }
 
+/* Generate a random key for a WebSocket Handshake in respect with rfc6455
+ * The key is 128-bits long encoded as a base64 string in <key_out> parameter
+ * (25 bytes long).
+ */
+void h1_generate_random_ws_input_key(char key_out[25])
+{
+       /* generate a random websocket key */
+       const uint64_t rand1 = ha_random64(), rand2 = ha_random64();
+       char key[16];
+
+       memcpy(key, &rand1, 8);
+       memcpy(&key[8], &rand2, 8);
+       a2base64(key, 16, key_out, 25);
+}
+
 #define H1_WS_KEY_SUFFIX_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 
 /*
index b4b5eba2f6c658403270c11507c60a43a8089c3e..871f1ff408955b6f45d69804a04e7a1a63dff20b 100644 (file)
@@ -1307,7 +1307,11 @@ static void h1_set_tunnel_mode(struct h1s *h1s)
  * 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
+ * On the response side, the key might be verified if haproxy has been
+ * responsible for the generation of a key. This happens when a h2 client is
+ * interfaced with a h1 server.
+ *
+ * Returns 0 if no key found or invalid key
  */
 static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx *htx)
 {
@@ -1343,6 +1347,15 @@ static int h1_search_websocket_key(struct h1s *h1s, struct h1m *h1m, struct htx
                }
                else if (isteqi(n, ist("sec-websocket-accept")) &&
                         h1m->flags & H1_MF_RESP) {
+                       /* Need to verify the response key if the input was
+                        * generated by haproxy
+                        */
+                       if (h1s->ws_key[0]) {
+                               char key[29];
+                               h1_calculate_ws_output_key(h1s->ws_key, key);
+                               if (!isteqi(ist(key), v))
+                                       break;
+                       }
                        ws_key_found = 1;
                        break;
                }
@@ -1391,7 +1404,7 @@ static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *h
            (H1_MF_CONN_UPG|H1_MF_UPG_WEBSOCKET)) {
                int ws_ret = h1_search_websocket_key(h1s, h1m, htx);
                if (!ws_ret) {
-                       TRACE_DEVEL("leaving on websocket missing key", H1_EV_RX_DATA|H1_EV_RX_HDRS, h1s->h1c->conn, h1s);
+                       TRACE_DEVEL("leaving on websocket missing/invalid key", H1_EV_RX_DATA|H1_EV_RX_HDRS, h1s->h1c->conn, h1s);
                        h1s->flags |= H1S_F_PARSING_ERROR;
                        TRACE_USER("parsing error, reject H1 message", H1_EV_RX_DATA|H1_EV_RX_HDRS|H1_EV_H1S_ERR, h1s->h1c->conn, h1s);
                        h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
@@ -1923,8 +1936,10 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                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) {
+                               else if ((isteq(n, ist("sec-websocket-accept")) &&
+                                         h1m->flags & H1_MF_RESP) ||
+                                        (isteq(n, ist("sec-websocket-key")) &&
+                                         !(h1m->flags & H1_MF_RESP))) {
                                        ws_key_found = 1;
                                }
 
@@ -2009,7 +2024,20 @@ static size_t h1_process_output(struct h1c *h1c, struct buffer *buf, size_t coun
                                /* 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) {
+                                       if (!(h1m->flags & H1_MF_RESP)) {
+                                               /* generate a random websocket key
+                                                * stored in the session to
+                                                * verify it on the response side
+                                                */
+                                               h1_generate_random_ws_input_key(h1s->ws_key);
+
+                                               if (!h1_format_htx_hdr(ist("Sec-Websocket-Key"),
+                                                                      ist(h1s->ws_key),
+                                                                      &tmp)) {
+                                                       goto full;
+                                               }
+                                       }
+                                       else {
                                                /* add the response header key */
                                                char key[29];
                                                h1_calculate_ws_output_key(h1s->ws_key, key);