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.
This commit is contained in:
Amaury Denoyelle 2020-12-11 17:53:07 +01:00 committed by Christopher Faulet
parent 9bf957335e
commit aad333a9fc
3 changed files with 50 additions and 5 deletions

View 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 */

View File

@ -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"
/*

View 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);