From: Amaury Denoyelle Date: Fri, 11 Dec 2020 16:53:03 +0000 (+0100) Subject: MEDIUM: h1: generate WebSocket key on response if needed X-Git-Tag: v2.4-dev7~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c193823343192014e7ebfe99a9bb1562c83ec81e;p=thirdparty%2Fhaproxy.git MEDIUM: h1: generate WebSocket key on response if needed 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. --- diff --git a/include/haproxy/h1.h b/include/haproxy/h1.h index 32d6caf8bf..fe78e8d978 100644 --- a/include/haproxy/h1.h +++ b/include/haproxy/h1.h @@ -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) { diff --git a/src/h1.c b/src/h1.c index ca39d97a4a..95bcc1f0e4 100644 --- a/src/h1.c +++ b/src/h1.c @@ -11,7 +11,11 @@ */ #include + +#include + #include +#include #include #include @@ -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 . Following the + * rfc6455, must be 24 bytes longs. The result is stored in + * 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); +} diff --git a/src/mux_h1.c b/src/mux_h1.c index 413ed09bec..b4b5eba2f6 100644 --- a/src/mux_h1.c +++ b/src/mux_h1.c @@ -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);