Browse Source

QUIC: Dummy Handshake Layer for Prototyping

This disables -Wtype-limits /
-Wtautological-constant-out-of-range-compare. Since it generates
warnings for valid and reasonable code, IMO this actually encourages
people to write worse code.

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19703)
Hugo Landau 1 year ago
parent
commit
f71ae05a4d
4 changed files with 454 additions and 1 deletions
  1. 2 1
      Configure
  2. 121 0
      include/internal/quic_dummy_handshake.h
  3. 3 0
      ssl/quic/build.info
  4. 328 0
      ssl/quic/quic_dummy_handshake.c

+ 2 - 1
Configure

@@ -167,7 +167,8 @@ my @gcc_devteam_warn = qw(
     -Wsign-compare
     -Wshadow
     -Wformat
-    -Wtype-limits
+    -Wno-type-limits
+    -Wno-tautological-constant-out-of-range-compare
     -Wundef
     -Werror
     -Wmissing-prototypes

+ 121 - 0
include/internal/quic_dummy_handshake.h

@@ -0,0 +1,121 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OSSL_QUIC_DUMMY_HANDSHAKE_H
+# define OSSL_QUIC_DUMMY_HANDSHAKE_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_stream.h"
+
+/*
+ * QUIC Dummy Handshake Module
+ * ===========================
+ *
+ * This implements a fake "handshake layer" for QUIC to be used for testing
+ * purposes until the real handshake layer is ready.
+ *
+ * Each message is of the following form, which reuses the TLS 1.3 framing:
+ *
+ *   1  ui  Type
+ *   3  ui  Length
+ *   ...    Data
+ *
+ * The following message types are implemented, which use values from the TLS
+ * HandshakeType registry. Most of them have no body data, except for messages
+ * which transport QUIC Transport Parameters.
+ *
+ *   0x01   Psuedo-ClientHello
+ *              (QUIC Transport Parameters)
+ *   0x02   Pseudo-ServerHello
+ *              (no data)
+ *   0x08   Pseudo-EncryptedExtensions
+ *              (QUIC Transport Parameters)
+ *   0x0B   Pseudo-Certificate
+ *              (no data)
+ *   0x0F   Pseudo-CertificateVerify
+ *              (no data)
+ *   0x14   Pseudo-Finished
+ *              (no data)
+ *
+ */
+typedef struct quic_dhs_st QUIC_DHS;
+
+typedef struct quic_dhs_args_st {
+    /*
+     * Called to send data on the crypto stream. We use a callback rather than
+     * passing the crypto stream QUIC_SSTREAM directly because this lets the CSM
+     * dynamically select the correct outgoing crypto stream based on the
+     * current EL.
+     */
+    int (*crypto_send_cb)(const unsigned char *buf, size_t buf_len,
+                          size_t *consumed, void *arg);
+    void *crypto_send_cb_arg;
+    int (*crypto_recv_cb)(unsigned char *buf, size_t buf_len,
+                          size_t *bytes_read, void *arg);
+    void *crypto_recv_cb_arg;
+
+    /* Called when a traffic secret is available for a given encryption level. */
+    int (*yield_secret_cb)(uint32_t enc_level, int direction /* 0=RX, 1=TX */,
+                           uint32_t suite_id, EVP_MD *md,
+                           const unsigned char *secret, size_t secret_len,
+                           void *arg);
+    void *yield_secret_cb_arg;
+
+    /*
+     * Called when we receive transport parameters from the peer.
+     *
+     * Note: These parameters are not authenticated until the handshake is
+     * marked as completed.
+     */
+    int (*got_transport_params_cb)(const unsigned char *params,
+                                   size_t params_len,
+                                   void *arg);
+    void *got_transport_params_cb_arg;
+
+    /*
+     * Called when the handshake has been completed as far as the handshake
+     * protocol is concerned, meaning that the connection has been
+     * authenticated.
+     */
+    int (*handshake_complete_cb)(void *arg);
+    void *handshake_complete_cb_arg;
+
+    /*
+     * Called when something has gone wrong with the connection as far as the
+     * handshake layer is concerned, meaning that it should be immediately torn
+     * down. Note that this may happen at any time, including after a connection
+     * has been fully established.
+     */
+    int (*alert_cb)(void *arg, unsigned char alert_code);
+    void *alert_cb_arg;
+
+    /*
+     * Transport parameters which client should send. Buffer lifetime must
+     * exceed the lifetime of the DHS.
+     */
+    const unsigned char *transport_params;
+    size_t transport_params_len;
+} QUIC_DHS_ARGS;
+
+QUIC_DHS *ossl_quic_dhs_new(const QUIC_DHS_ARGS *args);
+
+void ossl_quic_dhs_free(QUIC_DHS *dhs);
+
+/*
+ * Advance the state machine. The DHS considers the receive stream and produces
+ * output on the send stream. Note that after a connection is established this
+ * is unlikely to ever produce any more output, but the handshake layer
+ * nonetheless reserves the right to and it should continue being called
+ * regularly. (When a real handshake layer is used, TLS 1.3 might e.g. produce a
+ * new session ticket; or it might decide to spontaneously produce an alert,
+ * however unlikely.)
+ */
+int ossl_quic_dhs_tick(QUIC_DHS *dhs);
+
+#endif

+ 3 - 0
ssl/quic/build.info

@@ -7,3 +7,6 @@ SOURCE[$LIBSSL]=quic_record_rx_wrap.c quic_rx_depack.c
 SOURCE[$LIBSSL]=quic_fc.c uint_set.c quic_sf_list.c quic_rstream.c quic_sstream.c
 SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c quic_txp.c
 SOURCE[$LIBSSL]=quic_stream_map.c
+SOURCE[$LIBSSL]=quic_sf_list.c quic_rstream.c quic_sstream.c
+SOURCE[$LIBSSL]=quic_sf_list.c quic_rstream.c quic_sstream.c
+SOURCE[$LIBSSL]=quic_dummy_handshake.c

+ 328 - 0
ssl/quic/quic_dummy_handshake.c

@@ -0,0 +1,328 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/macros.h>
+#include <openssl/objects.h>
+#include "internal/quic_dummy_handshake.h"
+
+#define QUIC_DHS_MSG_TYPE_CH            0x01
+#define QUIC_DHS_MSG_TYPE_SH            0x02
+#define QUIC_DHS_MSG_TYPE_EE            0x08
+#define QUIC_DHS_MSG_TYPE_CERT          0x0B
+#define QUIC_DHS_MSG_TYPE_CERT_VERIFY   0x0F
+#define QUIC_DHS_MSG_TYPE_FINISHED      0x14
+
+#define QUIC_DHS_STATE_INITIAL              0
+#define QUIC_DHS_STATE_SENT_CH              1
+#define QUIC_DHS_STATE_RECEIVED_SH          2
+#define QUIC_DHS_STATE_RECEIVED_EE_HDR      8
+#define QUIC_DHS_STATE_RECEIVED_EE          3
+#define QUIC_DHS_STATE_RECEIVED_CERT        4
+#define QUIC_DHS_STATE_RECEIVED_CERT_VERIFY 5
+#define QUIC_DHS_STATE_RECEIVED_FINISHED    6
+#define QUIC_DHS_STATE_SENT_FINISHED        7
+
+#define QUIC_DHS_STATE_ERROR        0xFF
+
+struct quic_dhs_st {
+    QUIC_DHS_ARGS   args;
+    unsigned char   state;
+    unsigned char   *server_transport_params;
+    size_t          server_transport_params_len;
+    unsigned char   rx_hdr[4];
+    size_t          rx_hdr_bytes_read;
+    size_t          rx_ee_bytes_read;
+};
+
+QUIC_DHS *ossl_quic_dhs_new(const QUIC_DHS_ARGS *args)
+{
+    QUIC_DHS *dhs;
+
+    if (args->crypto_send_cb == NULL
+        || args->crypto_recv_cb == NULL)
+        return NULL;
+
+    dhs = OPENSSL_zalloc(sizeof(*dhs));
+    if (dhs == NULL)
+        return NULL;
+
+    dhs->args   = *args;
+    dhs->state  = QUIC_DHS_STATE_INITIAL;
+    return dhs;
+}
+
+void ossl_quic_dhs_free(QUIC_DHS *dhs)
+{
+    if (dhs == NULL)
+        return;
+
+    OPENSSL_free(dhs->server_transport_params);
+    OPENSSL_free(dhs);
+}
+
+static int dhs_send(QUIC_DHS *dhs, unsigned char type,
+                    const void *buf, size_t buf_len)
+{
+    size_t consumed = 0;
+    uint32_t len;
+    unsigned char hdr[4];
+
+    len = buf_len;
+    hdr[0] = type;
+    hdr[1] = (len >> 16) & 0xFF;
+    hdr[2] = (len >>  8) & 0xFF;
+    hdr[3] = (len      ) & 0xFF;
+
+    if (!dhs->args.crypto_send_cb(hdr, sizeof(hdr), &consumed,
+                                  dhs->args.crypto_send_cb_arg)
+        || consumed < sizeof(hdr)
+        || (buf_len > 0 && (!dhs->args.crypto_send_cb(buf, buf_len, &consumed,
+                                                      dhs->args.crypto_send_cb_arg)
+                            || consumed < buf_len)))
+        /*
+         * We do not handle a full buffer here properly but the DHS produces so
+         * little data this should not matter. By the time we want to fix this
+         * the real handshake layer will be ready.
+         */
+        return 0;
+
+    return 1;
+}
+
+static int dhs_recv_sof(QUIC_DHS *dhs, uint32_t *type, size_t *frame_len)
+{
+    size_t bytes_read = 0;
+    uint32_t l;
+
+    if (!dhs->args.crypto_recv_cb(dhs->rx_hdr + dhs->rx_hdr_bytes_read,
+                                  sizeof(dhs->rx_hdr) - dhs->rx_hdr_bytes_read,
+                                  &bytes_read,
+                                  dhs->args.crypto_recv_cb_arg))
+        return 0;
+
+    dhs->rx_hdr_bytes_read += bytes_read;
+    if (dhs->rx_hdr_bytes_read < sizeof(dhs->rx_hdr)) {
+        /* Not got entire header yet. */
+        *type       = UINT32_MAX;
+        *frame_len  = 0;
+        return 2;
+    }
+
+    l = (((uint32_t)dhs->rx_hdr[1]) << 16)
+      | (((uint32_t)dhs->rx_hdr[2]) <<  8)
+      |   (uint32_t)dhs->rx_hdr[3];
+
+    if (l > SIZE_MAX)
+        return 0;
+
+    *type       = dhs->rx_hdr[0];
+    *frame_len  = (size_t)l;
+
+    dhs->rx_hdr_bytes_read = 0;
+    return 1;
+}
+
+static int dhs_recv_body(QUIC_DHS *dhs, unsigned char *buf, size_t buf_len,
+                         size_t *bytes_read)
+{
+    if (!dhs->args.crypto_recv_cb(buf, buf_len, bytes_read,
+                                  dhs->args.crypto_recv_cb_arg))
+        return 0;
+
+    if (*bytes_read == 0)
+        return 2;
+
+    return 1;
+}
+
+static const unsigned char default_handshake_read[32] = {42, 2};
+static const unsigned char default_handshake_write[32] = {42, 1};
+static const unsigned char default_1rtt_read[32] = {43, 2};
+static const unsigned char default_1rtt_write[32] = {43, 1};
+
+int ossl_quic_dhs_tick(QUIC_DHS *dhs)
+{
+    int ret;
+    uint32_t type;
+    size_t frame_len, bytes_read = 0;
+
+    for (;;) {
+        switch (dhs->state) {
+            case QUIC_DHS_STATE_INITIAL:
+                /* We need to send a CH */
+                if (!dhs_send(dhs, QUIC_DHS_MSG_TYPE_CH,
+                              dhs->args.transport_params,
+                              dhs->args.transport_params_len))
+                    return 0;
+
+                dhs->state = QUIC_DHS_STATE_SENT_CH;
+                break;
+
+            case QUIC_DHS_STATE_SENT_CH:
+                ret = dhs_recv_sof(dhs, &type, &frame_len);
+                if (ret == 1) {
+                    if (type == QUIC_DHS_MSG_TYPE_SH && frame_len == 0) {
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_SH;
+
+                        if (!dhs->args.yield_secret_cb(QUIC_ENC_LEVEL_HANDSHAKE,
+                                                       /*TX=*/0,
+                                                       QRL_SUITE_AES128GCM,
+                                                       NULL,
+                                                       default_handshake_read,
+                                                       sizeof(default_handshake_read),
+                                                       dhs->args.yield_secret_cb_arg))
+                            return 0;
+
+                        if (!dhs->args.yield_secret_cb(QUIC_ENC_LEVEL_HANDSHAKE,
+                                                       /*TX=*/1,
+                                                       QRL_SUITE_AES128GCM,
+                                                       NULL,
+                                                       default_handshake_write,
+                                                       sizeof(default_handshake_write),
+                                                       dhs->args.yield_secret_cb_arg))
+                            return 0;
+
+                    } else {
+                        return 0; /* error state, unexpected type */
+                    }
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_SH:
+                ret = dhs_recv_sof(dhs, &type, &frame_len);
+                if (ret == 1) {
+                    if (type == QUIC_DHS_MSG_TYPE_EE) {
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_EE_HDR;
+                        dhs->rx_ee_bytes_read               = 0;
+                        dhs->server_transport_params_len    = frame_len;
+                        dhs->server_transport_params
+                            = OPENSSL_malloc(dhs->server_transport_params_len);
+                        if (dhs->server_transport_params == NULL)
+                            return 0;
+                    } else {
+                        return 0; /* error state, unexpected type */
+                    }
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_EE_HDR:
+                ret = dhs_recv_body(dhs, dhs->server_transport_params + dhs->rx_ee_bytes_read,
+                                    dhs->server_transport_params_len - dhs->rx_ee_bytes_read,
+                                    &bytes_read);
+                if (ret == 1) {
+                    dhs->rx_ee_bytes_read += bytes_read;
+                    if (bytes_read == dhs->server_transport_params_len) {
+                        if (!dhs->args.got_transport_params_cb(dhs->server_transport_params,
+                                                               dhs->server_transport_params_len,
+                                                               dhs->args.got_transport_params_cb_arg))
+                            return 0;
+
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_EE;
+                    }
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_EE:
+                /* Expect Cert */
+                ret = dhs_recv_sof(dhs, &type, &frame_len);
+                if (ret == 1) {
+                    if (type == QUIC_DHS_MSG_TYPE_CERT && frame_len == 0)
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_CERT;
+                    else
+                        return 0; /* error state, unexpected type */
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_CERT:
+                /* Expect CertVerify */
+                ret = dhs_recv_sof(dhs, &type, &frame_len);
+                if (ret == 1) {
+                    if (type == QUIC_DHS_MSG_TYPE_CERT_VERIFY && frame_len == 0)
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_CERT_VERIFY;
+                    else
+                        return 0; /* error state, unexpected type */
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_CERT_VERIFY:
+                /* Expect Finished */
+                ret = dhs_recv_sof(dhs, &type, &frame_len);
+                if (ret == 1) {
+                    if (type == QUIC_DHS_MSG_TYPE_FINISHED && frame_len == 0)
+                        dhs->state = QUIC_DHS_STATE_RECEIVED_FINISHED;
+                    else
+                        return 0; /* error state, unexpected type */
+                } else if (ret == 2) {
+                    return 1; /* no more data yet, not an error */
+                } else {
+                    return 0;
+                }
+                break;
+
+            case QUIC_DHS_STATE_RECEIVED_FINISHED:
+                /* Send Finished */
+                if (!dhs_send(dhs, QUIC_DHS_MSG_TYPE_FINISHED, NULL, 0))
+                    return 0;
+
+                dhs->state = QUIC_DHS_STATE_SENT_FINISHED;
+
+                if (!dhs->args.yield_secret_cb(QUIC_ENC_LEVEL_1RTT,
+                                               /*TX=*/0,
+                                               QRL_SUITE_AES128GCM,
+                                               NULL,
+                                               default_1rtt_read,
+                                               sizeof(default_1rtt_read),
+                                               dhs->args.yield_secret_cb_arg))
+                    return 0;
+
+                if (!dhs->args.yield_secret_cb(QUIC_ENC_LEVEL_1RTT,
+                                               /*TX=*/1,
+                                               QRL_SUITE_AES128GCM,
+                                               NULL,
+                                               default_1rtt_write,
+                                               sizeof(default_1rtt_write),
+                                               dhs->args.yield_secret_cb_arg))
+                    return 0;
+
+                if (!dhs->args.handshake_complete_cb(dhs->args.handshake_complete_cb_arg))
+                    return 0;
+
+                break;
+
+            case QUIC_DHS_STATE_SENT_FINISHED:
+                /* Nothing to do, handshake complete. */
+                return 1;
+
+            default:
+                return 0; /* error state */
+        }
+    }
+
+    return 1;
+}