From f71ae05a4d22d52780fc7cfc7e60710b74fd3dd7 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Mon, 31 Oct 2022 13:57:40 +0000 Subject: [PATCH] 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 Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/19703) --- Configure | 3 +- include/internal/quic_dummy_handshake.h | 121 +++++++++ ssl/quic/build.info | 3 + ssl/quic/quic_dummy_handshake.c | 328 ++++++++++++++++++++++++ 4 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 include/internal/quic_dummy_handshake.h create mode 100644 ssl/quic/quic_dummy_handshake.c diff --git a/Configure b/Configure index d5233103fc..e723bf2123 100755 --- a/Configure +++ b/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 diff --git a/include/internal/quic_dummy_handshake.h b/include/internal/quic_dummy_handshake.h new file mode 100644 index 0000000000..3a424a6db6 --- /dev/null +++ b/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 +# 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 diff --git a/ssl/quic/build.info b/ssl/quic/build.info index 97654b1021..1380112197 100644 --- a/ssl/quic/build.info +++ b/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 diff --git a/ssl/quic/quic_dummy_handshake.c b/ssl/quic/quic_dummy_handshake.c new file mode 100644 index 0000000000..b14979d223 --- /dev/null +++ b/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 +#include +#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; +}