From: Amaury Denoyelle Date: Fri, 11 Dec 2020 16:53:07 +0000 (+0100) Subject: MEDIUM: h1: add a WebSocket key on handshake if needed X-Git-Tag: v2.4-dev7~82 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aad333a9fceb3247a503d15c140aa1d7c2193a3c;p=thirdparty%2Fhaproxy.git MEDIUM: h1: add a WebSocket key on handshake if needed 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. --- diff --git a/include/haproxy/h1.h b/include/haproxy/h1.h index fe78e8d978..3c085801bb 100644 --- a/include/haproxy/h1.h +++ b/include/haproxy/h1.h @@ -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 */ diff --git a/src/h1.c b/src/h1.c index 95bcc1f0e4..3a6c1c3309 100644 --- a/src/h1.c +++ b/src/h1.c @@ -18,6 +18,7 @@ #include #include #include +#include /* 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 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" /* diff --git a/src/mux_h1.c b/src/mux_h1.c index b4b5eba2f6..871f1ff408 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -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);