From patchwork Wed Apr 27 12:34:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 2421 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend41.mail.ord1d.rsapps.net with LMTP id uGaUC0vFaWKdCwAAqwncew (envelope-from ) for ; Wed, 27 Apr 2022 18:35:55 -0400 Received: from proxy3.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net with LMTP id sDnFKEvFaWJCcAAAIasKDg (envelope-from ) for ; Wed, 27 Apr 2022 18:35:55 -0400 Received: from smtp15.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy3.mail.iad3b.rsapps.net with LMTPS id iG6BI0vFaWLHBwAAM8Wetg (envelope-from ) for ; Wed, 27 Apr 2022 18:35:55 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp15.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 6605d5e4-c67a-11ec-b2d1-5254003d6d3a-1-1 Received: from [216.105.38.7] ([216.105.38.7:42588] helo=lists.sourceforge.net) by smtp15.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 19/E7-26086-A45C9626; Wed, 27 Apr 2022 18:35:55 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.94.2) (envelope-from ) id 1njqF2-0000kF-Kt; Wed, 27 Apr 2022 22:34:35 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1njqF1-0000k9-1G for openvpn-devel@lists.sourceforge.net; Wed, 27 Apr 2022 22:34:34 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=0I78TtF7si8+/ZqQr3Gu+Tjog4jgxkV408tdjgxFhno=; b=YK8JZvoH6Wail90+0uhcpJUPFD MVmtUzPQmMMgu8z4k4LzRZKEQo7ndnFR/0un4eL3IgRGOoqravoLWdz98xmMG+Umbp2XRXajfnjVz 3Rxx7qIxtzdCRYoIxuHciH+Zj/DxIIbLVG4lLv7RntJVDknu4Br6lhJry+pn7y1+0qx0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=0I78TtF7si8+/ZqQr3Gu+Tjog4jgxkV408tdjgxFhno=; b=V79PYCiSV1tDqjp7cZSCw78ev5 XD12hGkf7Q9c5x8gkTWfTPTq/8zQHyKa2VIUbhXJjmERcnu2SCvDqJWBtehJfhyljXcZDh5EMUShX p/uE6F1zj5sV/iGSCEBs9i5e381NJwPcB0x1sTxQ+1HytauFNS225Lt0wCJslfm/R8d8=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.94.2) id 1njqEx-0005L5-9N for openvpn-devel@lists.sourceforge.net; Wed, 27 Apr 2022 22:34:33 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1njqEm-000FA6-2z for openvpn-devel@lists.sourceforge.net; Thu, 28 Apr 2022 00:34:20 +0200 Received: (nullmailer pid 241953 invoked by uid 10006); Wed, 27 Apr 2022 22:34:20 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Thu, 28 Apr 2022 00:34:19 +0200 Message-Id: <20220427223419.241904-2-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220427223419.241904-1-arne@rfc2549.org> References: <20220422134038.3801239-1-arne@rfc2549.org> <20220427223419.241904-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: 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 session on the server side leading to poential ressource exhaustian - make the server respond with 3 answers trying [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record X-Headers-End: 1njqEx-0005L5-9N Subject: [Openvpn-devel] [PATCH v2] Implement stateless, HMAC basedsesssion id three way handshake X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox 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 session on the server side leading to poential ressource exhaustian - make the server respond with 3 answers trying to get an ACK for its answer making it a amplification Instead of allocating a connection for each client on the initial packet OpenVPN will now send back a response that contains an HMAC based cookie that the client will need to respond to. This eliminates the amplification attack and resource exhaustion attacks. For tls-crypt-v2 client HMAC based handshake is not used yet. Patch v2: rebase on master Signed-off-by: Arne Schwabe --- doc/doxygen/doc_protocol_overview.h | 2 + src/openvpn/init.c | 4 + src/openvpn/mudp.c | 105 ++++++++++++-- src/openvpn/multi.h | 3 + src/openvpn/openvpn.h | 6 + src/openvpn/ssl.c | 41 +++++- src/openvpn/ssl.h | 8 ++ src/openvpn/ssl_pkt.c | 102 +++++++++++++- src/openvpn/ssl_pkt.h | 55 +++++++- tests/unit_tests/openvpn/test_pkt.c | 204 ++++++++++++++++++++++++++-- 10 files changed, 503 insertions(+), 27 deletions(-) diff --git a/doc/doxygen/doc_protocol_overview.h b/doc/doxygen/doc_protocol_overview.h index f26ce3a36..37de1cb0e 100644 --- a/doc/doxygen/doc_protocol_overview.h +++ b/doc/doxygen/doc_protocol_overview.h @@ -118,6 +118,8 @@ * parts: * * - local \c session_id (random 64 bit value to identify TLS session). + * (the tls-server side uses a HMAC of the client to create a pseudo + * random number for a SYN Cookie like approach) * - HMAC signature of entire encapsulation header for HMAC firewall * [only if \c --tls-auth is specified] (usually 16 or 20 bytes). * - packet-id for replay protection (4 or 8 bytes, includes sequence diff --git a/src/openvpn/init.c b/src/openvpn/init.c index e41bb9d4b..7e7041a8e 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -54,6 +54,7 @@ #include "forward.h" #include "auth_token.h" #include "mss.h" +#include "mudp.h" #include "memdbg.h" @@ -2973,7 +2974,9 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) if (flags & CF_INIT_TLS_AUTH_STANDALONE) { c->c2.tls_auth_standalone = tls_auth_standalone_init(&to, &c->c2.gc); + c->c2.session_id_hmac = session_id_hmac_init(); } + } static void @@ -2992,6 +2995,7 @@ do_init_frame_tls(struct context *c) tls_init_control_channel_frame_parameters(&c->c2.frame, &c->c2.tls_auth_standalone->frame); frame_print(&c->c2.tls_auth_standalone->frame, D_MTU_INFO, "TLS-Auth MTU parms"); + c->c2.tls_auth_standalone->tls_wrap.work = alloc_buf_gc(BUF_SIZE(&c->c2.frame), &c->c2.gc); } } diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c index e5cc36b45..92a1673ee 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -40,24 +40,81 @@ #include #endif +/* Return if this packet should create a new session */ static bool -do_pre_decrypt_check(struct multi_context *m) +do_pre_decrypt_check(struct multi_context *m, + struct tls_pre_decrypt_state *state, + struct mroute_addr addr) { ASSERT(m->top.c2.tls_auth_standalone); enum first_packet_verdict verdict; - struct tls_pre_decrypt_state state = {0}; - verdict = tls_pre_decrypt_lite(m->top.c2.tls_auth_standalone, &state, - &m->top.c2.from, &m->top.c2.buf); + struct tls_auth_standalone *tas = m->top.c2.tls_auth_standalone; - free_tls_pre_decrypt_state(&state); + verdict = tls_pre_decrypt_lite(tas, state, &m->top.c2.from, &m->top.c2.buf); - if (verdict == VERDICT_INVALID || verdict == VERDICT_VALID_CONTROL_V1) + hmac_ctx_t *hmac = m->top.c2.session_id_hmac; + struct openvpn_sockaddr *from = &m->top.c2.from.dest; + int handwindow = m->top.options.handshake_window; + + + if (verdict == VERDICT_VALID_RESET_V3) + { + /* For tls-crypt-v2 we need to keep the state of the first packet to + * store the unwrapped key */ + return true; + } + else if (verdict == VERDICT_VALID_RESET_V2) { + /* Calculate the session ID HMAC for our reply and create reset packet */ + struct session_id sid = calculate_session_id_hmac(state->peer_session_id, + from, hmac, handwindow, 0); + reset_packet_id_send(&tas->tls_wrap.opt.packet_id.send); + tas->tls_wrap.opt.packet_id.rec.initialized = true; + uint8_t header = 0 | (P_CONTROL_HARD_RESET_SERVER_V2 << P_OPCODE_SHIFT); + struct buffer buf = tls_reset_standalone(tas, &sid, + &state->peer_session_id, header); + + + struct context *c = &m->top; + + buf_reset_len(&c->c2.buffers->aux_buf); + buf_copy(&c->c2.buffers->aux_buf, &buf); + m->hmac_reply = c->c2.buffers->aux_buf; + m->hmac_reply_dest = &m->top.c2.from; + msg(D_MULTI_DEBUG, "Reset packet from client, sending HMAC based reset challenge"); + /* We have a reply do not create a new session */ return false; + + } + else if (verdict == VERDICT_VALID_CONTROL_V1 || verdict == VERDICT_VALID_ACK_V1) + { + /* ACK_V1 contains the peer id (our id) while CONTROL_V1 can but does not + * need to contain the peer id */ + struct gc_arena gc = gc_new(); + + bool ret = check_session_id_hmac(state, from, hmac, handwindow); + + const char *peer = print_link_socket_actual(&m->top.c2.from, &gc); + if (!ret) + { + + msg(D_MULTI_MEDIUM, "Packet with invalid or missing SID from %s", peer); + + } + else + { + msg(D_MULTI_DEBUG, "Reset packet from client (%s), " + "sending HMAC based reset challenge", peer); + } + gc_free(&gc); + + return ret; } - return true; + + /* VERDICT_INVALID */ + return false; } /* @@ -114,10 +171,18 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) mi = (struct multi_instance *) he->value; } } + + /* we not have no existing multi instance for this connection */ if (!mi) { - if (do_pre_decrypt_check(m)) + struct tls_pre_decrypt_state state = {0}; + + if (do_pre_decrypt_check(m, &state, real)) { + /* This is an unknown session but with valid tls-auth/tls-crypt (or no auth at all), + * if this is the initial packet of a session, we just send a reply with a HMAC session id and do not + * generate a session slot */ + if (frequency_limit_event_allowed(m->new_connection_limiter)) { mi = multi_create_instance(m, &real); @@ -127,6 +192,14 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) mi->did_real_hash = true; multi_assign_peer_id(m, mi); } + /* If we have a session ids already, ensure that the state is using the same */ + if (session_id_defined(&state.server_session_id) + && session_id_defined((&state.peer_session_id))) + { + mi->context.c2.tls_multi->n_sessions++; + struct tls_session *session = &mi->context.c2.tls_multi->session[TM_ACTIVE]; + session_skip_to_pre_start(session, &state, &m->top.c2.from); + } } else { @@ -135,6 +208,7 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) mroute_addr_print(&real, &gc)); } } + free_tls_pre_decrypt_state(&state); } #ifdef ENABLE_DEBUG @@ -155,7 +229,7 @@ multi_get_create_instance_udp(struct multi_context *m, bool *floated) } /* - * Send a packet to TCP/UDP socket. + * Send a packet to UDP socket. */ static inline void multi_process_outgoing_link(struct multi_context *m, const unsigned int mpp_flags) @@ -165,6 +239,14 @@ multi_process_outgoing_link(struct multi_context *m, const unsigned int mpp_flag { multi_process_outgoing_link_dowork(m, mi, mpp_flags); } + if (m->hmac_reply_dest && m->hmac_reply.len > 0) + { + msg_set_prefix("Connection Attempt"); + m->top.c2.to_link = m->hmac_reply; + m->top.c2.to_link_addr = m->hmac_reply_dest; + process_outgoing_link(&m->top); + m->hmac_reply_dest = NULL; + } } /* @@ -272,6 +354,10 @@ p2mp_iow_flags(const struct multi_context *m) { flags |= IOW_MBUF; } + else if (m->hmac_reply_dest) + { + flags |= IOW_TO_LINK; + } else { flags |= IOW_READ; @@ -364,4 +450,3 @@ tunnel_server_udp(struct context *top) multi_top_free(&multi); close_instance(top); } - diff --git a/src/openvpn/multi.h b/src/openvpn/multi.h index f89c7dbd2..f1e9ab91f 100644 --- a/src/openvpn/multi.h +++ b/src/openvpn/multi.h @@ -191,6 +191,9 @@ struct multi_context { struct context top; /**< Storage structure for process-wide * configuration. */ + struct buffer hmac_reply; + struct link_socket_actual *hmac_reply_dest; + /* * Timer object for stale route check */ diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index 77263dfbe..00cd652fa 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -330,6 +330,12 @@ struct context_2 * received from a new client. See the * \c --tls-auth commandline option. */ + + hmac_ctx_t *session_id_hmac; + /**< the HMAC we use to generate and verify our syn cookie like + * session ids from the server. + */ + /* used to optimize calls to tls_multi_process */ struct interval tmp_int; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index cb3382d6f..80440c411 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1285,6 +1285,10 @@ tls_auth_standalone_init(struct tls_options *tls_options, /* get initial frame parms, still need to finalize */ tas->frame = tls_options->frame; + packet_id_init(&tas->tls_wrap.opt.packet_id, + tls_options->replay_window, tls_options->replay_time, "TAS", + 0); + return tas; } @@ -2398,13 +2402,13 @@ auth_deferred_expire_window(const struct tls_options *o) /** * Move the session from S_INITIAL to S_PRE_START. This will also generate - * the intial message based on ks->initial_opcode + * the initial message based on ks->initial_opcode * * @return if the state change was succesful */ static bool session_move_pre_start(const struct tls_session *session, - struct key_state *ks) + struct key_state *ks, bool skip_initial_send) { struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable); if (!buf) @@ -2418,6 +2422,13 @@ session_move_pre_start(const struct tls_session *session, /* null buffer */ reliable_mark_active_outgoing(ks->send_reliable, buf, ks->initial_opcode); + + /* If we want to skip sending the initial handshake packet we still generate + * it to increase internal counters etc. but immediately mark it as done */ + if (skip_initial_send) + { + reliable_mark_deleted(ks->send_reliable, buf); + } INCR_GENERATED; ks->state = S_PRE_START; @@ -2491,6 +2502,30 @@ session_move_active(struct tls_multi *multi, struct tls_session *session, #endif } +bool +session_skip_to_pre_start(struct tls_session *session, + struct tls_pre_decrypt_state *state, + struct link_socket_actual *from) +{ + struct key_state *ks = &session->key[KS_PRIMARY]; + ks->session_id_remote = state->peer_session_id; + ks->remote_addr = *from; + session->session_id = state->server_session_id; + session->untrusted_addr = *from; + session->burst = true; + + /* The OpenVPN protocol implicitly mandates that packet id always start + * from 0 in the RESET packets as OpenVPN 2.x will not allow gaps in the + * ids and starts always from 0. Since we skip/ignore one (RESET) packet + * in each direction, we need to set the ids to 1 */ + ks->rec_reliable->packet_id = 1; + /* for ks->send_reliable->packet_id, session_move_pre_start moves the + * counter to 1 */ + session->tls_wrap.opt.packet_id.send.id = 1; + return session_move_pre_start(session, ks, true); +} + + static bool tls_process_state(struct tls_multi *multi, @@ -2506,7 +2541,7 @@ tls_process_state(struct tls_multi *multi, /* Initial handshake */ if (ks->state == S_INITIAL) { - state_change = session_move_pre_start(session, ks); + state_change = session_move_pre_start(session, ks, false); } /* Are we timed out on receive? */ diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index e925a16ea..0ba86d3e6 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -556,4 +556,12 @@ tls_session_generate_data_channel_keys(struct tls_session *session); void load_xkey_provider(void); +/* Special method to skip the three way handshake RESET stages. This is + * used by the HMAC code when seeing a packet that matches the previous + * HMAC based stateless server state */ +bool +session_skip_to_pre_start(struct tls_session *session, + struct tls_pre_decrypt_state *state, + struct link_socket_actual *from); + #endif /* ifndef OPENVPN_SSL_H */ diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c index a93027505..56baa2895 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -320,7 +320,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* Allow only the reset packet or the first packet of the actual handshake. */ if (op != P_CONTROL_HARD_RESET_CLIENT_V2 && op != P_CONTROL_HARD_RESET_CLIENT_V3 - && op != P_CONTROL_V1) + && op != P_CONTROL_V1 + && op != P_ACK_V1) { /* * This can occur due to bogus data or DoS packets. @@ -388,9 +389,17 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, { return VERDICT_VALID_CONTROL_V1; } + else if (op == P_ACK_V1) + { + return VERDICT_VALID_ACK_V1; + } + else if (op == P_CONTROL_HARD_RESET_CLIENT_V3) + { + return VERDICT_VALID_RESET_V3; + } else { - return VERDICT_VALID_RESET; + return VERDICT_VALID_RESET_V2; } error: @@ -399,6 +408,7 @@ error: return VERDICT_INVALID; } + struct buffer tls_reset_standalone(struct tls_auth_standalone *tas, struct session_id *own_sid, @@ -430,3 +440,91 @@ tls_reset_standalone(struct tls_auth_standalone *tas, return buf; } + +hmac_ctx_t * +session_id_hmac_init(void) +{ + /* We assume that SHA256 is always available */ + ASSERT(md_valid("SHA256")); + hmac_ctx_t *hmac_ctx = hmac_ctx_new(); + + uint8_t key[SHA256_DIGEST_LENGTH]; + ASSERT(rand_bytes(key, sizeof(key))); + + hmac_ctx_init(hmac_ctx, key, "SHA256"); + return hmac_ctx; +} + +struct session_id +calculate_session_id_hmac(struct session_id client_sid, + const struct openvpn_sockaddr *from, + hmac_ctx_t *hmac, + int handwindow, int offset) +{ + union { + uint8_t hmac_result[SHA256_DIGEST_LENGTH]; + struct session_id sid; + } result; + + /* Get the valid time quantisation for our hmac, + * we divide time by handwindow/2 and allow the current + * and the previous timestamp */ + uint32_t session_id_time = now/((handwindow+1)/2) + offset; + + hmac_ctx_reset(hmac); + /* We do not care about endian here since it does not need to be + * portable */ + hmac_ctx_update(hmac, (const uint8_t *) &session_id_time, + sizeof(session_id_time)); + + /* add client IP and port */ + switch (af_addr_size(from->addr.sa.sa_family)) + { + case AF_INET: + hmac_ctx_update(hmac, (const uint8_t *) &from->addr.in4, sizeof(struct sockaddr_in)); + break; + + case AF_INET6: + hmac_ctx_update(hmac, (const uint8_t *) &from->addr.in6, sizeof(struct sockaddr_in6)); + break; + } + + /* add session id of client */ + hmac_ctx_update(hmac, client_sid.id, SID_SIZE); + + hmac_ctx_final(hmac, result.hmac_result); + + return result.sid; +} + +bool +check_session_id_hmac(struct tls_pre_decrypt_state *state, + const struct openvpn_sockaddr *from, + hmac_ctx_t *hmac, + int handwindow) +{ + if (!from) + { + return false; + } + + struct buffer buf = state->newbuf; + struct reliable_ack ack; + + if (!reliable_ack_parse(&buf, &ack, &state->server_session_id)) + { + return false; + } + + for (int i = -1; i<=1; i++) + { + struct session_id expected_id = + calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, i); + + if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE)) + { + return true; + } + } + return false; +} diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h index dd9361407..75cdc1c58 100644 --- a/src/openvpn/ssl_pkt.h +++ b/src/openvpn/ssl_pkt.h @@ -77,11 +77,15 @@ struct tls_auth_standalone }; enum first_packet_verdict { - /** This packet is a valid reset packet from the peer */ - VERDICT_VALID_RESET, - /** This packet is a valid control packet from the peer, - * i.e. it has a valid session id hmac in it */ + /** This packet is a valid reset packet from the peer (all but tls-crypt-v2) */ + VERDICT_VALID_RESET_V2, + /** This is a valid v3 reset (tls-crypt-v2) */ + VERDICT_VALID_RESET_V3, + /** This packet is a valid control packet from the peer */ VERDICT_VALID_CONTROL_V1, + /** This packet is a valid ACK control packet from the peer, + * i.e. it has a valid session id hmac in it */ + VERDICT_VALID_ACK_V1, /** the packet failed on of the various checks */ VERDICT_INVALID }; @@ -94,6 +98,7 @@ struct tls_pre_decrypt_state { struct tls_wrap_ctx tls_wrap_tmp; struct buffer newbuf; struct session_id peer_session_id; + struct session_id server_session_id; }; /** @@ -141,6 +146,48 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, const struct link_socket_actual *from, const struct buffer *buf); +/* Creates an SHA256 HMAC context with a random key that is used for the + * session id. + * + * We do not support loading this from a config file since continuing session + * between restarts of OpenVPN has never been supported and that includes + * early session setup + */ +hmac_ctx_t *session_id_hmac_init(void); + +/** + * Calculates the a HMAC based server session id based on a client session id + * and socket addr. + * + * @param client_sid session id of the client + * @param from link_socket from the client + * @param hmac the hmac context to use for the calculation + * @param handwindow the quantisation of the current time + * @param offset offset to 'now' to use + * @return the expected server session id + */ +struct session_id +calculate_session_id_hmac(struct session_id client_sid, + const struct openvpn_sockaddr *from, + hmac_ctx_t *hmac, + int handwindow, int offset); + +/** + * Checks if a control packet has a correct HMAC server session id + * + * @param client_sid session id of the client + * @param from link_socket from the client + * @param hmac the hmac context to use for the calculation + * @param handwindow the quantisation of the current time + * @param offset offset to 'now' to use + * @return the expected server session id + */ +bool +check_session_id_hmac(struct tls_pre_decrypt_state *state, + const struct openvpn_sockaddr *from, + hmac_ctx_t *hmac, + int handwindow); + /* * Write a control channel authentication record. */ diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c index 77338cd3a..c4e23521d 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,8 +347,170 @@ 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); + + check_session_id_hmac(&state, &from.dest, hmac, 30); + + 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); + + check_session_id_hmac(&state, &from.dest, hmac, 30); + 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(); + + 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, 100, 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, 100, -1); + struct session_id server_id_p1 = calculate_session_id_hmac(client_id, &addr, hmac, 100, 1); + struct session_id server_id_p2 = calculate_session_id_hmac(client_id, &addr, hmac, 100, 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); + + /* moving now should also move the calculated ids */ + now = 1062; + + struct session_id server_id2_m2 = calculate_session_id_hmac(client_id, &addr, hmac, 100, -2); + struct session_id server_id2_m1 = calculate_session_id_hmac(client_id, &addr, hmac, 100, -1); + struct session_id server_id2 = calculate_session_id_hmac(client_id, &addr, hmac, 100, 0); + struct session_id server_id2_p1 = calculate_session_id_hmac(client_id, &addr, hmac, 100, 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 @@ -351,7 +535,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 +569,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); @@ -414,6 +598,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), };