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/+/1758?usp=email

to review the following change.


Change subject: oob: Unwrap tls-crypt-v2 server probes (P_CONTROL_OOB_WKC_V1)
......................................................................

oob: Unwrap tls-crypt-v2 server probes (P_CONTROL_OOB_WKC_V1)

A tls-crypt-v2 server learns the per-client key only from the wrapped
client key (WKc). An out-of-band probe belongs to no session, so the
client must carry the WKc with the probe for the server to unwrap it.

Add the receive/protocol side of this:

  - new opcode P_CONTROL_OOB_WKC_V1 (13): like P_CONTROL_OOB_V1 but with
    the WKc appended, mirroring CONTROL_HARD_RESET_CLIENT_V3/CONTROL_WKC_V1
  - tls_wrap_control() appends the WKc for opcode 13; read_control_auth()
    runs tls_crypt_v2_extract_client_key() for it, loading the per-client
    key before unwrapping the message
  - tls_pre_decrypt_lite() accepts opcode 13 and returns VERDICT_VALID_OOB_V1
  - tls_wrap_oob_standalone() takes the opcode; the server reply stays a
    plain P_CONTROL_OOB_V1 (the server already holds the key after unwrap)
  - test_pkt: round-trip test of the wrap/extract/unwrap path

The client still sends plain probes; sending P_CONTROL_OOB_WKC_V1 is added
in a following commit.

Change-Id: Idb8c0c660a98de8ec549b69f6692a7b41b092eaf
---
M src/openvpn/mudp.c
M src/openvpn/oob_client.c
M src/openvpn/ssl_pkt.c
M src/openvpn/ssl_pkt.h
M tests/unit_tests/openvpn/test_pkt.c
5 files changed, 157 insertions(+), 10 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/58/1758/1

diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c
index 0e9414d..7ce7ecc 100644
--- a/src/openvpn/mudp.c
+++ b/src/openvpn/mudp.c
@@ -106,7 +106,12 @@
     reset_packet_id_send(&state->tls_wrap_tmp.opt.packet_id.send);
     state->tls_wrap_tmp.opt.packet_id.rec.initialized = true;

-    struct buffer buf = tls_wrap_oob_standalone(&state->tls_wrap_tmp, tas, 
own_sid, &payload);
+    /* The reply carries no WKc: with tls-crypt-v2 the server has already
+     * recovered the per-client key from the request's WKc (it is loaded into
+     * state->tls_wrap_tmp), so the reply is wrapped with that key as a plain
+     * P_CONTROL_OOB_V1 message. */
+    struct buffer buf =
+        tls_wrap_oob_standalone(&state->tls_wrap_tmp, tas, own_sid, &payload, 
P_CONTROL_OOB_V1);
     send_standalone_reply(m, &buf, "Server Probe", "Server probe from client, 
sending probe reply",
                           sock);

diff --git a/src/openvpn/oob_client.c b/src/openvpn/oob_client.c
index 7391d1e..2b1b732 100644
--- a/src/openvpn/oob_client.c
+++ b/src/openvpn/oob_client.c
@@ -467,7 +467,8 @@
         return;
     }

