]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Implement SSL_new_from_listener()
authorAlexandr Nedvedicky <sashan@openssl.org>
Mon, 9 Dec 2024 09:49:05 +0000 (10:49 +0100)
committerNeil Horman <nhorman@openssl.org>
Mon, 17 Feb 2025 16:27:33 +0000 (11:27 -0500)
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)

ssl/quic/quic_channel.c
ssl/quic/quic_impl.c
test/quicapitest.c

index 61611bf3145dc8fb429f9a33717ea128fd39098d..8b061630a6c47a6c6bcf0f332e7780d428a5526c 100644 (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;
     }
 
index e8b0888bcad00636c2547199cff2b30f56c7cb5e..0fb72cd3caa2381ff3a66ef70e31b085f4c71a20 100644 (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;
 }
 
index 48bca30d2d9b49125b9986ec7d241def09fc9130..504256ec73e053689705d715c3e23abaac910c9a 100644 (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();