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 fe78e8d..3c08580 100644
--- a/include/haproxy/h1.h
+++ b/include/haproxy/h1.h
@@ -150,6 +150,7 @@
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 95bcc1f..3a6c1c3 100644
--- 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 @@
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"
/*
diff --git a/src/mux_h1.c b/src/mux_h1.c
index b4b5eba..871f1ff 100644
--- a/src/mux_h1.c
+++ b/src/mux_h1.c
@@ -1307,7 +1307,11 @@
* 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 @@
}
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 @@
(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 @@
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 @@
/* 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);