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 <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26138)
This commit is contained in:
Alexandr Nedvedicky 2024-12-09 10:49:05 +01:00 committed by Neil Horman
parent 6bae611c99
commit 0b15147a37
3 changed files with 275 additions and 9 deletions

View file

@ -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;
@ -1316,7 +1316,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;
@ -1797,9 +1796,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;
}

View file

@ -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;
}

View file

@ -2346,6 +2346,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")
@ -2441,6 +2605,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();