From: Alexandr Nedvedicky Date: Mon, 9 Dec 2024 09:49:05 +0000 (+0100) Subject: Implement SSL_new_from_listener() X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f30b75a7fe443bfc1a6d72319febdc0295e637be;p=thirdparty%2Fopenssl.git Implement SSL_new_from_listener() SSL_new_from_listner() creates QUIC connection object (QCSO) from listener. Caller can use the object retuned from SSL_new_from_listener() to connect to remote QUIC server. The QCSO created here shares engine/port with listener. the change is covered by `test_ssl_new_from_listener()` in test/quicapitest.c Reviewed-by: Neil Horman Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/26138) --- diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 4ab498d024d..80360f687c8 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -263,10 +263,10 @@ static int ch_init(QUIC_CHANNEL *ch) ch->have_qsm = 1; if (!ch->is_server - && !ossl_quic_lcidm_generate_initial(ch->lcidm, ch, &txp_args.cur_scid)) + && !ossl_quic_lcidm_generate_initial(ch->lcidm, ch, &ch->init_scid)) goto err; - /* We use a zero-length SCID. */ + txp_args.cur_scid = ch->init_scid; txp_args.cur_dcid = ch->init_dcid; txp_args.ack_delay_exponent = 3; txp_args.qtx = ch->qtx; @@ -1296,7 +1296,6 @@ static int ch_on_transport_params(const unsigned char *params, goto malformed; } - /* Must match SCID of first Initial packet from server. */ if (!ossl_quic_conn_id_eq(&ch->init_scid, &cid)) { reason = TP_REASON_EXPECTED_VALUE("INITIAL_SCID"); goto malformed; @@ -1777,9 +1776,8 @@ static int ch_generate_transport_params(QUIC_CHANNEL *ch) &ch->init_dcid)) goto err; } else { - /* Client always uses an empty SCID. */ - if (ossl_quic_wire_encode_transport_param_bytes(&wpkt, QUIC_TPARAM_INITIAL_SCID, - NULL, 0) == NULL) + if (!ossl_quic_wire_encode_transport_param_cid(&wpkt, QUIC_TPARAM_INITIAL_SCID, + &ch->init_scid)) goto err; } diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index e8b0888bcad..0fb72cd3caa 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -1257,8 +1257,7 @@ int ossl_quic_conn_set_initial_peer_addr(SSL *s, return 1; } - ctx.qc->init_peer_addr = *peer_addr; - return 1; + return BIO_ADDR_copy(&ctx.qc->init_peer_addr, peer_addr); } /* @@ -4328,10 +4327,114 @@ err: /* * SSL_new_from_listener * --------------------- + * code here is derived from ossl_quic_new(). The `ssl` argument is + * a listener object which already comes with QUIC port/engine. The newly + * created QUIC connection object (QCSO) is going to share the port/engine + * with listener (`ssl`). The `ssl` also becomes a parent of QCSO created + * by this function. The caller uses QCSO instance to connect to + * remote QUIC server. + * + * The QCSO created here requires us to also create a channel so we + * can connect to remote server. */ SSL *ossl_quic_new_from_listener(SSL *ssl, uint64_t flags) { - /* TODO(QUIC SERVER): Implement SSL_new_from_listener */ + QCTX ctx; + QUIC_CONNECTION *qc; + QUIC_LISTENER *ql; + SSL_CONNECTION *sc = NULL; + + if (flags != 0) + return NULL; + + if (!expect_quic_listener(ssl, &ctx)) + return NULL; + + if (!SSL_up_ref(&ctx.ql->obj.ssl)) + return NULL; + + qctx_lock(&ctx); + + ql = ctx.ql; + + if ((qc = OPENSSL_zalloc(sizeof(*qc))) == NULL) { + QUIC_RAISE_NON_NORMAL_ERROR(NULL, ERR_R_CRYPTO_LIB, NULL); + goto err; + } + + /* + * NOTE: setting a listener here is needed so `qc_cleanup()` does the right + * thing. Setting listener to ql avoids premature destruction of port in + * qc_cleanup() + */ + qc->listener = ql; + qc->engine = ql->engine; + qc->port = ql->port; +/* create channel */ +#if defined(OPENSSL_THREADS) + /* this is the engine mutex */ + qc->mutex = ql->mutex; +#endif +#if !defined(OPENSSL_NO_QUIC_THREAD_ASSIST) + qc->is_thread_assisted + = ((ql->obj.domain_flags & SSL_DOMAIN_FLAG_THREAD_ASSISTED) != 0); +#endif + + /* Create the handshake layer. */ + qc->tls = ossl_ssl_connection_new_int(ql->obj.ssl.ctx, NULL, TLS_method()); + if (qc->tls == NULL || (sc = SSL_CONNECTION_FROM_SSL(qc->tls)) == NULL) { + QUIC_RAISE_NON_NORMAL_ERROR(NULL, ERR_R_INTERNAL_ERROR, NULL); + goto err; + } + sc->s3.flags |= TLS1_FLAGS_QUIC; + + qc->default_ssl_options = OSSL_QUIC_PERMITTED_OPTIONS; + qc->last_error = SSL_ERROR_NONE; + + /* + * This is QCSO, we don't expect to accept connections + * on success the channel assumes ownership of tls, we need + * to grab reference for qc. + */ + qc->ch = ossl_quic_port_create_outgoing(qc->port, qc->tls); + + ossl_quic_channel_set_msg_callback(qc->ch, ql->obj.ssl.ctx->msg_callback, &qc->obj.ssl); + ossl_quic_channel_set_msg_callback_arg(qc->ch, ql->obj.ssl.ctx->msg_callback_arg); + + /* + * We deliberately pass NULL for engine and port, because we don't want to + * to turn QCSO we create here into an event leader, nor port leader. + * Both those roles are occupied already by listener (`ssl`) we use + * to create a new QCSO here. + */ + if (!ossl_quic_obj_init(&qc->obj, ql->obj.ssl.ctx, + SSL_TYPE_QUIC_CONNECTION, + &ql->obj.ssl, NULL, NULL)) { + QUIC_RAISE_NON_NORMAL_ERROR(NULL, ERR_R_INTERNAL_ERROR, NULL); + goto err; + } + + /* Initialise libssl APL-related state. */ + qc->default_stream_mode = SSL_DEFAULT_STREAM_MODE_AUTO_BIDI; + qc->default_ssl_mode = qc->obj.ssl.ctx->mode; + qc->default_ssl_options = qc->obj.ssl.ctx->options & OSSL_QUIC_PERMITTED_OPTIONS; + qc->incoming_stream_policy = SSL_INCOMING_STREAM_POLICY_AUTO; + qc->last_error = SSL_ERROR_NONE; + + qc_update_reject_policy(qc); + + qctx_unlock(&ctx); + + return &qc->obj.ssl; + +err: + if (qc != NULL) { + qc_cleanup(qc, /* have_lock= */ 0); + OPENSSL_free(qc); + } + qctx_unlock(&ctx); + SSL_free(&ctx.ql->obj.ssl); + return NULL; } diff --git a/test/quicapitest.c b/test/quicapitest.c index 34ee52d07bf..114fedf2c99 100644 --- a/test/quicapitest.c +++ b/test/quicapitest.c @@ -2345,6 +2345,170 @@ static int test_early_ticks(void) return testresult; } +static int select_alpn(SSL *ssl, const unsigned char **out, + unsigned char *out_len, const unsigned char *in, + unsigned int in_len, void *arg) +{ + static unsigned char alpn[] = { 8, 'o', 's', 's', 'l', 't', 'e', 's', 't' }; + + if (SSL_select_next_proto((unsigned char **)out, out_len, alpn, sizeof(alpn), + in, in_len) == OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_OK; + return SSL_TLSEXT_ERR_ALERT_FATAL; +} + +static SSL_CTX *create_server_ctx(void) +{ + SSL_CTX *ssl_ctx; + + if (!TEST_ptr(ssl_ctx = SSL_CTX_new_ex(libctx, NULL, OSSL_QUIC_server_method())) + || !TEST_true(SSL_CTX_use_certificate_file(ssl_ctx, cert, SSL_FILETYPE_PEM)) + || !TEST_true(SSL_CTX_use_PrivateKey_file(ssl_ctx, privkey, SSL_FILETYPE_PEM))) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + } else { + SSL_CTX_set_alpn_select_cb(ssl_ctx, select_alpn, NULL); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + } + + return ssl_ctx; +} + +static BIO_ADDR *create_addr(struct in_addr *ina, short int port) +{ + BIO_ADDR *addr = NULL; + + if (!TEST_ptr(addr = BIO_ADDR_new())) + return NULL; + + if (!TEST_true(BIO_ADDR_rawmake(addr, AF_INET, ina, sizeof(struct in_addr), + htons(port)))) { + BIO_ADDR_free(addr); + return NULL; + } + + return addr; +} + +static int bio_addr_bind(BIO *bio, BIO_ADDR *addr) +{ + int bio_caps = BIO_DGRAM_CAP_HANDLES_DST_ADDR | BIO_DGRAM_CAP_HANDLES_SRC_ADDR; + + if (!TEST_true(BIO_dgram_set_caps(bio, bio_caps))) + return 0; + + if (!TEST_int_eq(BIO_dgram_set0_local_addr(bio, addr), 1)) + return 0; + + return 1; +} + +static SSL *ql_create(SSL_CTX *ssl_ctx, BIO *bio) +{ + SSL *qserver; + + if (!TEST_ptr(qserver = SSL_new_listener(ssl_ctx, 0))) { + BIO_free(bio); + return NULL; + } + + SSL_set_bio(qserver, bio, bio); + + if (!TEST_true(SSL_listen(qserver))) { + SSL_free(qserver); + return NULL; + } + + return qserver; +} + +static int qc_init(SSL *qconn, BIO_ADDR *dst_addr) +{ + static unsigned char alpn[] = { 8, 'o', 's', 's', 'l', 't', 'e', 's', 't' }; + + if (!TEST_true(SSL_set1_initial_peer_addr(qconn, dst_addr))) + return 0; + + if (!TEST_false(SSL_set_alpn_protos(qconn, alpn, sizeof(alpn)))) + return 0; + + return 1; +} + +static int test_ssl_new_from_listener(void) +{ + SSL_CTX *lctx = NULL, *sctx = NULL; + SSL *qlistener = NULL, *qserver = NULL, *qconn = 0; + int testresult = 0; + int chk; + BIO *lbio = NULL, *sbio = NULL; + BIO_ADDR *addr = NULL; + struct in_addr ina; + + ina.s_addr = htonl(0x1f000001); + if (!TEST_ptr(lctx = create_server_ctx()) + || !TEST_ptr(sctx = create_server_ctx()) + || !TEST_true(BIO_new_bio_dgram_pair(&lbio, 0, &sbio, 0))) + goto err; + + if (!TEST_ptr(addr = create_addr(&ina, 8040))) + goto err; + + if (!TEST_true(bio_addr_bind(lbio, addr))) + goto err; + addr = NULL; + + if (!TEST_ptr(addr = create_addr(&ina, 4080))) + goto err; + + if (!TEST_true(bio_addr_bind(sbio, addr))) + goto err; + addr = NULL; + + qlistener = ql_create(lctx, lbio); + lbio = NULL; + if (!TEST_ptr(qlistener)) + goto err; + + qserver = ql_create(sctx, sbio); + sbio = NULL; + if (!TEST_ptr(qserver)) + goto err; + + if (!TEST_ptr(qconn = SSL_new_from_listener(qlistener, 0))) + goto err; + + if (!TEST_ptr(addr = create_addr(&ina, 4080))) + goto err; + + chk = qc_init(qconn, addr); + if (!TEST_true(chk)) + goto err; + + while ((chk = SSL_do_handshake(qconn)) == -1) { + SSL_handle_events(qserver); + SSL_handle_events(qlistener); + } + + if (!TEST_int_gt(chk, 0)) { + TEST_info("SSL_do_handshake() failed\n"); + goto err; + } + + testresult = 1; + err: + SSL_free(qconn); + SSL_free(qlistener); + SSL_free(qserver); + BIO_free(lbio); + BIO_free(sbio); + SSL_CTX_free(sctx); + SSL_CTX_free(lctx); + BIO_ADDR_free(addr); + + return testresult; +} + /***********************************************************************************/ OPT_TEST_DECLARE_USAGE("provider config certsdir datadir\n") @@ -2440,6 +2604,7 @@ int setup_tests(void) ADD_TEST(test_session_cb); ADD_TEST(test_domain_flags); ADD_TEST(test_early_ticks); + ADD_TEST(test_ssl_new_from_listener); return 1; err: cleanup_tests();