Attention is currently required from: plaisthos.
Hello plaisthos,
I'd like you to do a code review.
Please visit
http://gerrit.openvpn.net/c/openvpn/+/1750?usp=email
to review the following change.
Change subject: oob: Wrap the client SERVER_PROBE with tls-auth/tls-crypt
......................................................................
oob: Wrap the client SERVER_PROBE with tls-auth/tls-crypt
The client --server-probe previously sent a plaintext SERVER_PROBE and
parsed replies by hand, so it only worked against a server with no
control-channel wrapping. Build a standalone wrapping context for the
probe (mirroring the server's tls_auth_standalone) and route both the
outgoing probe and the incoming replies through the same control-channel
path the rest of the code uses:
- tls_wrap_oob_standalone() wraps the probe payload, applying the
tls-auth HMAC or tls-crypt encryption (or nothing, when neither is
configured).
- read_control_auth() unwraps each reply, verifying the HMAC /
decrypting and stripping the opcode + session id, on a per-packet
copy of the wrapping context (as tls_pre_decrypt_lite() does).
With neither tls-auth nor tls-crypt configured the context stays in
TLS_WRAP_NONE and the on-wire probe is byte-for-byte identical to before,
so the plaintext case is unchanged.
tls-crypt-v2 is not supported yet: the server only learns the client key
from the WKc carried in the TLS handshake, which an out-of-band probe
cannot provide. Such configurations skip probing and keep the configured
remote order.
Change-Id: I1f9d7b5a5ec19bf77c0c212795f583c5ba4c03ac
Signed-off-by: Lev Stipakov <[email protected]>
---
M src/openvpn/oob_client.c
1 file changed, 111 insertions(+), 30 deletions(-)
git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/50/1750/1
diff --git a/src/openvpn/oob_client.c b/src/openvpn/oob_client.c
index 74b892b..5f22650 100644
--- a/src/openvpn/oob_client.c
+++ b/src/openvpn/oob_client.c
@@ -29,6 +29,8 @@
#include "oob_client.h"
#include "openvpn.h"
#include "oob.h"
+#include "init.h"
+#include "ssl.h"
#include "ssl_pkt.h"
#include "session_id.h"
#include "socket.h"
@@ -59,21 +61,54 @@
struct timeval sent_at; /* when the probe was sent, for RTT measurement */
};
-/* Build a plaintext SERVER_PROBE packet:
- * [opcode | key_id=0] [client session id] [SERVER_PROBE message]
- * This is the unauthenticated OOB wire format; adding tls-auth/tls-crypt
- * wrapping for the probe is a follow-up (it only works against a server with
- * no control-channel wrapping for now). */
-static bool
-oob_probe_build_packet(struct buffer *buf, const struct session_id *client_sid)
+/* Build a standalone control-channel wrapping context for the probe, mirroring
+ * the tls_auth_standalone the server uses to answer it. With neither tls-auth
+ * nor tls-crypt configured the context stays in TLS_WRAP_NONE and the probe is
+ * sent in plaintext, exactly as before; with either configured the probe is
+ * authenticated/encrypted like any other control packet. Returns NULL (and
+ * logs) for configurations the probe cannot wrap yet, in which case the caller
+ * skips probing and keeps the configured remote order. */
+static struct tls_auth_standalone *
+oob_probe_init_tls_auth_standalone(struct context *c, struct gc_arena *gc)
{
- const struct oob_probe_parameter param = {
- .timestamp = (uint64_t)now,
- .flags = 0,
- };
- uint8_t header = (uint8_t)(P_CONTROL_OOB_V1 << P_OPCODE_SHIFT);
- return buf_write_u8(buf, header) && session_id_write(client_sid, buf)
- && oob_server_probe_write(buf, ¶m);
+ /* The probe runs before next_connection_entry() maps a connection entry
+ * into options.ce, so take the wrapping config from the first connection
+ * entry directly. A global --tls-auth/--tls-crypt is copied into every
+ * connection-list entry by options_postprocess_mutate_ce(), so the first
+ * entry carries it even though options.ce does not yet. (Probing wraps a
+ * single packet for all remotes, so per-connection-block keys are not
+ * supported; the first entry's wrapping is used for all.) */
+ const struct connection_entry *ce = c->options.connection_list->array[0];
+
+ /* tls-crypt-v2 wraps with a per-client key the server only learns from the
+ * wrapped client key (WKc) carried in the TLS handshake. An out-of-band
+ * probe carries no WKc, so the server cannot unwrap it; skip probing
rather
+ * than send something unverifiable. */
+ if (ce->tls_crypt_v2_file)
+ {
+ msg(D_LOW, "server-probe: not supported with tls-crypt-v2; using
configured order");
+ return NULL;
+ }
+
+ /* Load the tls-auth/tls-crypt key material into c->c1.ks (a no-op if
neither
+ * is configured). This is run again per-connection later; calling it early
+ * here is harmless. */
+ do_init_tls_wrap_key(c, ce);
+
+ struct tls_options to;
+ CLEAR(to);
+ init_tls_wrap_ctx(&to.tls_wrap, ce, c->options.tls_client, &c->c1.ks,
&c->c1.pid_persist);
+ to.replay_window = c->options.replay_window;
+ to.replay_time = c->options.replay_time;
+
+ struct tls_auth_standalone *tas = tls_auth_standalone_init(&to, gc);
+
+ /* Control-channel frame and work buffers, mirroring do_init_frame_tls().
*/
+ tls_init_control_channel_frame_parameters(&tas->frame, ce->tls_mtu);
+ tas->tls_wrap.work = alloc_buf_gc(BUF_SIZE(&tas->frame), gc);
+ tas->workbuf = alloc_buf_gc(BUF_SIZE(&tas->frame), gc);
+
+ return tas;
}
/* Open a UDP socket for probing. Prefer a dual-stack IPv6 socket (so a single
@@ -141,9 +176,9 @@
/* Parse one received datagram as a PROBE_REPLY and, if valid and matching one
* of the probes we sent, record the reply in @p results. */
static void
-oob_probe_handle_reply(const uint8_t *data, int len, const struct session_id
*client_sid,
- const struct sockaddr_storage *from, const struct
probe_target *targets,
- struct oob_probe_result *results, int n)
+oob_probe_handle_reply(uint8_t *data, int len, const struct session_id
*client_sid,
+ const struct tls_wrap_ctx *base_wrap, const struct
sockaddr_storage *from,
+ const struct probe_target *targets, struct
oob_probe_result *results, int n)
{
/* Need at least the opcode byte and the session id. */
if (len < 1 + (int)SID_SIZE || (data[0] >> P_OPCODE_SHIFT) !=
P_CONTROL_OOB_V1)
@@ -153,7 +188,18 @@
struct buffer buf;
buf_set_read(&buf, data, (size_t)len);
- buf_advance(&buf, 1 + SID_SIZE); /* skip opcode + server session id */
+
+ /* Unwrap the reply with the same control-channel path the server used to
+ * wrap it: this verifies the tls-auth HMAC / decrypts tls-crypt, and (in
all
+ * modes) strips the opcode + server session id, leaving buf at the TLV
+ * payload. With no tls-auth/tls-crypt it just strips those header bytes,
as
+ * before. read_control_auth() mutates the wrapping context, so we work on
a
+ * per-packet copy (as tls_pre_decrypt_lite() does on the server). */
+ struct tls_wrap_ctx wrap = *base_wrap;
+ if (!read_control_auth(&buf, &wrap, NULL, NULL, true))
+ {
+ return; /* not for us, or failed authentication */
+ }
struct oob_probe_reply reply;
if (!oob_client_reply_read(&buf, &reply))
@@ -206,8 +252,9 @@
* matches a probe we sent. Returns true if every sent probe has been
answered. */
static bool
oob_probe_receive_slice(socket_descriptor_t sd, const struct timeval *deadline,
- const struct session_id *client_sid, const struct
probe_target *targets,
- struct oob_probe_result *results, int n, int
outstanding)
+ const struct session_id *client_sid, const struct
tls_wrap_ctx *wrap,
+ const struct probe_target *targets, struct
oob_probe_result *results, int n,
+ int outstanding)
{
while (true)
{
@@ -240,7 +287,7 @@
(struct sockaddr *)&from, &fromlen);
if (len > 0)
{
- oob_probe_handle_reply(data, len, client_sid, &from, targets,
results, n);
+ oob_probe_handle_reply(data, len, client_sid, wrap, &from,
targets, results, n);
if (oob_count_answered(targets, results, n) >= outstanding)
{
return true; /* every probe we sent has been answered */
@@ -272,8 +319,8 @@
* once the window elapses or every sent probe has been answered. */
static void
oob_probe_collect(socket_descriptor_t sd, const struct buffer *probe,
- const struct session_id *client_sid, const struct
probe_target *targets,
- struct oob_probe_result *results, int n)
+ const struct session_id *client_sid, const struct
tls_wrap_ctx *wrap,
+ const struct probe_target *targets, struct oob_probe_result
*results, int n)
{
/* number of probes we actually sent: stop early once they all answer */
int want = 0;
@@ -297,7 +344,7 @@
deadline.tv_usec -= 1000000;
}
- if (oob_probe_receive_slice(sd, &deadline, client_sid, targets,
results, n, want))
+ if (oob_probe_receive_slice(sd, &deadline, client_sid, wrap, targets,
results, n, want))
{
return; /* all answered */
}
@@ -339,6 +386,16 @@
struct gc_arena gc = gc_new();
+ /* Wrapping context for the probe (tls-auth/tls-crypt, or plaintext if
+ * neither). NULL means this configuration cannot be probed; keep the
+ * configured order. */
+ struct tls_auth_standalone *tas = oob_probe_init_tls_auth_standalone(c,
&gc);
+ if (!tas)
+ {
+ gc_free(&gc);
+ return;
+ }
+
/* A single random session id identifies all of our probes; servers echo it
* back in the reply's peer_session_id, letting us reject spoofed replies.
*/
struct session_id client_sid;
@@ -349,6 +406,7 @@
if (sd == SOCKET_UNDEFINED)
{
msg(D_LOW, "server-probe: could not open probe socket; using
configured order");
+ tls_auth_standalone_free(tas);
gc_free(&gc);
return;
}
@@ -356,17 +414,39 @@
struct probe_target *targets = gc_malloc(sizeof(*targets) * l->len, true,
&gc);
struct oob_probe_result *results = gc_malloc(sizeof(*results) * l->len,
true, &gc);
- struct buffer probe = alloc_buf_gc(256, &gc);
- if (!oob_probe_build_packet(&probe, &client_sid))
+ /* Build the probe once and reuse the same bytes for every remote and for
+ * resends: the SERVER_PROBE payload is a single probe_parameter TLV,
wrapped
+ * (or sent in plaintext) like any other control packet. The client session
+ * id is prepended as the sender session id. */
+ struct buffer payload = alloc_buf_gc(64, &gc);
+ const struct oob_probe_parameter param = {
+ .timestamp = (uint64_t)now,
+ .flags = 0,
+ };
+ if (!oob_server_probe_write(&payload, ¶m))
{
- msg(D_LOW, "server-probe: could not build probe packet; using
configured order");
+ msg(D_LOW, "server-probe: could not build probe payload; using
configured order");
openvpn_close_socket(sd);
+ tls_auth_standalone_free(tas);
gc_free(&gc);
return;
}
- msg(D_LOW, "server-probe: probing %d remote(s) with a %d ms window",
l->len,
- OOB_PROBE_WINDOW_MS);
+ struct buffer probe = tls_wrap_oob_standalone(&tas->tls_wrap, tas,
&client_sid, &payload);
+ if (!BLEN(&probe))
+ {
+ msg(D_LOW, "server-probe: could not wrap probe packet; using
configured order");
+ openvpn_close_socket(sd);
+ tls_auth_standalone_free(tas);
+ gc_free(&gc);
+ return;
+ }
+
+ const char *wrap_name = (tas->tls_wrap.mode == TLS_WRAP_CRYPT) ?
"tls-crypt"
+ : (tas->tls_wrap.mode == TLS_WRAP_AUTH) ?
"tls-auth"
+ : "none
(plaintext)";
+ msg(D_LOW, "server-probe: probing %d remote(s) with a %d ms window,
control-channel wrapping: %s",
+ l->len, OOB_PROBE_WINDOW_MS, wrap_name);
/* Send a probe to each configured remote. */
int sent_count = 0;
@@ -420,7 +500,7 @@
if (sent_count > 0)
{
- oob_probe_collect(sd, &probe, &client_sid, targets, results, l->len);
+ oob_probe_collect(sd, &probe, &client_sid, &tas->tls_wrap, targets,
results, l->len);
}
openvpn_close_socket(sd);
@@ -466,5 +546,6 @@
msg(M_INFO, "server-probe: %d of %d remote(s) answered; connecting
best-first", responded,
l->len);
+ tls_auth_standalone_free(tas);
gc_free(&gc);
}
--
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1750?usp=email
To unsubscribe, or for help writing mail filters, visit
http://gerrit.openvpn.net/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I1f9d7b5a5ec19bf77c0c212795f583c5ba4c03ac
Gerrit-Change-Number: 1750
Gerrit-PatchSet: 1
Gerrit-Owner: stipa <[email protected]>
Gerrit-Reviewer: plaisthos <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: plaisthos <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel