diff options
| -rw-r--r-- | include/internal/quic_channel.h | 2 | ||||
| -rw-r--r-- | include/internal/quic_ssl.h | 8 | ||||
| -rw-r--r-- | ssl/quic/quic_channel.c | 63 | ||||
| -rw-r--r-- | ssl/quic/quic_channel_local.h | 2 | ||||
| -rw-r--r-- | ssl/quic/quic_impl.c | 47 | ||||
| -rw-r--r-- | ssl/ssl_lib.c | 38 | ||||
| -rw-r--r-- | ssl/ssl_sess.c | 6 | ||||
| -rw-r--r-- | test/build.info | 6 | ||||
| -rw-r--r-- | test/helpers/quictestlib.c | 312 | ||||
| -rw-r--r-- | test/helpers/quictestlib.h | 4 | ||||
| -rw-r--r-- | test/quic_0rtt_test.c | 475 | ||||
| -rw-r--r-- | test/recipes/70-test_quic_0rtt.t | 27 |
12 files changed, 977 insertions, 13 deletions
diff --git a/include/internal/quic_channel.h b/include/internal/quic_channel.h index 147642d0fb5..7498a3b48d8 100644 --- a/include/internal/quic_channel.h +++ b/include/internal/quic_channel.h @@ -545,6 +545,8 @@ int ossl_quic_bind_channel(QUIC_CHANNEL *ch, const BIO_ADDR *peer, void ossl_quic_channel_set_tcause(QUIC_CHANNEL *ch, uint64_t app_error_code, const char *app_reason); + +void ossl_quic_update_parms_from_session(QUIC_CHANNEL *ch); #endif #endif diff --git a/include/internal/quic_ssl.h b/include/internal/quic_ssl.h index 88168d169c2..f5a63ff180d 100644 --- a/include/internal/quic_ssl.h +++ b/include/internal/quic_ssl.h @@ -181,6 +181,14 @@ int ossl_quic_conn_poll_events(SSL *ssl, uint64_t events, int do_tick, int ossl_quic_get_notifier_fd(SSL *ssl); void ossl_quic_enter_blocking_section(SSL *ssl, QUIC_REACTOR_WAIT_CTX *wctx); void ossl_quic_leave_blocking_section(SSL *ssl, QUIC_REACTOR_WAIT_CTX *wctx); +int ossl_quic_read_early_data(SSL *s, void *buf, size_t num, size_t *readbytes); +int ossl_quic_write_early_data(SSL *s, const void *buf, size_t num, size_t *written); +int ossl_quic_get_early_data_status(const SSL *s); +int ossl_quic_set_max_early_data(SSL *ssl, uint32_t max_early_data); +uint32_t ossl_quic_get_max_early_data(const SSL *ssl); +int ossl_quic_set_recv_max_early_data(SSL *ssl, uint32_t recv_max_early_data); +uint32_t ossl_quic_get_recv_max_early_data(const SSL *ssl); +void ossl_quic_update_params(SSL *s); #endif diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 3ce445c1a64..87eca9da0c4 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -434,7 +434,7 @@ void ossl_quic_channel_bind_qrx(QUIC_CHANNEL *tserver_ch, OSSL_QRX *qrx) QUIC_CHANNEL *ossl_quic_channel_alloc(const QUIC_CHANNEL_ARGS *args) { - QUIC_CHANNEL *ch = NULL; + QUIC_CHANNEL *ch; if ((ch = OPENSSL_zalloc(sizeof(*ch))) == NULL) return NULL; @@ -450,10 +450,8 @@ QUIC_CHANNEL *ossl_quic_channel_alloc(const QUIC_CHANNEL_ARGS *args) ch->use_qlog = args->use_qlog; if (ch->use_qlog && args->qlog_title != NULL) { - if ((ch->qlog_title = OPENSSL_strdup(args->qlog_title)) == NULL) { - OPENSSL_free(ch); - return NULL; - } + if ((ch->qlog_title = OPENSSL_strdup(args->qlog_title)) == NULL) + goto err; } #endif @@ -473,10 +471,18 @@ QUIC_CHANNEL *ossl_quic_channel_alloc(const QUIC_CHANNEL_ARGS *args) if (!ossl_quic_rxfc_init(&ch->conn_rxfc, NULL, ch->tx_init_max_data, DEFAULT_CONN_RXFC_MAX_WND_MUL * ch->tx_init_max_data, - get_time, ch)) - return NULL; + get_time, ch)) { + goto err; + } return ch; + +err: +#ifndef OPENSSL_NO_QLOG + OPENSSL_free(ch->qlog_title); +#endif + OPENSSL_free(ch); + return NULL; } void ossl_quic_channel_free(QUIC_CHANNEL *ch) @@ -1614,7 +1620,7 @@ static int ch_on_transport_params(const unsigned char *params, goto malformed; } - assert(ch->max_local_streams_bidi == 0); + assert(ch->resumed || ch->max_local_streams_bidi == 0); ch->max_local_streams_bidi = v; ch->rx_init_max_streams_bidi = v; got_initial_max_streams_bidi = 1; @@ -1633,7 +1639,7 @@ static int ch_on_transport_params(const unsigned char *params, goto malformed; } - assert(ch->max_local_streams_uni == 0); + assert(ch->resumed || ch->max_local_streams_uni == 0); ch->max_local_streams_uni = v; ch->rx_init_max_streams_uni = v; got_initial_max_streams_uni = 1; @@ -4343,3 +4349,42 @@ uint64_t ossl_quic_channel_get_active_conn_id_limit_peer_request(const QUIC_CHAN { return ch->rx_active_conn_id_limit; } + +/* + * The function updates QUIC protocol parameters from session. + * It complements code implemented ch_on_transport_params) where + * protocol parameters are saved to session. + * + * Function here is called on behalf of SSL_set_session() which + * populates QUIC protocol parameters from session. + * + * We will need to revisit the code which handles session and + * parameters. Server may want to override parameters from session because the + * session might be resumed on different network path. So we may want to + * re-think which parameters are kept along the ssession. + */ +void ossl_quic_update_parms_from_session(QUIC_CHANNEL *ch) +{ + SSL_SESSION *session; + + if (ch == NULL || ch->tls == NULL) + return; + + session = SSL_CONNECTION_FROM_SSL(ch->tls)->session; + if (session == NULL) + return; + + ch->tx_init_max_stream_data_bidi_local = session->quic_params.init_max_stream_data_bidi_local; + ch->tx_init_max_stream_data_bidi_remote = session->quic_params.init_max_stream_data_bidi_remote; + ch->tx_init_max_stream_data_uni = session->quic_params.init_max_stream_data_uni; + ch->rx_max_udp_payload_size = session->quic_params.max_udp_payload_size; + ch->tx_init_max_data = session->quic_params.init_max_data; + ch->max_idle_timeout_local_req = /* sent in transport params */ + session->quic_params.max_idle_timeout; + ch->max_idle_timeout = /* negotiated value */ + session->quic_params.max_idle_timeout; + ch->rx_active_conn_id_limit = session->quic_params.active_conn_id_limit; + ch->max_local_streams_bidi = session->quic_params.max_local_streams_bidi; + ch->max_local_streams_uni = session->quic_params.max_local_streams_uni; + ch->resumed = 1; +} diff --git a/ssl/quic/quic_channel_local.h b/ssl/quic/quic_channel_local.h index 6dea6fdf1b2..f726b9555e9 100644 --- a/ssl/quic/quic_channel_local.h +++ b/ssl/quic/quic_channel_local.h @@ -482,6 +482,8 @@ struct quic_channel_st { /* Has qlog been requested? */ unsigned int is_tserver_ch : 1; + unsigned int resumed : 1; + /* Saved error stack in case permanent error was encountered */ ERR_STATE *err_state; diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index 8fb60911714..2556190ed00 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -5976,3 +5976,50 @@ int ossl_quic_set_diag_title(SSL_CTX *ctx, const char *title) return 1; } + +int ossl_quic_read_early_data(SSL *s, void *buf, size_t num, size_t *readbytes) +{ + return 0; +} + +int ossl_quic_write_early_data(SSL *s, const void *buf, size_t num, size_t *written) +{ + return 0; +} + +int ossl_quic_get_early_data_status(const SSL *s) +{ + return 0; +} + +int ossl_quic_set_max_early_data(SSL *ssl, uint32_t max_early_data) +{ + return 0; +} + +uint32_t ossl_quic_get_max_early_data(const SSL *ssl) +{ + return 0; +} + +int ossl_quic_set_recv_max_early_data(SSL *ssl, uint32_t recv_max_early_data) +{ + return 0; +} + +uint32_t ossl_quic_get_recv_max_early_data(const SSL *ssl) +{ + return 0; +} + +void ossl_quic_update_params(SSL *s) +{ + QCTX ctx; + + if (!expect_quic_conn_only(s, &ctx)) + return; + + qctx_lock(&ctx); + ossl_quic_update_parms_from_session(ctx.qc->ch); + qctx_unlock(&ctx); +} diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index be5f4a20f02..53d17dce9e7 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -2511,12 +2511,16 @@ int SSL_read_early_data(SSL *s, void *buf, size_t num, size_t *readbytes) int ret; SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(s); - /* TODO(QUIC 0RTT): 0-RTT support */ if (sc == NULL) { ERR_raise(ERR_LIB_SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); return SSL_READ_EARLY_DATA_ERROR; } +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_read_early_data(s, buf, num, readbytes); +#endif + if (ssl_reset_error_state(sc) == 0) return 0; @@ -2576,10 +2580,14 @@ int SSL_get_early_data_status(const SSL *s) { const SSL_CONNECTION *sc = SSL_CONNECTION_FROM_CONST_SSL_ONLY(s); - /* TODO(QUIC 0RTT): 0-RTT support */ if (sc == NULL) return 0; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_get_early_data_status(s); +#endif + return sc->ext.early_data; } @@ -2861,7 +2869,11 @@ int SSL_write_early_data(SSL *s, const void *buf, size_t num, size_t *written) uint32_t partialwrite; SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL_ONLY(s); - /* TODO(QUIC 0RTT): This will need special handling for QUIC */ +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_write_early_data(s, buf, num, written); +#endif + if (sc == NULL) return 0; @@ -7405,6 +7417,11 @@ int SSL_set_max_early_data(SSL *s, uint32_t max_early_data) if (sc == NULL) return 0; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_set_max_early_data(s, max_early_data); +#endif + sc->max_early_data = max_early_data; return 1; @@ -7417,6 +7434,11 @@ uint32_t SSL_get_max_early_data(const SSL *s) if (sc == NULL) return 0; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_get_max_early_data(s); +#endif + return sc->max_early_data; } @@ -7439,6 +7461,11 @@ int SSL_set_recv_max_early_data(SSL *s, uint32_t recv_max_early_data) if (sc == NULL) return 0; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_set_recv_max_early_data(s, recv_max_early_data); +#endif + sc->recv_max_early_data = recv_max_early_data; return 1; @@ -7451,6 +7478,11 @@ uint32_t SSL_get_recv_max_early_data(const SSL *s) if (sc == NULL) return 0; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + return ossl_quic_get_recv_max_early_data(s); +#endif + return sc->recv_max_early_data; } diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c index 9a1af383798..99644930d7c 100644 --- a/ssl/ssl_sess.c +++ b/ssl/ssl_sess.c @@ -21,6 +21,7 @@ #include "statem/statem_local.h" #ifndef OPENSSL_NO_QUIC #include "internal/quic_types.h" +#include "internal/quic_ssl.h" #endif static void SSL_SESSION_list_remove(SSL_CTX *ctx, SSL_SESSION *s); @@ -937,6 +938,11 @@ int SSL_set_session(SSL *s, SSL_SESSION *session) SSL_SESSION_free(sc->session); sc->session = session; +#ifndef OPENSSL_NO_QUIC + if (IS_QUIC(s)) + ossl_quic_update_params(s); +#endif + return 1; } diff --git a/test/build.info b/test/build.info index c7a9bd28f6e..126f7d332b8 100644 --- a/test/build.info +++ b/test/build.info @@ -434,6 +434,10 @@ IF[{- !$disabled{tests} -}] SOURCE[quic_radix_test]=helpers/ssltestlib.c $QUICTESTHELPERS INCLUDE[quic_radix_test]=../include ../apps/include DEPEND[quic_radix_test]=../libcrypto.a ../libssl.a libtestutil.a + + SOURCE[quic_0rtt_test]=quic_0rtt_test.c helpers/ssltestlib.c $QUICTESTHELPERS + INCLUDE[quic_0rtt_test]=../include ../apps/include + DEPEND[quic_0rtt_test]=../libcrypto.a ../libssl.a libtestutil.a ENDIF IF[{- !$disabled{'qlog'} -}] @@ -1318,7 +1322,7 @@ ENDIF PROGRAMS{noinst}=quic_srtm_test quic_lcidm_test quic_rcidm_test PROGRAMS{noinst}=quic_fifd_test quic_txp_test quic_tserver_test PROGRAMS{noinst}=quic_client_test quic_cc_test quic_multistream_test - PROGRAMS{noinst}=quic_radix_test + PROGRAMS{noinst}=quic_radix_test quic_0rtt_test SOURCE[quic_ackm_test]=quic_ackm_test.c cc_dummy.c INCLUDE[quic_ackm_test]=../include ../apps/include diff --git a/test/helpers/quictestlib.c b/test/helpers/quictestlib.c index 5405df6ebf3..7baa1ae7802 100644 --- a/test/helpers/quictestlib.c +++ b/test/helpers/quictestlib.c @@ -1374,3 +1374,315 @@ int bio_msg_copy(BIO_MSG *dst, BIO_MSG *src) return 1; } + +static const unsigned char alpn[] = { + 8, 'o', 's', 's', 'l', 't', 'e', 's', 't' +}; + +static int select_alpn(SSL *ssl, const unsigned char **out, unsigned char *out_len, + const unsigned char *in, unsigned int in_len, void *arg) +{ + int e; + + e = SSL_select_next_proto((unsigned char **)out, out_len, alpn, sizeof(alpn), + in, in_len); + return (e == OPENSSL_NPN_NEGOTIATED) ? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_ALERT_FATAL; +} + +int create_quic_ctx_pair(OSSL_LIB_CTX *libctx, SSL_CTX **c_sctx_p, SSL_CTX **s_sctx_p, + const char *certfile, const char *keyfile) +{ + int ok = 0; + SSL_CTX *c_sctx, *s_sctx; + + c_sctx = NULL; + s_sctx = NULL; + c_sctx = SSL_CTX_new_ex(libctx, NULL, OSSL_QUIC_client_method()); + if (!TEST_ptr(c_sctx)) { + TEST_info("%s SSL_CTX_new_ex(OSSL_QUIC_client_method()) failed", __func__); + goto done; + } + + s_sctx = SSL_CTX_new_ex(libctx, NULL, OSSL_QUIC_server_method()); + if (!TEST_ptr(s_sctx)) { + TEST_info("%s SSL_CTX_new_ex(OSSL_QUIC_server_method()) failed", __func__); + goto done; + } + + ok = SSL_CTX_use_certificate_file(s_sctx, certfile, SSL_FILETYPE_PEM); + if (ok != 1) { + TEST_info("%s SSL_CTX_use_certificate_file(%s) failed", __func__, certfile); + ok = 0; + goto done; + } + + ok = SSL_CTX_use_PrivateKey_file(s_sctx, keyfile, SSL_FILETYPE_PEM); + if (ok != 1) { + TEST_info("%s SSL_CTX_use_PrivateKey_file(%s) failed", __func__, keyfile); + ok = 0; + goto done; + } + SSL_CTX_set_alpn_select_cb(s_sctx, select_alpn, NULL); + + *c_sctx_p = c_sctx; + c_sctx = NULL; + *s_sctx_p = s_sctx; + s_sctx = NULL; + +done: + SSL_CTX_free(c_sctx); + SSL_CTX_free(s_sctx); + + return ok; +} + +static int create_dgram_pair(BIO **c_bio_p, BIO **s_bio_p) +{ + BIO *c_bio, *s_bio; + BIO_ADDR *localaddr = NULL; + struct in_addr ina = { (uint32_t)htonl(0x7f000001UL) }; + int bio_flags = 0; + int ok; + + bio_flags |= BIO_DGRAM_CAP_HANDLES_DST_ADDR; + bio_flags |= BIO_DGRAM_CAP_HANDLES_SRC_ADDR; + bio_flags |= BIO_DGRAM_CAP_PROVIDES_DST_ADDR; + bio_flags |= BIO_DGRAM_CAP_PROVIDES_SRC_ADDR; + + c_bio = NULL; + s_bio = NULL; + ok = BIO_new_bio_dgram_pair(&c_bio, 1500, &s_bio, 1500); + if (ok == 0) { + TEST_info("%s BIO_new_bio_dgram_pair() error", __func__); + goto done; + } + + ok = BIO_dgram_set_caps(c_bio, bio_flags); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_caps(c_bio, bio_flags) failed", __func__); + goto done; + } + + ok = BIO_dgram_set_caps(s_bio, bio_flags); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_caps(s_bio, bio_flags) failed", __func__); + goto done; + } + + ok = BIO_dgram_set_mtu(c_bio, 1500); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_mtu(c_bio) error", __func__); + goto done; + } + + ok = BIO_dgram_set_mtu(s_bio, 1500); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_mtu(s_bio) error", __func__); + goto done; + } + + localaddr = BIO_ADDR_new(); + if (!TEST_ptr(localaddr)) { + TEST_info("%s BIO_ADDR_new() error", __func__); + goto done; + } + ok = BIO_ADDR_rawmake(localaddr, AF_INET, &ina, sizeof(ina), htons(4080)); + if (ok == 0) { + TEST_info("%s BIO_ADDR_rawmake(4080) error", __func__); + goto done; + } + ok = BIO_dgram_set0_local_addr(c_bio, localaddr); + if (ok != 1) { + TEST_info("%s BIO_dgram_set0_local_addr(c_bio)", __func__); + ok = 0; + goto done; + } + + localaddr = BIO_ADDR_new(); + if (!TEST_ptr(localaddr)) { + TEST_info("%s BIO_ADDR_new() error", __func__); + goto done; + } + ok = BIO_ADDR_rawmake(localaddr, AF_INET, &ina, sizeof(ina), htons(8040)); + if (ok == 0) { + TEST_info("%s BIO_ADDR_rawmake(8040) error", __func__); + goto done; + } + ok = BIO_dgram_set0_local_addr(s_bio, localaddr); + if (ok != 1) { + TEST_info("%s BIO_dgram_set0_local_addr(c_bio)", __func__); + ok = 0; + goto done; + } + localaddr = NULL; + + ok = BIO_dgram_set_local_addr_enable(c_bio, 1); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_local_addr_enable(c_bio)", __func__); + goto done; + } + + ok = BIO_dgram_set_local_addr_enable(s_bio, 1); + if (ok == 0) { + TEST_info("%s BIO_dgram_set_local_addr_enable(s_bio)", __func__); + goto done; + } + + *c_bio_p = c_bio; + c_bio = NULL; + *s_bio_p = s_bio; + s_bio = NULL; + +done: + BIO_free(c_bio); + BIO_free(s_bio); + BIO_ADDR_free(localaddr); + + return ok; +} + +static int init_client(SSL *c_ssl) +{ + BIO_ADDR *peer_addr = NULL; + struct in_addr ina = { (uint32_t)htonl(0x7f000001UL) }; + int ok = 0; + + ok = SSL_set_tlsext_host_name(c_ssl, "localhost"); + if (ok == 0) { + TEST_info("%s SSL_set_tlsext_host_name()", __func__); + goto done; + } + + ok = SSL_set1_dnsname(c_ssl, "localhost"); + if (ok == 0) { + TEST_info("%s SSL_set1_dnsname()", __func__); + goto done; + } + + ok = SSL_set_alpn_protos(c_ssl, alpn, sizeof(alpn)); + if (ok != 0) { + TEST_info("%s SSL_set_alpn_protos() failed", __func__); + ok = 0; + goto done; + } + + ok = SSL_set_blocking_mode(c_ssl, 0); + if (ok == 0) { + TEST_info("%s SSL_set_block_mode() failed", __func__); + goto done; + } + + peer_addr = BIO_ADDR_new(); + if (!TEST_ptr(peer_addr)) { + TEST_info("%s BIO_ADDR_new() failed", __func__); + goto done; + } + ok = BIO_ADDR_rawmake(peer_addr, AF_INET, &ina, sizeof(ina), htons(8040)); + if (ok == 0) { + TEST_info("%s BIO_ADDR_rawmake() failed", __func__); + goto done; + } + ok = SSL_set1_initial_peer_addr(c_ssl, peer_addr); + if (ok == 0) { + TEST_info("%s SSL_set1_initial_peer_addr() failed", __func__); + goto done; + } + +done: + BIO_ADDR_free(peer_addr); + + return ok; +} + +int create_quic_conn_objects(SSL_CTX *c_sctx, SSL_CTX *s_sctx, SSL **c_ssl_p, SSL **s_ssl_p) +{ + BIO *c_bio, *s_bio; + SSL *c_ssl, *s_ssl; + int ok; + + c_bio = NULL; + s_bio = NULL; + c_ssl = NULL; + s_ssl = NULL; + ok = create_dgram_pair(&c_bio, &s_bio); + if (ok == 0) + goto done; + + c_ssl = SSL_new(c_sctx); + if (!TEST_ptr(c_ssl)) { + TEST_info("%s SSL_new(c_sctx) failed", __func__); + ok = 0; + goto done; + } + + ok = init_client(c_ssl); + if (ok == 0) + goto done; + + s_ssl = SSL_new_listener(s_sctx, 0); + if (!TEST_ptr(s_ssl)) { + TEST_info("%s SSL_new_listener() failed", __func__); + ok = 0; + goto done; + } + + SSL_set_bio(c_ssl, c_bio, c_bio); + SSL_set_bio(s_ssl, s_bio, s_bio); + c_bio = NULL; + s_bio = NULL; + + ok = SSL_set_blocking_mode(s_ssl, 0); + if (ok == 0) { + TEST_info("%s SSL_set_blocking_mode() failed", __func__); + ok = 0; + goto done; + } + + *c_ssl_p = c_ssl; + c_ssl = NULL; + *s_ssl_p = s_ssl; + s_ssl = NULL; + +done: + BIO_free(c_bio); + BIO_free(s_bio); + SSL_free(c_ssl); + SSL_free(s_ssl); + + return ok; +} + +SSL *create_quic_client(SSL_CTX *c_sctx, BIO *c_bio) +{ + SSL *c_ssl; + + if (!TEST_ptr(c_bio)) + return NULL; + + c_ssl = SSL_new(c_sctx); + if (!TEST_ptr(c_ssl)) { + TEST_info("%s SSL_new(c_sctx) failed", __func__); + return NULL; + } + + if (BIO_up_ref(c_bio) == 0) { + TEST_info("%s BIO_up_ref() failed)", __func__); + goto error; + } + SSL_set_bio(c_ssl, c_bio, c_bio); + + if (init_client(c_ssl) == 0) + goto error; + + if (SSL_set_blocking_mode(c_ssl, 0) == 0) { + TEST_info("%s SSL_set_blocking_mode() failed", __func__); + goto error; + } + + return c_ssl; + +error: + SSL_free(c_ssl); + + return NULL; +} diff --git a/test/helpers/quictestlib.h b/test/helpers/quictestlib.h index 63c77f90b78..a60d40ffe3c 100644 --- a/test/helpers/quictestlib.h +++ b/test/helpers/quictestlib.h @@ -289,3 +289,7 @@ const BIO_METHOD *bio_f_pkt_split_dgram_filter(void); /* Free the BIO filter method object */ void bio_f_pkt_split_dgram_filter_free(void); + +int create_quic_ctx_pair(OSSL_LIB_CTX *libctx, SSL_CTX **c_sctx_p, SSL_CTX **s_sctx_p, const char *certfile, const char *keyfile); +int create_quic_conn_objects(SSL_CTX *c_sctx, SSL_CTX *s_ctx, SSL **c_ssl_p, SSL **s_ssl_p); +SSL *create_quic_client(SSL_CTX *c_sctx, BIO *c_bio); diff --git a/test/quic_0rtt_test.c b/test/quic_0rtt_test.c new file mode 100644 index 00000000000..e824017e95d --- /dev/null +++ b/test/quic_0rtt_test.c @@ -0,0 +1,475 @@ +/* + * Copyright 2026 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/ssl.h> +#include <openssl/quic.h> +#include <openssl/bio.h> +#include <openssl/lhash.h> +#include <openssl/rand.h> + +#include "helpers/quictestlib.h" +#include "helpers/ssltestlib.h" + +#if defined(OPENSSL_THREADS) +#include "internal/thread_arch.h" +#endif + +#include "internal/numbers.h" /* UINT64_C */ + +#include "testutil.h" + +static char *certfile, *keyfile; + +static OSSL_LIB_CTX *libctx = NULL; +static OSSL_PROVIDER *defctxnull = NULL; + +#define MESSAGE "Hello There!" +#define MESSAGE_LEN (sizeof(MESSAGE) - 1) + +static SSL *client_ssl; +static SSL *server_ssl; + +static void handle_events(void) +{ + SSL_handle_events(client_ssl); + SSL_handle_events(server_ssl); +} + +static void set_server(SSL *s) +{ + server_ssl = s; +} +static void set_client(SSL *s) +{ + client_ssl = s; +} + +static int send_msg(SSL *s, const char *msg, int to_write, const char *print_msg) +{ + int rv, done; + + if (to_write <= 0) + return 0; + + done = 0; + rv = 0; + while (!done) { + rv = SSL_write(s, msg, to_write); + if (rv <= 0) { + switch (SSL_get_error(s, rv)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + rv = 0; + break; + default: + TEST_info("%s write failed\n", print_msg); + return 0; + } + } else { + done = 1; + } + /* + * Need to poke to handle_events() two times. Not doing so makes + * shutdown operation in caller later to fail. It then makes session + * resumption to fail, because instead of finishing shutdown we do + * SSL_free(). The exact failure mechanism is yet to be understood, + * making sure we call handle_events() two times here fixes/workarounds + * the problem. It's a magic! + */ + handle_events(); + handle_events(); + } + + return rv == to_write; +} + +static int recv_msg(SSL *s, char *buf, int to_read, const char *print_msg) +{ + int read, rv, done; + + if (to_read <= 0) + return 0; + + done = 0; + read = 0; + while (!done) { + if (read < to_read) { + to_read = to_read - read; + rv = SSL_read(s, &buf[read], to_read); + if (rv <= 0) { + switch (SSL_get_error(s, rv)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + rv = 0; + break; + default: + TEST_info("%s read failed at %d\n", print_msg, read); + return 0; + } + break; + } + read += rv; + } else { + done = 1; + } + handle_events(); + } + + return done; +} + +static int create_connection(SSL *clientssl, SSL *serverssl_listener, SSL **serverssl) +{ + int done = 0; + SSL *serverconn = NULL; + + while (!done) { + done = SSL_connect(clientssl); + if (done != 1) { + switch (SSL_get_error(clientssl, done)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + done = 0; + break; + default: + TEST_info("SSL_connect() failure"); + return 0; + } + } + done = SSL_handle_events(clientssl); + if (done == 0) { + break; + } + done = SSL_handle_events(serverssl_listener); + if (done == 0) { + break; + } + serverconn = SSL_accept_connection(serverssl_listener, 0); + done = (serverconn != NULL); + } + + if (done != 0) + *serverssl = serverconn; + + return done; +} + +static int destroy_connection(SSL *clientssl, SSL *serverssl) +{ + int done = 0; + + while (!done) { + done = SSL_shutdown(clientssl); + if (done != 1) { + switch (SSL_get_error(clientssl, done)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + done = 0; + break; + default: + TEST_info("SSL_shutdown() failure"); + return 0; + } + } + done = SSL_handle_events(clientssl); + if (done == 0) { + break; + } + done = SSL_handle_events(serverssl); + if (done == 0) { + break; + } + } + + return done; +} + +/* + * Test QUIC connection using regular QUIC client and server methods. + * No quic t-server is needed. Runs non blocking variant using busy-loops. + */ +static int test_quic_conn(void) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL, *serverssl_listener = NULL; + char recv_buf[80]; + int testresult = 0; + + if (!TEST_true(create_quic_ctx_pair(libctx, &cctx, &sctx, certfile, keyfile))) + return 0; + + if (!TEST_true(create_quic_conn_objects(cctx, sctx, &clientssl, &serverssl_listener))) + goto end; + + testresult = SSL_listen(serverssl_listener); + if (testresult == 0) { + TEST_info("%s SSL_listen() failed", __func__); + goto end; + } + + testresult = create_connection(clientssl, serverssl_listener, &serverssl); + if (testresult == 0) { + TEST_info("%s connect failed", __func__); + goto end; + } + + set_server(serverssl); + set_client(clientssl); + + testresult = send_msg(clientssl, MESSAGE, MESSAGE_LEN, + "test_quic_conn send_msg(clnt -> srv)"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(serverssl, recv_buf, MESSAGE_LEN, + "test_quic_conn recv_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + testresult = send_msg(serverssl, MESSAGE, MESSAGE_LEN, + "test_quic_conn send_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(clientssl, recv_buf, MESSAGE_LEN, + "test_quic_conn recv_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + testresult = 1; +end: + set_server(NULL); + set_client(NULL); + + SSL_free(clientssl); + SSL_free(serverssl); + SSL_free(serverssl_listener); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} + +static int test_quic_resume(void) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL, *serverssl_listener = NULL, *new_clientssl; + SSL_SESSION *sess = NULL; + BIO *bio = NULL; + char recv_buf[80]; + int testresult = 0; + + if (!TEST_true(create_quic_ctx_pair(libctx, &cctx, &sctx, certfile, keyfile))) + return 0; + /* + * note: no t-server for testing is used here. + */ + if (!TEST_true(create_quic_conn_objects(cctx, sctx, &clientssl, &serverssl_listener))) + goto end; + + testresult = SSL_listen(serverssl_listener); + if (testresult == 0) { + TEST_info("%s SSL_listen() failed", __func__); + goto end; + } + + testresult = create_connection(clientssl, serverssl_listener, &serverssl); + if (testresult == 0) { + TEST_info("%s connect failed", __func__); + goto end; + } + + set_server(serverssl); + set_client(clientssl); + + testresult = send_msg(clientssl, MESSAGE, MESSAGE_LEN, + "test_quic_resume send_msg(clnt -> srv)"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(serverssl, recv_buf, MESSAGE_LEN, + "test_quic_resume recv_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + testresult = send_msg(serverssl, MESSAGE, MESSAGE_LEN, + "test_quic_resume send_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(clientssl, recv_buf, MESSAGE_LEN, + "test_quic_resume recv_msg(srv -> clnt)"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + sess = SSL_get1_session(clientssl); + if (!TEST_ptr(sess)) { + TEST_info("%s SSL_get1_session() fails", __func__); + testresult = 0; + goto end; + } + + /* + * Drop existing connection and create new client. + * The new client is using the same client BIO. + */ + set_server(NULL); + set_client(NULL); + /* + * Need to call SSL_shutdown() in order to be able + * to resume session. Paragraph here comes from + * SSL_shutdown(3ossl) + * + * This approach of a single SSL_shutdown() call without + * waiting is preferable to simply calling SSL_free(3) + * or SSL_clear(3) as calling SSL_shutdown() beforehand + * makes an SSL session eligible for subsequent reuse and + * notifies the peer of connection shutdown. + * + * NOTE: session from test_quic_conn() can not be resumed, + * because the test uses SSL_free(). + */ + destroy_connection(clientssl, serverssl); + SSL_free(serverssl); + serverssl = NULL; + bio = SSL_get_rbio(clientssl); + if (bio != SSL_get_wbio(clientssl)) { + TEST_info("%s bio != SSL_get_wbio(clientssl)", __func__); + testresult = 0; + goto end; + } + /* + * NOTE: We can not free existing client yet, because + * c_bio is not copied/duplicated. Old client can + * be freed after create_quic_client() returns. + * The create_quic_client() duplicates the bio + * for new_clientssl. + */ + new_clientssl = create_quic_client(cctx, bio); + if (new_clientssl == NULL) { + TEST_info("%s create_quic_client() failed", __func__); + testresult = 0; + goto end; + } + SSL_free(clientssl); + clientssl = new_clientssl; + + testresult = SSL_set_session(clientssl, sess); + if (testresult == 0) { + TEST_info("%s SSL_set_session(clientssl) failed", __func__); + goto end; + } + + testresult = create_connection(clientssl, serverssl_listener, &serverssl); + if (testresult == 0) { + TEST_info("%s re-connect failed", __func__); + goto end; + } + + set_server(serverssl); + set_client(clientssl); + + testresult = send_msg(clientssl, MESSAGE, MESSAGE_LEN, + "test_quic_resume send_msg(clnt -> srv) [resumed]"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(serverssl, recv_buf, MESSAGE_LEN, + "test_quic_resume recv_msg(srv -> clnt) [resumed]"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + testresult = send_msg(serverssl, MESSAGE, MESSAGE_LEN, + "test_quic_resume send_msg(srv -> clnt) [resumed]"); + if (testresult == 0) + goto end; + + memset(recv_buf, 0, sizeof(recv_buf)); + testresult = recv_msg(clientssl, recv_buf, MESSAGE_LEN, + "test_quic_resume recv_msg(srv -> clnt) [resumed]"); + if (testresult == 0) + goto end; + if (!TEST_str_eq(recv_buf, MESSAGE)) + goto end; + + testresult = SSL_session_reused(clientssl); + if (testresult == 0) { + TEST_info("%s SSL_session_reused(): did not reuse", __func__); + goto end; + } + + testresult = 1; +end: + set_server(NULL); + set_client(NULL); + SSL_SESSION_free(sess); + SSL_free(serverssl); + SSL_free(clientssl); + SSL_free(serverssl_listener); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} + +int setup_tests(void) +{ +#if defined(OPENSSL_NO_QUIC) + return TEST_skip("QUIC is not supported by this build"); +#else + libctx = OSSL_LIB_CTX_new(); + if (!TEST_ptr(libctx)) + return 0; + + defctxnull = OSSL_PROVIDER_load(NULL, "null"); + + if (!test_skip_common_options()) { + TEST_error("Error parsing test options\n"); + return 0; + } + + if (!TEST_ptr(certfile = test_get_argument(0)) + || !TEST_ptr(keyfile = test_get_argument(1))) + return 0; + + ADD_TEST(test_quic_conn); + ADD_TEST(test_quic_resume); + + return 1; +#endif +} + +void cleanup_tests(void) +{ + OSSL_PROVIDER_unload(defctxnull); + OSSL_LIB_CTX_free(libctx); +} diff --git a/test/recipes/70-test_quic_0rtt.t b/test/recipes/70-test_quic_0rtt.t new file mode 100644 index 00000000000..290915daa7c --- /dev/null +++ b/test/recipes/70-test_quic_0rtt.t @@ -0,0 +1,27 @@ +#! /usr/bin/env perl +# Copyright 2026 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 + +use OpenSSL::Test qw/:DEFAULT srctop_file result_dir data_file/; +use OpenSSL::Test::Utils; +use File::Temp qw(tempfile); +use File::Path 2.00 qw(rmtree mkpath); + +setup("test_quic_0rtt"); + +plan skip_all => "QUIC protocol is not supported by this OpenSSL build" + if disabled('quic'); + +plan tests => 1; + +# +# note we might need to add coverage for qlog +# + +ok(run(test(["quic_0rtt_test", + srctop_file("test", "certs", "servercert.pem"), + srctop_file("test", "certs", "serverkey.pem")]))); |
