aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorArne Schwabe2022-05-02 17:43:10 +0200
committerGert Doering2022-05-05 12:12:55 +0200
commitb364711486dc6371ad2659a5aa190941136f4f04 (patch)
tree0c766020ff14f07d5041ae5cb34767a506e99ce6 /tests
parent870af5f54967821c72074a7c5c60e10a4561d95e (diff)
downloadopenvpn-b364711486dc6371ad2659a5aa190941136f4f04.zip
openvpn-b364711486dc6371ad2659a5aa190941136f4f04.tar.gz
Implement stateless HMAC-based sesssion-id three-way-handshake
OpenVPN currently has a bit of a weakness in its early three way handshake A single client reset packet (first packet of the handshake) will - trigger creating a session on the server side leading to potential ressource exhaustion - make the server respond with 3 answers trying to get an ACK for its P_CONTROL_HARD_RESET_SERVER_V2 answer making it an amplification Instead of allocating a connection for each client on the initial packet OpenVPN will now calculate a session id based on a HMAC that serves as verifiable cookie that can be checked for authenticity when the client responds with it. This eliminates the amplification attack and resource exhaustion attacks. For tls-crypt-v2 clients the HMAC based handshake is not used yet (will be added in one of the next patches). Patch v2: rebase on master patch v3: fix unit tests, improve comment/style of code Signed-off-by: Arne Schwabe <arne@rfc2549.org> Acked-by: Frank Lichtenheld <frank@lichtenheld.com> Message-Id: <20220502154310.836947-1-arne@rfc2549.org> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg24262.html Signed-off-by: Gert Doering <gert@greenie.muc.de>
Diffstat (limited to 'tests')
-rw-r--r--tests/unit_tests/openvpn/test_pkt.c210
1 files changed, 201 insertions, 9 deletions
diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c
index 77338cd..3681262 100644
--- a/tests/unit_tests/openvpn/test_pkt.c
+++ b/tests/unit_tests/openvpn/test_pkt.c
@@ -44,6 +44,7 @@
#include "mock_msg.h"
#include "mss.h"
+#include "reliable.h"
int
parse_line(const char *line, char **p, const int n, const char *file,
@@ -151,6 +152,21 @@ const uint8_t client_ack_tls_auth_randomid[] = {
0x56, 0x33, 0x6b
};
+/* This is a truncated packet as we do not care for the TLS payload in the
+ * unit test */
+const uint8_t client_control_with_ack[] = {
+ 0x20, 0x78, 0x19, 0xbf, 0x2e, 0xbc, 0xd1, 0x9a,
+ 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0xea,
+ 0xfe,0xbf, 0xa4, 0x41, 0x8a, 0xe3, 0x1b,
+ 0x00, 0x00, 0x00, 0x01, 0x16, 0x03, 0x01
+};
+
+const uint8_t client_ack_none_random_id[] = {
+ 0x28, 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79,
+ 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0xdd,
+ 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e
+};
+
struct tls_auth_standalone
init_tas_auth(int key_direction)
{
@@ -168,6 +184,7 @@ init_tas_auth(int key_direction)
crypto_read_openvpn_key(&tls_crypt_kt, &tas.tls_wrap.opt.key_ctx_bi,
static_key, true, key_direction,
"Control Channel Authentication", "tls-auth");
+
return tas;
}
@@ -210,7 +227,7 @@ test_tls_decrypt_lite_crypt(void **ut_state)
buf_reset_len(&buf);
buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_tls_crypt));
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
free_tls_pre_decrypt_state(&state);
/* flip a byte in various places */
@@ -251,17 +268,19 @@ test_tls_decrypt_lite_auth(void **ut_state)
buf_reset_len(&buf);
buf_write(&buf, client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
+ free_tls_pre_decrypt_state(&state);
free_tls_pre_decrypt_state(&state);
/* The pre decrypt function should not modify the buffer, so calling it
* again should have the same result */
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
free_tls_pre_decrypt_state(&state);
/* and buf memory should be equal */
assert_memory_equal(BPTR(&buf), client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth));
+ free_tls_pre_decrypt_state(&state);
buf_reset_len(&buf);
buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));
@@ -273,6 +292,7 @@ test_tls_decrypt_lite_auth(void **ut_state)
BPTR(&buf)[20] = 0x23;
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
assert_int_equal(verdict, VERDICT_INVALID);
+ free_tls_pre_decrypt_state(&state);
free_tls_pre_decrypt_state(&state);
/* Wrong key direction gives a wrong hmac key and should not validate */
@@ -304,19 +324,21 @@ test_tls_decrypt_lite_none(void **ut_state)
/* the method will not do additional test, so the tls-auth and tls-crypt
* reset will be accepted */
enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
free_tls_pre_decrypt_state(&state);
buf_reset_len(&buf);
buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
+ free_tls_pre_decrypt_state(&state);
free_tls_pre_decrypt_state(&state);
buf_reset_len(&buf);
buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_none));
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
+ free_tls_pre_decrypt_state(&state);
free_tls_pre_decrypt_state(&state);
@@ -325,11 +347,178 @@ test_tls_decrypt_lite_none(void **ut_state)
buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);
+
free_tls_pre_decrypt_state(&state);
free_buf(&buf);
}
static void
+test_parse_ack(void **ut_state)
+{
+ struct buffer buf = alloc_buf(1024);
+ buf_write(&buf, client_control_with_ack, sizeof(client_control_with_ack));
+
+ /* skip over op code and peer session id */
+ buf_advance(&buf, 9);
+
+ struct reliable_ack ack;
+ struct session_id sid;
+ bool ret;
+
+ ret = reliable_ack_parse(&buf, &ack, &sid);
+ assert_true(ret);
+
+ assert_int_equal(ack.len, 1);
+ assert_int_equal(ack.packet_id[0], 0);
+
+ struct session_id expected_id = { .id = {0xea, 0xfe, 0xbf, 0xa4, 0x41, 0x8a, 0xe3, 0x1b }};
+ assert_memory_equal(&sid, &expected_id, SID_SIZE);
+
+ buf_reset_len(&buf);
+ buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id));
+
+ /* skip over op code and peer session id */
+ buf_advance(&buf, 9);
+ ret = reliable_ack_parse(&buf, &ack, &sid);
+ assert_true(ret);
+
+ assert_int_equal(ack.len, 1);
+ assert_int_equal(ack.packet_id[0], 0);
+
+ struct session_id expected_id2 = { .id = {0xdd, 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e }};
+ assert_memory_equal(&sid, &expected_id2, SID_SIZE);
+
+ buf_reset_len(&buf);
+ buf_write(&buf, client_reset_v2_none, sizeof(client_reset_v2_none));
+
+ /* skip over op code and peer session id */
+ buf_advance(&buf, 9);
+ ret = reliable_ack_parse(&buf, &ack, &sid);
+
+ free_buf(&buf);
+}
+
+static void
+test_verify_hmac_tls_auth(void **ut_state)
+{
+ hmac_ctx_t *hmac = session_id_hmac_init();
+
+ struct link_socket_actual from = { 0 };
+ struct tls_auth_standalone tas = { 0 };
+ struct tls_pre_decrypt_state state = { 0 };
+
+ struct buffer buf = alloc_buf(1024);
+ enum first_packet_verdict verdict;
+
+ tas = init_tas_auth(KEY_DIRECTION_NORMAL);
+
+ buf_reset_len(&buf);
+ buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid));
+ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
+ assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1);
+
+ /* This is a valid packet but containing a random id instead of an HMAC id*/
+ bool valid = check_session_id_hmac(&state, &from.dest, hmac, 30);
+ assert_false(valid);
+
+ free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi);
+ free_key_ctx(&tas.tls_wrap.tls_crypt_v2_server_key);
+ free_tls_pre_decrypt_state(&state);
+ free_buf(&buf);
+ hmac_ctx_cleanup(hmac);
+ hmac_ctx_free(hmac);
+}
+
+static void
+test_verify_hmac_none(void **ut_state)
+{
+ hmac_ctx_t *hmac = session_id_hmac_init();
+
+ struct link_socket_actual from = { 0 };
+ struct tls_auth_standalone tas = { 0 };
+ struct tls_pre_decrypt_state state = { 0 };
+
+ struct buffer buf = alloc_buf(1024);
+ enum first_packet_verdict verdict;
+
+ tas.tls_wrap.mode = TLS_WRAP_NONE;
+
+ buf_reset_len(&buf);
+ buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id));
+ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
+ assert_int_equal(verdict, VERDICT_VALID_ACK_V1);
+
+ bool valid = check_session_id_hmac(&state, &from.dest, hmac, 30);
+ assert_true(valid);
+
+ free_tls_pre_decrypt_state(&state);
+ free_buf(&buf);
+ hmac_ctx_cleanup(hmac);
+ hmac_ctx_free(hmac);
+}
+
+static hmac_ctx_t *
+init_static_hmac(void)
+{
+ ASSERT(md_valid("SHA256"));
+ hmac_ctx_t *hmac_ctx = hmac_ctx_new();
+
+ uint8_t key[SHA256_DIGEST_LENGTH] = {1, 2, 3};
+
+ hmac_ctx_init(hmac_ctx, key, "SHA256");
+ return hmac_ctx;
+}
+
+static void
+test_calc_session_id_hmac_static(void **ut_state)
+{
+ hmac_ctx_t *hmac = init_static_hmac();
+ static const int handwindow = 100;
+
+ struct openvpn_sockaddr addr = {0 };
+
+ /* we do not use htons functions here since the hmac calculate function
+ * also does not care about the endianness of the data but just assumes
+ * the endianness doesn't change between calls */
+ addr.addr.in4.sin_family = AF_INET;
+ addr.addr.in4.sin_addr.s_addr = 0xff000ff;
+ addr.addr.in4.sin_port = 1194;
+
+
+ struct session_id client_id = { {0, 1, 2, 3, 4, 5, 6, 7}};
+
+ now = 1005;
+ struct session_id server_id = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 0);
+
+ struct session_id expected_server_id = { {0xba, 0x83, 0xa9, 0x00, 0x72, 0xbd,0x93, 0xba }};
+ assert_memory_equal(expected_server_id.id, server_id.id, SID_SIZE);
+
+ struct session_id server_id_m1 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -1);
+ struct session_id server_id_p1 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 1);
+ struct session_id server_id_p2 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 2);
+
+ assert_memory_not_equal(expected_server_id.id, server_id_m1.id, SID_SIZE);
+ assert_memory_not_equal(expected_server_id.id, server_id_p1.id, SID_SIZE);
+
+ /* changing the time puts us into the next hmac time window (handwindow/2=50)
+ * and shifts the ids by one */
+ now = 1062;
+
+ struct session_id server_id2_m2 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -2);
+ struct session_id server_id2_m1 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, -1);
+ struct session_id server_id2 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 0);
+ struct session_id server_id2_p1 = calculate_session_id_hmac(client_id, &addr, hmac, handwindow, 1);
+
+ assert_memory_equal(server_id2_m2.id, server_id_m1.id, SID_SIZE);
+ assert_memory_equal(server_id2_m1.id, expected_server_id.id, SID_SIZE);
+ assert_memory_equal(server_id2.id, server_id_p1.id, SID_SIZE);
+ assert_memory_equal(server_id2_p1.id, server_id_p2.id, SID_SIZE);
+
+ hmac_ctx_cleanup(hmac);
+ hmac_ctx_free(hmac);
+}
+
+static void
test_generate_reset_packet_plain(void **ut_state)
{
struct link_socket_actual from = { 0 };
@@ -351,7 +540,7 @@ test_generate_reset_packet_plain(void **ut_state)
verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
/* Assure repeated generation of reset is deterministic/stateless*/
assert_memory_equal(state.peer_session_id.id, client_id.id, SID_SIZE);
@@ -385,7 +574,7 @@ test_generate_reset_packet_tls_auth(void **ut_state)
struct buffer buf = tls_reset_standalone(&tas_client, &client_id, &server_id, header);
enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas_server, &state, &from, &buf);
- assert_int_equal(verdict, VERDICT_VALID_RESET);
+ assert_int_equal(verdict, VERDICT_VALID_RESET_V2);
assert_memory_equal(state.peer_session_id.id, client_id.id, SID_SIZE);
@@ -396,7 +585,6 @@ test_generate_reset_packet_tls_auth(void **ut_state)
assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLEN(&buf));
free_buf(&buf2);
-
free_tls_pre_decrypt_state(&state);
packet_id_free(&tas_client.tls_wrap.opt.packet_id);
@@ -414,6 +602,10 @@ main(void)
cmocka_unit_test(test_tls_decrypt_lite_none),
cmocka_unit_test(test_tls_decrypt_lite_auth),
cmocka_unit_test(test_tls_decrypt_lite_crypt),
+ cmocka_unit_test(test_parse_ack),
+ cmocka_unit_test(test_calc_session_id_hmac_static),
+ cmocka_unit_test(test_verify_hmac_none),
+ cmocka_unit_test(test_verify_hmac_tls_auth),
cmocka_unit_test(test_generate_reset_packet_plain),
cmocka_unit_test(test_generate_reset_packet_tls_auth),
};