-    struct buffer probe = tls_wrap_oob_standalone(&tas->tls_wrap, tas, 
&client_sid, &payload);
+    struct buffer probe =
+        tls_wrap_oob_standalone(&tas->tls_wrap, tas, &client_sid, &payload, 
P_CONTROL_OOB_V1);
     if (!BLEN(&probe))
     {
         msg(D_LOW, "server-probe: could not wrap probe packet; using 
configured order");
diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c
index 86e42c1..b68c67a 100644
--- a/src/openvpn/ssl_pkt.c
+++ b/src/openvpn/ssl_pkt.c
@@ -144,7 +144,8 @@
         }

         if ((header >> P_OPCODE_SHIFT) == P_CONTROL_HARD_RESET_CLIENT_V3
-            || (header >> P_OPCODE_SHIFT) == P_CONTROL_WKC_V1)
+            || (header >> P_OPCODE_SHIFT) == P_CONTROL_WKC_V1
+            || (header >> P_OPCODE_SHIFT) == P_CONTROL_OOB_WKC_V1)
         {
             if (!buf_copy(&ctx->work, ctx->tls_crypt_v2_wkc))
             {
@@ -199,7 +200,8 @@
     bool ret = false;

     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
-    if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 || opcode == 
P_CONTROL_WKC_V1)
+    if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 || opcode == P_CONTROL_WKC_V1
+         || opcode == P_CONTROL_OOB_WKC_V1)
         && !tls_crypt_v2_extract_client_key(buf, ctx, opt, initial_packet))
     {
         msg(D_TLS_ERRORS, "TLS Error: can not extract tls-crypt-v2 client key 
from %s",
@@ -316,7 +318,7 @@
     /* 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_WKC_V1 && op != P_ACK_V1
-        && op != P_CONTROL_OOB_V1)
+        && op != P_CONTROL_OOB_V1 && op != P_CONTROL_OOB_WKC_V1)
     {
         /*
          * This can occur due to bogus data or DoS packets.
@@ -391,8 +393,12 @@
     {
         return VERDICT_VALID_WKC_V1;
     }
-    else if (op == P_CONTROL_OOB_V1)
+    else if (op == P_CONTROL_OOB_V1 || op == P_CONTROL_OOB_WKC_V1)
     {
+        /* A tls-crypt-v2 probe (P_CONTROL_OOB_WKC_V1) has, at this point,
+         * already had its WKc unwrapped by read_control_auth() above, so the
+         * per-client key is loaded into state->tls_wrap_tmp and the rest of 
the
+         * handling is identical to a plain OOB probe. */
         return VERDICT_VALID_OOB_V1;
     }
     else
@@ -451,8 +457,10 @@

 struct buffer
 tls_wrap_oob_standalone(struct tls_wrap_ctx *ctx, struct tls_auth_standalone 
*tas,
-                        struct session_id *own_sid, const struct buffer 
*payload)
+                        struct session_id *own_sid, const struct buffer 
*payload, int opcode)
 {
+    ASSERT(opcode == P_CONTROL_OOB_V1 || opcode == P_CONTROL_OOB_WKC_V1);
+
     /* Copy buffer here to point at the same data but allow tls_wrap_control
      * to potentially change buf to point to another buffer without
      * modifying the buffer in tas */
@@ -463,10 +471,11 @@
      * or ACK fields. */
     ASSERT(buf_copy(&buf, payload));

-    uint8_t header = (uint8_t)(P_CONTROL_OOB_V1 << P_OPCODE_SHIFT);
+    uint8_t header = (uint8_t)(opcode << P_OPCODE_SHIFT);

     /* Add tls-auth/tls-crypt wrapping, this might replace buf with
-     * ctx->work */
+     * ctx->work. For P_CONTROL_OOB_WKC_V1 the wrapped client key is appended
+     * here too (tls-crypt-v2). */
     tls_wrap_control(ctx, header, &buf, own_sid);

     return buf;
diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h
index 6f05a19..4efebac 100644
--- a/src/openvpn/ssl_pkt.h
+++ b/src/openvpn/ssl_pkt.h
@@ -71,6 +71,14 @@
  * as a control packet. */
 #define P_CONTROL_OOB_V1 12

+/* Variant of P_CONTROL_OOB_V1 with an appended wrapped client key (WKc), like
+ * P_CONTROL_HARD_RESET_CLIENT_V3 / P_CONTROL_WKC_V1. Used when tls-crypt-v2 is
+ * configured: an out-of-band message belongs to no established session, so the
+ * client must carry the WKc for the server to recover the per-client key and
+ * unwrap the message. See doc/tls-crypt-v2.txt and the openvpn-rfc wire
+ * protocol (CONTROL_OOB_WKC_V1). */
+#define P_CONTROL_OOB_WKC_V1 13
+
 /* define the range of legal opcodes
  * Since we do no longer support key-method 1 we consider
  * the v1 op codes invalid */
@@ -250,10 +258,14 @@
  * @param tas       standalone auth context providing the work buffer
  * @param own_sid   session id to use as our session id in the header
  * @param payload   the OOB message payload (TLV stream) to wrap
+ * @param opcode    the OOB opcode to use: P_CONTROL_OOB_V1, or
+ *                  P_CONTROL_OOB_WKC_V1 to append the tls-crypt-v2 wrapped
+ *                  client key (the context must then carry it).
  * @return          the wrapped packet buffer, ready to send
  */
 struct buffer tls_wrap_oob_standalone(struct tls_wrap_ctx *ctx, struct 
tls_auth_standalone *tas,
-                                      struct session_id *own_sid, const struct 
buffer *payload);
+                                      struct session_id *own_sid, const struct 
buffer *payload,
+                                      int opcode);


 /**
diff --git a/tests/unit_tests/openvpn/test_pkt.c 
b/tests/unit_tests/openvpn/test_pkt.c
index cad2ce0..e64a698 100644
--- a/tests/unit_tests/openvpn/test_pkt.c
+++ b/tests/unit_tests/openvpn/test_pkt.c
@@ -712,6 +712,125 @@
     free_tas(&tas_server);
 }

+/* A matching tls-crypt-v2 server/client key pair (the client key's WKc was
+ * wrapped with this server key), reused from the tls-crypt-v2 unit tests. */
+static const char *oob_v2_server_key =
+    "-----BEGIN OpenVPN tls-crypt-v2 server key-----\n"
+    "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n"
+    "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n"
+    "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=\n"
+    "-----END OpenVPN tls-crypt-v2 server key-----\n";
+
+static const char *oob_v2_client_key =
+    "-----BEGIN OpenVPN tls-crypt-v2 client key-----\n"
+    "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n"
+    "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n"
+    "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n"
+    "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n"
+    "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n"
+    "8PHy8/T19vf4+fr7/P3+/xd9pcB0qUYZsWvkrLcfGmzPJPM8a7r0mEWdXwbDadSV\n"
+    "LHg5bv2TwlmPR3HgaMr8o9LTh9hxUTkrH3S0PfKRNwcso86ua/dBFTyXsM9tg4aw\n"
+    "3dS6ogH9AkaT+kRRDgNcKWkQCbwmJK2JlfkXHBwbAtmn78AkNuho6QCFqCdqGab3\n"
+    "zh2vheFqGMPdGpukbFrT3rcO3VLxUeG+RdzXiMTCpJSovFBP1lDkYwYJPnz6daEh\n"
+    "j0TzJ3BVru9W3CpotdNt7u09knxAfpCxjtrP3semsDew/gTBtcfQ/OoTFyFHnN5k\n"
+    "RZ+q17SC4nba3Pp8/Fs0+hSbv2tJozoD8SElFq7SIWJsciTYh8q8f5yQxjdt4Wxu\n"
+    "/Z5wtPCAZ0tOzj4ItTI77fBOYRTfEayzHgEr\n"
+    "-----END OpenVPN tls-crypt-v2 client key-----\n";
+
+/* End-to-end check of the tls-crypt-v2 out-of-band probe: the client wraps an
+ * OOB payload as a P_CONTROL_OOB_WKC_V1 message (the payload encrypted with 
the
+ * per-client key Kc, with the wrapped client key WKc appended), and the 
server's
+ * stateless first-packet path (tls_pre_decrypt_lite) recovers Kc from the WKc,
+ * decrypts the payload, and returns VERDICT_VALID_OOB_V1. */
+static void
+test_oob_wkc_v1_roundtrip(void **ut_state)
+{
+    now = 0x5f000000;
+
+    struct frame frame = { .buf = { .headroom = 200, .payload_size = 1400 }, 0 
};
+
+    /* ---- Client: wrap an OOB probe as P_CONTROL_OOB_WKC_V1 (WKc appended) 
---- */
+    struct tls_auth_standalone tas_client = { 0 };
+    tas_client.frame = frame;
+    tas_client.tls_wrap.mode = TLS_WRAP_CRYPT;
+    tas_client.tls_wrap.opt.flags |= (CO_IGNORE_PACKET_ID | 
CO_PACKET_ID_LONG_FORM);
+    packet_id_init(&tas_client.tls_wrap.opt.packet_id, 0, 0, "UT-CLIENT", 0);
+    tas_client.workbuf = alloc_buf(1600);
+    tas_client.tls_wrap.work = alloc_buf(1600);
+
+    /* Load Kc and obtain the WKc, exactly as a tls-crypt-v2 client does. */
+    struct buffer wkc = { 0 };
+    tls_crypt_v2_init_client_key(&tas_client.tls_wrap.opt.key_ctx_bi,
+                                 &tas_client.tls_wrap.original_wrap_keydata, 
&wkc, oob_v2_client_key,
+                                 true);
+    tas_client.tls_wrap.tls_crypt_v2_wkc = &wkc;
+
+    const uint8_t payload_bytes[] = { 0x01, 0x00, 0x02, 0x00, 0x0c, 0xde,
+                                      0xad, 0xbe, 0xef, 0x00, 0x11, 0x22 };
+    struct buffer payload = alloc_buf(64);
+    buf_write(&payload, payload_bytes, sizeof(payload_bytes));
+
+    struct session_id client_sid = { { 1, 2, 3, 4, 5, 6, 7, 8 } };
+    reset_packet_id_send(&tas_client.tls_wrap.opt.packet_id.send);
+
+    struct buffer probe = tls_wrap_oob_standalone(&tas_client.tls_wrap, 
&tas_client, &client_sid,
+                                                  &payload, 
P_CONTROL_OOB_WKC_V1);
+    assert_true(BLEN(&probe) > 0);
+    assert_int_equal((BPTR(&probe))[0] >> P_OPCODE_SHIFT, 
P_CONTROL_OOB_WKC_V1);
+
+    /* ---- Server: stateless path recovers Kc from the WKc and decrypts ---- 
*/
+    struct tls_auth_standalone tas_server = { 0 };
+    tas_server.frame = frame;
+    /* No static wrapping, only a tls-crypt-v2 server key (as a v2 server 
runs). */
+    tas_server.tls_wrap.mode = TLS_WRAP_NONE;
+    tas_server.tls_wrap.opt.flags |= (CO_IGNORE_PACKET_ID | 
CO_PACKET_ID_LONG_FORM);
+    packet_id_init(&tas_server.tls_wrap.opt.packet_id, 0, 0, "UT-SERVER", 0);
+    tls_crypt_v2_init_server_key(&tas_server.tls_wrap.tls_crypt_v2_server_key, 
true, oob_v2_server_key,
+                                 true);
+    tas_server.workbuf = alloc_buf(1600);
+
+    struct link_socket_actual from = { 0 };
+    struct buffer srv = alloc_buf(1600);
+    buf_write(&srv, BPTR(&probe), BLEN(&probe));
+
+    struct tls_pre_decrypt_state state = { 0 };
+    enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas_server, 
&state, &from, &srv);
+    assert_int_equal(verdict, VERDICT_VALID_OOB_V1);
+
+    /* The decrypted payload must equal what the client sent, and the client's
+     * session id must be recovered. */
+    assert_int_equal(BLEN(&state.newbuf), (int)sizeof(payload_bytes));
+    assert_memory_equal(BPTR(&state.newbuf), payload_bytes, 
sizeof(payload_bytes));
+    assert_memory_equal(state.peer_session_id.id, client_sid.id, SID_SIZE);
+    free_tls_pre_decrypt_state(&state);
+
+    /* Flipping any single byte (header, session id, ciphertext or WKc) must 
make
+     * the probe fail to validate. */
+    for (size_t i = 0; i < (size_t)BLEN(&probe); i++)
+    {
+        struct tls_pre_decrypt_state tstate = { 0 };
+        buf_reset_len(&srv);
+        buf_write(&srv, BPTR(&probe), BLEN(&probe));
+        (BPTR(&srv))[i] ^= 0xff;
+        enum first_packet_verdict v = tls_pre_decrypt_lite(&tas_server, 
&tstate, &from, &srv);
+        assert_int_equal(v, VERDICT_INVALID);
+        free_tls_pre_decrypt_state(&tstate);
+    }
+
+    free_key_ctx_bi(&tas_client.tls_wrap.opt.key_ctx_bi);
+    packet_id_free(&tas_client.tls_wrap.opt.packet_id);
+    free_buf(&wkc);
+    free_buf(&payload);
+    free_buf(&tas_client.workbuf);
+    free_buf(&tas_client.tls_wrap.work);
+
+    free_key_ctx(&tas_server.tls_wrap.tls_crypt_v2_server_key);
+    packet_id_free(&tas_server.tls_wrap.opt.packet_id);
+    free_buf(&tas_server.workbuf);
+
+    free_buf(&srv);
+}
+
 static void
 test_extract_control_message(void **ut_state)
 {
@@ -762,6 +881,7 @@
         cmocka_unit_test(test_verify_hmac_none_out_of_range_ack),
         cmocka_unit_test(test_generate_reset_packet_plain),
         cmocka_unit_test(test_generate_reset_packet_tls_auth),
+        cmocka_unit_test(test_oob_wkc_v1_roundtrip),
         cmocka_unit_test(test_extract_control_message)
     };


--
To view, visit http://gerrit.openvpn.net/c/openvpn/+/1758?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: Idb8c0c660a98de8ec549b69f6692a7b41b092eaf
Gerrit-Change-Number: 1758
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

Reply via email to