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