Commit 6b3f51aebb for bind
commit 6b3f51aebbb60b2a7bd5bfe49d45eeea29305889
Author: Aydın Mercan <aydin@isc.org>
Date: Thu Aug 27 12:21:16 2026 +0300
Add the required TLS plumbing for QUIC connections
The TLS usage inside QUIC connections require coordination from the TLS
stack. While ngtcp2 provides relevant `libngtcp2-crypto-*` libaries for
this, they are not used due to a general lack of packaging across
distributions.
Currently, there are two ways of doing this for BIND:
- Relevant `OSSL_DISPATCH` functions added in 3.5
- The `SSL_QUIC_METHOD` vtable created by BoringSSL and adopted by other
OpenSSL forks.
Supporting one of the two interfaces is a hard requirement for QUIC.
diff --git a/lib/isc/include/isc/quic.h b/lib/isc/include/isc/quic.h
index 7c45baecdc..b189d382cd 100644
--- a/lib/isc/include/isc/quic.h
+++ b/lib/isc/include/isc/quic.h
@@ -70,13 +70,106 @@ typedef enum isc_quic_version {
ISC_QUIC_VERSION__MAX = 4,
} isc_quic_version_t;
+typedef enum isc_quic_application_error_kind {
+ /**
+ * \brief
+ * Indeterminate application error status.
+ *
+ * It is safe to trigger an contract failure upon receiving this value.
+ */
+ ISC_QUIC_APPLICATION_ERROR_INVALID = 0,
+
+ /**
+ * \brief
+ * No application error code is set.
+ */
+ ISC_QUIC_APPLICATION_ERROR_NONE = 1,
+
+ /**
+ * \brief
+ * The linked ngtcp2 is too old to determine which side has set the
+ * application error code.
+ */
+ ISC_QUIC_APPLICATION_ERROR_UNKNOWN = 2,
+
+ ISC_QUIC_APPLICATION_ERROR_RX_ONLY = 3,
+ ISC_QUIC_APPLICATION_ERROR_TX_ONLY = 4,
+ ISC_QUIC_APPLICATION_ERROR_RX_AND_TX = 5,
+ ISC_QUIC_APPLICATION_ERROR__MAX = 6,
+} isc_quic_application_error_kind_t;
+
/**
* \brief
* User-specified callbacks
*
* All callbacks are optional.
*/
-struct isc_quic_conn_callbacks {};
+struct isc_quic_conn_callbacks {
+ /**
+ * \brief
+ * Callback function for when handshake has been completed
+ */
+ void (*handshake_completed)(void *cbarg);
+
+ /**
+ * \brief
+ * Callback function for when the remote endpoint has opened a new
+ * stream.
+ */
+ isc_result_t (*stream_opened)(isc_quic_conn_t *conn, void *cbarg,
+ int64_t stream_id);
+
+ /**
+ * \brief
+ * Callback function for when a stream has been closed.
+ *
+ * If the application error kind is unknown, both RX and TX application
+ * errors codes will be set as the unknown side's error code.
+ *
+ * Returning anything other than `ISC_R_SUCCESS` will cause the
+ * connection to fail and require closing.
+ */
+ isc_result_t (*stream_closed)(isc_quic_conn_t *conn, void *cbarg,
+ int64_t stream_id,
+ isc_quic_application_error_kind_t kind,
+ uint64_t rx_application_error_code,
+ uint64_t tx_application_error_code);
+
+ /**
+ * \brief
+ * Callback function for when there is data to be read in a stream.
+ *
+ * Called by isc_quic_conn_push_packet when there is data to be read
+ * in a stream.
+ *
+ * Returning anything other than `ISC_R_SUCCESS` will cause the
+ * connection to fail and require closing.
+ */
+ isc_result_t (*data_read)(isc_quic_conn_t *conn, void *cbarg,
+ isc_quic_stream_data_info_t info,
+ isc_constregion_t data);
+};
+
+struct isc_quic_stream_data_info {
+ /**
+ * \brief
+ * True if this is the final packet that will be received from this
+ * stream.
+ */
+ bool final;
+
+ /**
+ * \brief
+ * True if the data is trasmitted during 0-RTT.
+ */
+ bool zerortt;
+
+ /**
+ * \brief
+ * Stream ID of received data.
+ */
+ int64_t stream_id;
+};
/**
* \brief
@@ -385,3 +478,34 @@ isc_quic_conn_server_create(
*/
ISC_REFCOUNT_DECL(isc_quic_conn);
+
+isc_result_t
+isc_quic_tlsctx_client_configure(isc_tlsctx_t *tlsctx);
+/**<
+ * \brief
+ * Configure the client TLS context to be used for QUIC connections.
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_TLSERROR on failure
+ */
+
+isc_result_t
+isc_quic_tlsctx_server_configure(isc_tlsctx_t *tlsctx);
+/**<
+ * \brief
+ * Configure the server TLS context to be used for QUIC connections.
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_TLSERROR on failure
+ */
+
+#ifdef HAVE_LIBNGTCP2
+void
+isc__quic_initialize(void);
+
+void
+isc__quic_shutdown(void);
+#else /* HAVE_LIBNGTCP2 */
+#define isc__quic_initialize()
+#define isc__quic_shutdown()
+#endif /* HAVE_LIBNGTCP2 */
diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h
index fcbe1e4092..72c3757f02 100644
--- a/lib/isc/include/isc/types.h
+++ b/lib/isc/include/isc/types.h
@@ -79,11 +79,10 @@ typedef struct isc_timer isc_timer_t; /*%< Timer */
typedef struct isc_work isc_work_t; /*%< Work offloaded to an
* external thread */
-#ifdef HAVE_LIBNGTCP2
-typedef struct isc_quic_conn isc_quic_conn_t;
-typedef struct isc_quic_conn_callbacks isc_quic_conn_callbacks_t;
-typedef struct isc_quic_conn_options isc_quic_conn_options_t;
-#endif /* HAVE_LIBNGTCP2 */
+typedef struct isc_quic_conn isc_quic_conn_t;
+typedef struct isc_quic_conn_options isc_quic_conn_options_t;
+typedef struct isc_quic_conn_callbacks isc_quic_conn_callbacks_t;
+typedef struct isc_quic_stream_data_info isc_quic_stream_data_info_t;
#if HAVE_LIBNGHTTP2
typedef struct isc_nm_http_endpoints isc_nm_http_endpoints_t;
diff --git a/lib/isc/lib.c b/lib/isc/lib.c
index c0f9d30dba..f98db0a79d 100644
--- a/lib/isc/lib.c
+++ b/lib/isc/lib.c
@@ -19,6 +19,7 @@
#include <isc/md.h>
#include <isc/mem.h>
#include <isc/os.h>
+#include <isc/quic.h>
#include <isc/refcount.h>
#include <isc/tls.h>
#include <isc/urcu.h>
@@ -54,6 +55,7 @@ isc__lib_initialize(void) {
isc__mem_initialize();
isc__log_initialize();
isc__crypto_initialize();
+ isc__quic_initialize();
isc__uv_initialize();
isc__xml_initialize();
isc__hash_initialize();
@@ -73,6 +75,7 @@ isc__lib_shutdown(void) {
isc__iterated_hash_shutdown();
isc__xml_shutdown();
isc__uv_shutdown();
+ isc__quic_shutdown();
isc__crypto_shutdown();
isc__log_shutdown();
isc__mem_shutdown();
diff --git a/lib/isc/quic/boringlike.c b/lib/isc/quic/boringlike.c
new file mode 100644
index 0000000000..49da236539
--- /dev/null
+++ b/lib/isc/quic/boringlike.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdint.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+
+#if __has_include(<openssl/nid.h>)
+#include <openssl/nid.h>
+#endif
+
+#include <isc/crypto.h>
+#include <isc/ossl_wrap.h>
+#include <isc/quic.h>
+#include <isc/util.h>
+
+#include "quic_p.h"
+
+static int quic_index = -1;
+
+static int
+set_read_secret_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const SSL_CIPHER *cipher, const uint8_t *secret, size_t len);
+
+static int
+set_write_secret_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const SSL_CIPHER *cipher, const uint8_t *secret,
+ size_t len);
+
+static int
+add_handshake_data_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const uint8_t *data, size_t len);
+
+static int
+flush_flight_cb(isc_tls_t *tls);
+
+static int
+send_alert_cb(isc_tls_t *ssl, enum ssl_encryption_level_t level, uint8_t alert);
+
+static SSL_QUIC_METHOD method = {
+ .set_read_secret = set_read_secret_cb,
+ .set_write_secret = set_write_secret_cb,
+ .add_handshake_data = add_handshake_data_cb,
+ .flush_flight = flush_flight_cb,
+ .send_alert = send_alert_cb,
+};
+
+/**
+ * \brief
+ * Lookup table for translating libssl encryption levels to its ngtcp2
+ * counterpart.
+ */
+static ngtcp2_encryption_level libssl_to_ngtcp2_level_lut[] = {
+ [ssl_encryption_initial] = NGTCP2_ENCRYPTION_LEVEL_INITIAL,
+ [ssl_encryption_early_data] = NGTCP2_ENCRYPTION_LEVEL_0RTT,
+ [ssl_encryption_handshake] = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE,
+ [ssl_encryption_application] = NGTCP2_ENCRYPTION_LEVEL_1RTT,
+};
+
+/**
+ * \brief
+ * Lookup table for translating ngtcp2 encryption levels to its libssl
+ * counterpart.
+ */
+static enum ssl_encryption_level_t libngtcp2_to_ssl_level_lut[] = {
+ [NGTCP2_ENCRYPTION_LEVEL_INITIAL] = ssl_encryption_initial,
+ [NGTCP2_ENCRYPTION_LEVEL_0RTT] = ssl_encryption_early_data,
+ [NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE] = ssl_encryption_handshake,
+ [NGTCP2_ENCRYPTION_LEVEL_1RTT] = ssl_encryption_application,
+};
+
+static int
+set_read_secret_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const SSL_CIPHER *cipher ISC_ATTR_UNUSED,
+ const uint8_t *secret, size_t len) {
+ isc_quic_conn_t *conn = SSL_get_ex_data(tls, quic_index);
+ bool is_server;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ is_server = ngtcp2_conn_is_server2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ is_server = ngtcp2_conn_is_server(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ if (isc__quic_setup_read_key(
+ conn, is_server, libssl_to_ngtcp2_level_lut[level],
+ (isc_constregion_t){ len, secret }) != ISC_R_SUCCESS)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+set_write_secret_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const SSL_CIPHER *cipher ISC_ATTR_UNUSED,
+ const uint8_t *secret, size_t len) {
+ isc_quic_conn_t *conn = SSL_get_ex_data(tls, quic_index);
+ bool is_server;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ is_server = ngtcp2_conn_is_server2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ is_server = ngtcp2_conn_is_server(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ if (isc__quic_setup_write_key(
+ conn, is_server, libssl_to_ngtcp2_level_lut[level],
+ (isc_constregion_t){ len, secret }) != ISC_R_SUCCESS)
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+add_handshake_data_cb(isc_tls_t *tls, enum ssl_encryption_level_t level,
+ const uint8_t *data, size_t len) {
+ isc_quic_conn_t *conn;
+ int r;
+
+ REQUIRE(level < ARRAY_SIZE(libssl_to_ngtcp2_level_lut));
+
+ conn = SSL_get_ex_data(tls, quic_index);
+
+ r = ngtcp2_conn_submit_crypto_data(
+ conn->inner, libssl_to_ngtcp2_level_lut[level], data, len);
+ if (r != 0) {
+ ngtcp2_conn_set_tls_error(conn->inner, r);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+flush_flight_cb(isc_tls_t *tls ISC_ATTR_UNUSED) {
+ return 1;
+}
+
+static int
+send_alert_cb(isc_tls_t *tls, enum ssl_encryption_level_t level ISC_ATTR_UNUSED,
+ uint8_t alert) {
+ isc_quic_conn_t *conn = SSL_get_ex_data(tls, quic_index);
+ ngtcp2_conn_set_tls_alert(conn->inner, alert);
+ return 1;
+}
+
+static int
+quic_alpn_select(isc_tls_t *tls, const unsigned char **outp,
+ unsigned char *lenp, const unsigned char *in, unsigned int len,
+ void *arg ISC_ATTR_UNUSED) {
+ isc_quic_conn_t *conn = SSL_get_ex_data(tls, quic_index);
+ INSIST(conn != NULL);
+
+ int r = SSL_select_next_proto(UNCONST(outp), lenp, in, len,
+ conn->alpn.data, conn->alpn.len);
+ if (r != OPENSSL_NPN_NEGOTIATED) {
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+isc_result_t
+isc__quic_set_remote_transport_params(isc_quic_conn_t *conn, isc_tls_t *tls) {
+ const uint8_t *out_params;
+ size_t out_params_len;
+ int r;
+
+ SSL_get_peer_quic_transport_params(tls, &out_params, &out_params_len);
+
+ r = ngtcp2_conn_decode_and_set_remote_transport_params(
+ conn->inner, out_params, out_params_len);
+ if (r != 0) {
+ ngtcp2_conn_set_tls_error(conn->inner, r);
+ return ISC_R_TLSERROR;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc__quic_set_local_transport_params(isc_quic_conn_t *conn ISC_ATTR_UNUSED,
+ isc_tls_t *tls) {
+ uint8_t buffer[512];
+ ngtcp2_ssize len;
+ int r;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ len = ngtcp2_conn_encode_local_transport_params2(conn->inner, buffer,
+ sizeof(buffer));
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ len = ngtcp2_conn_encode_local_transport_params(conn->inner, buffer,
+ sizeof(buffer));
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ if (len < 0) {
+ return ISC_R_NOSPACE;
+ }
+
+ r = SSL_set_quic_transport_params(tls, buffer, len);
+ if (r != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_set_quic_transport_params", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc__quic_setup_tls(isc_tls_t *tls, isc_quic_conn_t *conn) {
+ SSL_set_ex_data(tls, quic_index, conn);
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc__quic_do_tls(isc_quic_conn_t *conn, isc_tls_t *tls,
+ ngtcp2_encryption_level nglevel, isc_constregion_t data) {
+ enum ssl_encryption_level_t level;
+ isc_result_t result;
+ ngtcp2_conn *ngconn;
+ int r;
+
+ REQUIRE(nglevel < ARRAY_SIZE(libngtcp2_to_ssl_level_lut));
+
+ level = libngtcp2_to_ssl_level_lut[nglevel];
+ ngconn = conn->inner;
+
+ ERR_set_mark();
+
+ if (data.length != 0 &&
+ SSL_provide_quic_data(tls, level, data.base, data.length) != 1)
+ {
+ CLEANUP(ISC_R_TLSERROR);
+ }
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ while (ngtcp2_conn_get_handshake_completed2(ngconn) == 0) {
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ while (ngtcp2_conn_get_handshake_completed(ngconn) == 0) {
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ r = SSL_do_handshake(tls);
+ if (r != 1) {
+ switch (SSL_get_error(tls, r)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+#ifdef LIBRESSL_VERSION_NUMBER
+ case SSL_ERROR_WANT_CLIENT_HELLO_CB:
+ CLEANUP(ISC_R_SUCCESS);
+#else
+ case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
+ case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
+ CLEANUP(ISC_R_SUCCESS);
+ case SSL_ERROR_EARLY_DATA_REJECTED:
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ INSIST(!ngtcp2_conn_is_server2(ngconn));
+#else
+ INSIST(!ngtcp2_conn_is_server(ngconn));
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ SSL_reset_early_data_reject(tls);
+ if (ngtcp2_conn_tls_early_data_rejected(
+ ngconn) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ continue;
+#endif /* LIBRESSL_VERSION_NUMBER */
+ default:
+ CLEANUP(ISC_R_TLSERROR);
+ }
+ }
+
+#ifndef LIBRESSL_VERSION_NUMBER
+ if (SSL_in_early_data(tls) == 1) {
+ CLEANUP(ISC_R_SUCCESS);
+ }
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+ ngtcp2_conn_tls_handshake_completed(ngconn);
+
+ break;
+ }
+
+ r = SSL_process_quic_post_handshake(tls);
+ if (r != 1) {
+ switch (SSL_get_error(tls, r)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ break;
+ default:
+ CLEANUP(ISC_R_TLSERROR);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ ERR_pop_to_mark();
+ return result;
+}
+
+isc_result_t
+isc_quic_tlsctx_client_configure(isc_tlsctx_t *tlsctx) {
+ REQUIRE(tlsctx != NULL);
+
+ /*
+ * Specified in RFC 9001, Section 4.2.
+ */
+ if (SSL_CTX_set_min_proto_version(tlsctx, TLS1_3_VERSION) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_min_proto_version", ISC_R_TLSERROR);
+ }
+
+ if (SSL_CTX_set_quic_method(tlsctx, &method) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_quic_method", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc_quic_tlsctx_server_configure(isc_tlsctx_t *tlsctx) {
+ REQUIRE(tlsctx != NULL);
+
+ SSL_CTX_set_alpn_select_cb(tlsctx, quic_alpn_select, NULL);
+
+ if (SSL_CTX_set_min_proto_version(tlsctx, TLS1_3_VERSION) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_min_proto_version", ISC_R_TLSERROR);
+ }
+
+ if (SSL_CTX_set_quic_method(tlsctx, &method) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_quic_method", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void
+isc__quic_initialize(void) {
+ quic_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ INSIST(quic_index != -1);
+}
+
+void
+isc__quic_shutdown(void) {
+ /* noop */
+}
diff --git a/lib/isc/quic/connection.c b/lib/isc/quic/connection.c
index 8dd4e9de95..935b26070c 100644
--- a/lib/isc/quic/connection.c
+++ b/lib/isc/quic/connection.c
@@ -14,21 +14,500 @@
#include <stdint.h>
#include <ngtcp2/ngtcp2.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
#include <isc/attributes.h>
+#include <isc/crypto.h>
#include <isc/magic.h>
#include <isc/mem.h>
+#include <isc/random.h>
+#include <isc/safe.h>
#include <isc/util.h>
#include "quic_p.h" /* IWYU pragma: keep */
+/**
+ * \brief
+ * Specified in RFC9001, Section 5.2.
+ */
+#define QUIC_INITIAL_SECRET_LENGTH 32
+
+#define QUIC_AES128_KEY_LENGTH 16
+
#define VALID_CONNECTION(c) ISC_MAGIC_VALID(c, conn_magic)
+#ifdef ISC_QUIC_STATE_CHECK
+#define CHECK_STATE(conn, ...) \
+ ({ \
+ __label__ out; \
+ isc__quic_conn_state_t _allowed[] = { __VA_ARGS__ }; \
+ for (size_t _i = 0; _i < ARRAY_SIZE(_allowed); _i++) { \
+ if ((conn)->state == _allowed[_i]) { \
+ goto out; \
+ } \
+ } \
+ UNREACHABLE(); \
+ out:; \
+ })
+#else /* ISC_QUIC_STATE_CHECK */
+#define CHECK_STATE(conn, ...)
+#endif /* ISC_QUIC_STATE_CHECK */
+
+struct isc__quic_stream {
+ int64_t id;
+ size_t last_acked_offset;
+ ISC_LIST(isc__quic_stream_data_t) ack_data;
+ ISC_LINK(isc__quic_stream_t) link;
+};
+
+struct isc__quic_stream_data {
+ bool finish;
+ bool zerortt;
+ int64_t stream_id;
+ size_t length;
+ ISC_LINK(isc__quic_stream_data_t) link;
+ uint8_t bytes[] ISC_ATTR_COUNTED_BY(length);
+};
+
constexpr uint32_t conn_magic = ISC_MAGIC('Q', 'U', 'I', 'c');
+/**
+ * \brief
+ * The confidentiality limit for AES-128-GCM and AES-256-GCM as used in TLS.
+ *
+ * The value is specified in RFC9001, Section 6.6.
+ */
+constexpr uint64_t aes_gcm_max_encryption = 8388608;
+
+/**
+ * \brief
+ * The integrity limit for AES-128-GCM and AES-256-GCM as used in TLS.
+ *
+ * The value is specified in RFC9001, Section 6.6.
+ */
+constexpr uint64_t aes_gcm_max_decryption_failure = 4503599627370496;
+
+/**
+ * \brief
+ * The confidentiality limit for ChaCha20-Poly1305 as used in TLS.
+ *
+ * The value is specified in RFC9001, Section 6.6.
+ */
+constexpr uint64_t chacha20poly1305_max_encryption = 4611686018427387904;
+
+/**
+ * \brief
+ * The integrity limit for ChaCha20-Poly1305 as used in TLS.
+ *
+ * The value is specified in RFC9001, Section 6.6.
+ */
+constexpr uint64_t chacha20poly1305_max_decryption_failure = 68719476736;
+
+static int
+client_initial_cb(ngtcp2_conn *ngconn, void *user_data);
+
+static int
+recv_client_initial_cb(ngtcp2_conn *ngconn, const ngtcp2_cid *dcid,
+ void *user_data);
+
+static int
+recv_crypto_data_cb(ngtcp2_conn *ngconn, ngtcp2_encryption_level nglevel,
+ uint64_t offset, const uint8_t *data, size_t len,
+ void *user_data);
+
+static int
+handshake_completed_cb(ngtcp2_conn *ngconn, void *user_data);
+
+static int
+encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *ngaead,
+ const ngtcp2_crypto_aead_ctx *ngctx, const uint8_t *plaintext,
+ size_t plaintextlen, const uint8_t *nonce, size_t noncelen,
+ const uint8_t *aad, size_t aadlen);
+
+static int
+decrypt_cb(uint8_t *out_ptr, const ngtcp2_crypto_aead *ngaead,
+ const ngtcp2_crypto_aead_ctx *ngctx, const uint8_t *ciphertext_ptr,
+ size_t ciphertext_len, const uint8_t *nonce_ptr, size_t nonce_len,
+ const uint8_t *aad_ptr, size_t aad_len);
+
+static int
+hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *nghp,
+ const ngtcp2_crypto_cipher_ctx *ngctx, const uint8_t *sample);
+
+static int
+recv_stream_data_cb(ngtcp2_conn *ngconn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data);
+
+static int
+acked_stream_data_offset_cb(ngtcp2_conn *ngconn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data);
+
+static int
+stream_open_cb(ngtcp2_conn *conn, int64_t stream_id, void *user_data);
+
+#ifndef NGTCP2_CALLBACKS_V5
+static int
+stream_close_cb(ngtcp2_conn *ngconn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data);
+#endif /* !NGTCP2_CALLBACKS_V5 */
+
+static int
+recv_retry_cb(ngtcp2_conn *ngconn, const ngtcp2_pkt_hd *header,
+ void *user_data);
+
+static void
+rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx);
+
+#ifndef NGTCP2_CALLBACKS_V3
+static int
+get_new_connection_id_cb(ngtcp2_conn *ngconn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data);
+#endif /* !NGTCP2_CALLBACKS_V3 */
+
+static int
+remove_connection_id_cb(ngtcp2_conn *ngconn, const ngtcp2_cid *ngcid,
+ void *user_data);
+
+static int
+update_key_cb(ngtcp2_conn *ngconn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen,
+ void *user_data);
+
+static void
+delete_crypto_aead_ctx_cb(ngtcp2_conn *ngconn, ngtcp2_crypto_aead_ctx *ngctx,
+ void *user_data);
+
+static void
+delete_crypto_cipher_ctx_cb(ngtcp2_conn *ngconn,
+ ngtcp2_crypto_cipher_ctx *ngctx, void *user_data);
+
+#ifndef NGTCP2_CALLBACKS_V3
+static int
+get_path_challenge_data_cb(ngtcp2_conn *ngconn, uint8_t *data, void *user_data);
+#endif /* NGTCP2_CALLBACKS_V3 */
+
+static int
+version_negotiation_cb(ngtcp2_conn *ngconn, uint32_t ngversion,
+ const ngtcp2_cid *client_dcid, void *user_data);
+
+#ifdef NGTCP2_CALLBACKS_V3
+static int
+get_new_connection_id2_cb(ngtcp2_conn *ngconn, ngtcp2_cid *ngcid,
+ ngtcp2_stateless_reset_token *token, size_t cidlen,
+ void *user_data);
+
+static int
+dcid_status2_cb(ngtcp2_conn *ngconn, ngtcp2_connection_id_status_type type,
+ uint64_t seq, const ngtcp2_cid *ngcid,
+ const ngtcp2_stateless_reset_token *ngtoken, void *user_data);
+
+static int
+get_path_challenge_data2_cb(ngtcp2_conn *ngconn,
+ ngtcp2_path_challenge_data *data, void *user_data);
+#endif /* NGTCP2_CALLBACKS_V3 */
+
+#ifdef NGTCP2_CALLBACKS_V4
+static int
+recv_stop_sending_cb(ngtcp2_conn *ngconn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data);
+#endif /* NGTCP2_CALLBACKS_V4 */
+
+#ifdef NGTCP2_CALLBACKS_V5
+static int
+stream_close2_cb(ngtcp2_conn *ngconn, uint32_t flags, int64_t stream_id,
+ uint64_t rx_app_error_code, uint64_t tx_app_error_code,
+ void *user_data, void *stream_user_data);
+#endif /* NGTCP2_CALLBACKS_V5 */
+
+static const ngtcp2_callbacks client_cb = {
+ .client_initial = client_initial_cb,
+ .recv_crypto_data = recv_crypto_data_cb,
+ .handshake_completed = handshake_completed_cb,
+ .encrypt = encrypt_cb,
+ .decrypt = decrypt_cb,
+ .hp_mask = hp_mask_cb,
+ .recv_stream_data = recv_stream_data_cb,
+ .acked_stream_data_offset = acked_stream_data_offset_cb,
+ .stream_open = stream_open_cb,
+#ifndef NGTCP2_CALLBACKS_V5
+ .stream_close = stream_close_cb,
+#endif /* NGTCP2_CALLBACKS_V5 */
+ .recv_retry = recv_retry_cb,
+ .rand = rand_cb,
+#ifndef NGTCP2_CALLBACKS_V3
+ .get_new_connection_id = get_new_connection_id_cb,
+#endif /* NGTCP2_CALLBACKS_V3 */
+ .remove_connection_id = remove_connection_id_cb,
+ .update_key = update_key_cb,
+ .delete_crypto_aead_ctx = delete_crypto_aead_ctx_cb,
+ .delete_crypto_cipher_ctx = delete_crypto_cipher_ctx_cb,
+#ifndef NGTCP2_CALLBACKS_V3
+ .get_path_challenge_data = get_path_challenge_data_cb,
+#endif /* NGTCP2_CALLBACKS_V3 */
+ .version_negotiation = version_negotiation_cb,
+#ifdef NGTCP2_CALLBACKS_V3
+ .get_new_connection_id2 = get_new_connection_id2_cb,
+ .get_path_challenge_data2 = get_path_challenge_data2_cb,
+#endif /* NGTCP2_CALLBACKS_V3 */
+#ifdef NGTCP2_CALLBACKS_V4
+ .recv_stop_sending = recv_stop_sending_cb,
+#endif /* NGTCP2_CALLBACKS_V4 */
+#ifdef NGTCP2_CALLBACKS_V5
+ .stream_close2 = stream_close2_cb,
+#endif /* NGTCP2_CALLBACKS_V5 */
+};
+
+static const ngtcp2_callbacks server_cb = {
+ .recv_client_initial = recv_client_initial_cb,
+ .recv_crypto_data = recv_crypto_data_cb,
+ .handshake_completed = handshake_completed_cb,
+ .encrypt = encrypt_cb,
+ .decrypt = decrypt_cb,
+ .hp_mask = hp_mask_cb,
+ .recv_stream_data = recv_stream_data_cb,
+ .acked_stream_data_offset = acked_stream_data_offset_cb,
+ .stream_open = stream_open_cb,
+#ifndef NGTCP2_CALLBACKS_V5
+ .stream_close = stream_close_cb,
+#endif /* !NGTCP2_CALLBACKS_V5 */
+ .rand = rand_cb,
+#ifndef NGTCP2_CALLBACKS_V3
+ .get_new_connection_id = get_new_connection_id_cb,
+#endif /* !NGTCP2_CALLBACKS_V3 */
+ .remove_connection_id = remove_connection_id_cb,
+ .update_key = update_key_cb,
+ .delete_crypto_aead_ctx = delete_crypto_aead_ctx_cb,
+ .delete_crypto_cipher_ctx = delete_crypto_cipher_ctx_cb,
+#ifndef NGTCP2_CALLBACKS_V3
+ .get_path_challenge_data = get_path_challenge_data_cb,
+#endif /* !NGTCP2_CALLBACKS_V3 */
+ .version_negotiation = version_negotiation_cb,
+#ifdef NGTCP2_CALLBACKS_V3
+ .get_new_connection_id2 = get_new_connection_id2_cb,
+ .dcid_status2 = dcid_status2_cb,
+ .get_path_challenge_data2 = get_path_challenge_data2_cb,
+#endif /* NGTCP2_CALLBACKS_V3 */
+#ifdef NGTCP2_CALLBACKS_V4
+ .recv_stop_sending = recv_stop_sending_cb,
+#endif /* NGTCP2_CALLBACKS_V4 */
+#ifdef NGTCP2_CALLBACKS_V5
+ .stream_close2 = stream_close2_cb,
+#endif /* NGTCP2_CALLBACKS_V5 */
+};
+
+static const ngtcp2_crypto_ctx initial_aes128gcm_sha256_ctx = {
+ .aead = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM,
+ .max_overhead = isc_crypto_aes128gcm_tag_length },
+ .md = { .native_handle = (void *)(uintptr_t)ISC_MD_SHA256 },
+ .hp = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128 },
+ .max_encryption = aes_gcm_max_encryption,
+ .max_decryption_failure = aes_gcm_max_decryption_failure,
+};
+
+static const ngtcp2_crypto_ctx initial_aes256_sha384_ctx = {
+ .aead = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM,
+ .max_overhead = isc_crypto_aes256gcm_tag_length, },
+ .md = { .native_handle = (void *)(uintptr_t)ISC_MD_SHA384 },
+ .hp = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256 },
+ .max_encryption = aes_gcm_max_encryption,
+ .max_decryption_failure = aes_gcm_max_decryption_failure,
+};
+
+static const ngtcp2_crypto_ctx initial_chacha20poly1305_sha256_ctx = {
+ .aead = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305,
+ .max_overhead = isc_crypto_chacha20poly1305_tag_length },
+ .md = { .native_handle = (void *)(uintptr_t)ISC_MD_SHA256 },
+ .hp = { .native_handle = (void *)(uintptr_t)
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20 },
+ .max_encryption = chacha20poly1305_max_encryption,
+ .max_decryption_failure = chacha20poly1305_max_decryption_failure,
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC client-side initial packet secret.
+ *
+ * The value is specified in RFC9001, Section 5.2.
+ */
+static const uint8_t client_label[] = {
+ 'c', 'l', 'i', 'e', 'n', 't', ' ', 'i', 'n',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC server-side initial packet secret.
+ *
+ * The value is specified in RFC9001, Section 5.2.
+ */
+static const uint8_t server_label[] = {
+ 's', 'e', 'r', 'v', 'e', 'r', ' ', 'i', 'n',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving QUIC v1 packet protection AEAD keys.
+ *
+ * The value is specified in RFC9001, Section 5.1.
+ */
+static const uint8_t key_label_v1[] = {
+ 'q', 'u', 'i', 'c', ' ', 'k', 'e', 'y',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v1 new write secret in a key
+ * update.
+ *
+ * The value is specified in RFC9001, Section 6.1.
+ */
+static const uint8_t key_update_label_v1[] = {
+ 'q', 'u', 'i', 'c', ' ', 'k', 'u',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v1 packet protection AEAD nonce.
+ *
+ * The value is specified in RFC9001, Section 5.1.
+ */
+static const uint8_t iv_label_v1[] = {
+ 'q', 'u', 'i', 'c', ' ', 'i', 'v',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v1 header protection key.
+ *
+ * The value is specified in RFC9001, Section 5.4.
+ */
+static const uint8_t hp_label_v1[] = {
+ 'q', 'u', 'i', 'c', ' ', 'h', 'p',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v2 packet protection keys.
+ *
+ * The value is specified in RFC9369, Section 3.3.2.
+ */
+static const uint8_t key_label_v2[] = {
+ 'q', 'u', 'i', 'c', 'v', '2', ' ', 'k', 'e', 'y',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v2 new write secret in a key
+ * update.
+ *
+ * The value is specified in RFC9369, Section 3.3.2.
+ */
+static const uint8_t key_update_label_v2[] = {
+ 'q', 'u', 'i', 'c', 'v', '2', ' ', 'k', 'u',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v2 packet protection AEAD nonce.
+ *
+ * The value is specified in RFC9369, Section 3.3.2.
+ */
+static const uint8_t iv_label_v2[] = {
+ 'q', 'u', 'i', 'c', 'v', '2', ' ', 'i', 'v',
+};
+
+/**
+ * \brief
+ * The HKDF label used when deriving the QUIC v1 header protection key.
+ *
+ * The value is specified in RFC9369, Section 3.3.2.
+ */
+static const uint8_t hp_label_v2[] = {
+ 'q', 'u', 'i', 'c', 'v', '2', ' ', 'h', 'p',
+};
+
+/**
+ * \brief
+ * Salt value of the HKDF-Extract when the deriving QUIC v1 initial secrets.
+ *
+ * The value is specified in RFC9001 Section 5.2.
+ */
+static const uint8_t salt_v1[] = {
+ 0x38, 0x76, 0x2C, 0xF7, 0xF5, 0x59, 0x34, 0xB3, 0x4D, 0x17,
+ 0x9A, 0xE6, 0xA4, 0xC8, 0x0C, 0xAD, 0xCC, 0xBB, 0x7F, 0x0A,
+};
+
+/**
+ * \brief
+ * Salt value of the HKDF-Extract when deriving the QUIC v2 initial secrets.
+ *
+ * The value is specified in RFC9369 Section 3.3.1.
+ */
+static const uint8_t salt_v2[] = {
+ 0x0D, 0xED, 0xE3, 0xDE, 0xF7, 0x00, 0xA6, 0xDB, 0x81, 0x93,
+ 0x81, 0xBE, 0x6E, 0x26, 0x9D, 0xCB, 0xF9, 0xBD, 0x2E, 0xD9,
+};
+
static void
destroy(isc_quic_conn_t *conn) {
- isc_mem_t *mctx = conn->mem.user_data;
+ isc_mem_t *mctx;
+ isc_tls_t *tls;
+
+ conn->magic = 0x00;
+ mctx = conn->mem.user_data;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ tls = ngtcp2_conn_get_tls_native_handle2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ tls = ngtcp2_conn_get_tls_native_handle(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ ISC_LIST_FOREACH(conn->streams, stream, link) {
+ ISC_LIST_FOREACH(stream->ack_data, data, link) {
+ isc_mem_put(
+ mctx, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+ isc_mem_put(mctx, stream, sizeof(*stream));
+ }
+
+ ISC_LIST_FOREACH(conn->outgoing_stream_data, data, link) {
+ isc_mem_put(mctx, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+#ifdef HAVE_OPENSSL_3
+ if (conn->local_transport_params != NULL) {
+ isc_mem_free(mctx, conn->local_transport_params);
+ }
+
+ ISC_LIST_FOREACH(conn->crypto_buffered_frames, frame, link) {
+ isc_mem_put(mctx, frame,
+ STRUCT_FLEX_SIZE(frame, data, frame->len));
+ }
+
+ ISC_LIST_FOREACH(conn->crypto_awaiting_frames, frame, link) {
+ isc_mem_put(mctx, frame,
+ STRUCT_FLEX_SIZE(frame, data, frame->len));
+ }
+#endif /* HAVE_OPENSSL_3 */
+
+ isc_tls_free(&tls);
+
+ ngtcp2_conn_del(conn->inner);
isc_quic_router_unref(conn->router);
@@ -36,6 +515,1381 @@ destroy(isc_quic_conn_t *conn) {
isc_mem_unref(mctx);
}
+static isc_result_t
+derive_initial_secret(isc_region_t secret, isc_region_t self_secret,
+ isc_region_t peer_secret, uint32_t version,
+ bool is_server, const ngtcp2_cid *cid) {
+ isc_region_t client_secret, server_secret;
+ isc_constregion_t salt;
+ isc_constregion_t initial_secret = {
+ .base = secret.base,
+ .length = secret.length,
+ };
+
+ switch (version) {
+ case NGTCP2_PROTO_VER_V1:
+ salt = (isc_constregion_t){
+ .base = salt_v1,
+ .length = sizeof(salt_v1),
+ };
+ break;
+ case NGTCP2_PROTO_VER_V2:
+ salt = (isc_constregion_t){
+ .base = salt_v2,
+ .length = sizeof(salt_v2),
+ };
+ break;
+ default:
+ return ISC_R_NOTIMPLEMENTED;
+ }
+
+ if (is_server) {
+ client_secret = peer_secret;
+ server_secret = self_secret;
+ } else {
+ client_secret = self_secret;
+ server_secret = peer_secret;
+ }
+
+ RETERR(isc_crypto_hkdf_extract(
+ secret, ISC_MD_SHA256,
+ (isc_constregion_t){ cid->datalen, cid->data }, salt));
+
+ RETERR(isc_crypto_hkdf_expand_label(
+ client_secret, ISC_MD_SHA256, initial_secret,
+ (isc_constregion_t){ sizeof(client_label), client_label }));
+
+ RETERR(isc_crypto_hkdf_expand_label(
+ server_secret, ISC_MD_SHA256, initial_secret,
+ (isc_constregion_t){ sizeof(server_label), server_label }));
+
+ return ISC_R_SUCCESS;
+}
+
+static isc_result_t
+derive_packet_keys(const uint32_t version, isc_md_type_t md,
+ isc_constregion_t secret, isc_region_t key, isc_region_t iv,
+ isc_region_t hp) {
+ isc_constregion_t key_label, iv_label, hp_label;
+ isc_result_t result;
+
+ switch (version) {
+ case NGTCP2_PROTO_VER_V1:
+ key_label = (isc_constregion_t){
+ .base = key_label_v1,
+ .length = sizeof(key_label_v1),
+ };
+ iv_label = (isc_constregion_t){
+ .base = iv_label_v1,
+ .length = sizeof(iv_label_v1),
+ };
+ hp_label = (isc_constregion_t){
+ .base = hp_label_v1,
+ .length = sizeof(hp_label_v1),
+ };
+ break;
+ case NGTCP2_PROTO_VER_V2:
+ key_label = (isc_constregion_t){
+ .base = key_label_v2,
+ .length = sizeof(key_label_v2),
+ };
+ iv_label = (isc_constregion_t){
+ .base = iv_label_v2,
+ .length = sizeof(iv_label_v2),
+ };
+ hp_label = (isc_constregion_t){
+ .base = hp_label_v2,
+ .length = sizeof(hp_label_v2),
+ };
+ break;
+ default:
+ return ISC_R_NOTIMPLEMENTED;
+ }
+
+ CHECK(isc_crypto_hkdf_expand_label(key, md, secret, key_label));
+ CHECK(isc_crypto_hkdf_expand_label(iv, md, secret, iv_label));
+ if (hp.base != NULL) {
+ CHECK(isc_crypto_hkdf_expand_label(hp, md, secret, hp_label));
+ }
+
+ return ISC_R_SUCCESS;
+
+cleanup:
+ isc_safe_memwipe(key.base, key.length);
+ isc_safe_memwipe(iv.base, iv.length);
+ if (hp.base != NULL) {
+ isc_safe_memwipe(hp.base, hp.length);
+ }
+ return result;
+}
+
+static isc_result_t
+derive_traffic_update(isc_region_t next_secret, uint32_t version,
+ isc_md_type_t md, isc_constregion_t secret) {
+ isc_constregion_t label;
+
+ switch (version) {
+ case NGTCP2_PROTO_VER_V1:
+ label = (isc_constregion_t){
+ .base = key_update_label_v1,
+ .length = sizeof(key_update_label_v1),
+ };
+ break;
+ case NGTCP2_PROTO_VER_V2:
+ label = (isc_constregion_t){
+ .base = key_update_label_v2,
+ .length = sizeof(key_update_label_v2),
+ };
+ break;
+ default:
+ return ISC_R_NOTIMPLEMENTED;
+ }
+
+ return isc_crypto_hkdf_expand_label(next_secret, md, secret, label);
+}
+
+/**
+ * Initial setup uses:
+ * \li AES-128 for the header protection
+ * \li AES-128-GCM for AEAD
+ */
+static isc_result_t
+setup_initial_key(ngtcp2_conn *ngconn, const ngtcp2_cid *dcid) {
+ uint8_t self_secret[QUIC_INITIAL_SECRET_LENGTH],
+ self_key[QUIC_AES128_KEY_LENGTH],
+ self_iv[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ self_hp[QUIC_AES128_KEY_LENGTH];
+ uint8_t peer_secret[QUIC_INITIAL_SECRET_LENGTH],
+ peer_key[QUIC_AES128_KEY_LENGTH],
+ peer_iv[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ peer_hp[QUIC_AES128_KEY_LENGTH];
+ ngtcp2_crypto_aead_ctx self_aead_ctx = { 0 }, peer_aead_ctx = { 0 };
+ ngtcp2_crypto_cipher_ctx self_hp_ctx = { 0 }, peer_hp_ctx = { 0 };
+ isc_crypto_quic_hp_protect_t *hp = NULL;
+ isc_crypto_aead_t *aead = NULL;
+ isc_result_t result;
+ uint32_t version;
+ bool is_server;
+
+ uint8_t initial_secret[QUIC_INITIAL_SECRET_LENGTH];
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ version = ngtcp2_conn_get_client_chosen_version2(ngconn);
+ is_server = ngtcp2_conn_is_server2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ version = ngtcp2_conn_get_client_chosen_version(ngconn);
+ is_server = ngtcp2_conn_is_server(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ ngtcp2_conn_set_initial_crypto_ctx(ngconn,
+ &initial_aes128gcm_sha256_ctx);
+
+ CHECK(derive_initial_secret(
+ (isc_region_t){ sizeof(initial_secret), initial_secret },
+ (isc_region_t){ sizeof(self_secret), self_secret },
+ (isc_region_t){ sizeof(peer_secret), peer_secret }, version,
+ is_server, dcid));
+
+ CHECK(derive_packet_keys(
+ version, ISC_MD_SHA256,
+ (isc_constregion_t){ sizeof(self_secret), self_secret },
+ (isc_region_t){ sizeof(self_key), self_key },
+ (isc_region_t){ sizeof(self_iv), self_iv },
+ (isc_region_t){ sizeof(self_hp), self_hp }));
+
+ CHECK(derive_packet_keys(
+ version, ISC_MD_SHA256,
+ (isc_constregion_t){ sizeof(peer_secret), peer_secret },
+ (isc_region_t){ sizeof(peer_key), peer_key },
+ (isc_region_t){ sizeof(peer_iv), peer_iv },
+ (isc_region_t){ sizeof(peer_hp), peer_hp }));
+
+ CHECK(isc_crypto_aead_create(
+ ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM,
+ (isc_constregion_t){ sizeof(self_key), self_key },
+ ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead));
+ self_aead_ctx.native_handle = MOVE_OWNERSHIP(aead);
+
+ CHECK(isc_crypto_aead_create(
+ ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM,
+ (isc_constregion_t){ sizeof(peer_key), peer_key },
+ ISC_CRYPTO_AEAD_DIRECTION_OPEN, &aead));
+ peer_aead_ctx.native_handle = MOVE_OWNERSHIP(aead);
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx,
+ (isc_constregion_t){ isc_crypto_aes128gcm_key_length, self_hp },
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128, &hp));
+ self_hp_ctx.native_handle = MOVE_OWNERSHIP(hp);
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx,
+ (isc_constregion_t){ isc_crypto_aes128gcm_key_length, peer_hp },
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128, &hp));
+ peer_hp_ctx.native_handle = MOVE_OWNERSHIP(hp);
+
+ INSIST(ngtcp2_conn_install_initial_key(
+ ngconn, &peer_aead_ctx, peer_iv, &peer_hp_ctx,
+ &self_aead_ctx, self_iv, &self_hp_ctx,
+ isc_crypto_aes128gcm_nonce_length) == 0);
+
+ return ISC_R_SUCCESS;
+
+cleanup:
+ if (hp != NULL) {
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (self_hp_ctx.native_handle != NULL) {
+ hp = self_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (peer_hp_ctx.native_handle != NULL) {
+ hp = peer_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (aead != NULL) {
+ isc_crypto_aead_destroy(&aead);
+ }
+ if (self_aead_ctx.native_handle != NULL) {
+ aead = self_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+ if (peer_aead_ctx.native_handle != NULL) {
+ aead = peer_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+ return result;
+}
+
+static int
+client_initial_cb(ngtcp2_conn *ngconn, void *user_data) {
+ const ngtcp2_cid *dcid;
+ isc_quic_conn_t *conn;
+ isc_result_t result;
+ isc_tls_t *tls;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ tls = ngtcp2_conn_get_tls_native_handle2(ngconn);
+ dcid = ngtcp2_conn_get_dcid2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ tls = ngtcp2_conn_get_tls_native_handle(ngconn);
+ dcid = ngtcp2_conn_get_dcid(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ conn = user_data;
+
+ result = setup_initial_key(ngconn, dcid);
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (isc__quic_set_local_transport_params(conn, tls) != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ result = isc__quic_do_tls(conn, tls, NGTCP2_ENCRYPTION_LEVEL_INITIAL,
+ (isc_constregion_t){ 0, NULL });
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+recv_client_initial_cb(ngtcp2_conn *ngconn, const ngtcp2_cid *dcid,
+ void *user_data ISC_ATTR_UNUSED) {
+ if (setup_initial_key(ngconn, dcid) != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+recv_crypto_data_cb(ngtcp2_conn *ngconn, ngtcp2_encryption_level nglevel,
+ uint64_t offset ISC_ATTR_UNUSED, const uint8_t *data,
+ size_t len, void *user_data) {
+ isc_quic_conn_t *conn = user_data;
+ isc_tls_t *tls;
+ int r;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ tls = ngtcp2_conn_get_tls_native_handle2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ tls = ngtcp2_conn_get_tls_native_handle(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ if (isc__quic_do_tls(conn, tls, nglevel,
+ (isc_constregion_t){ len, data }) != ISC_R_SUCCESS)
+ {
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ r = ngtcp2_conn_get_tls_error2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ r = ngtcp2_conn_get_tls_error(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ if (r == 0) {
+ return NGTCP2_ERR_CRYPTO;
+ }
+ return r;
+ }
+
+ return 0;
+}
+
+static int
+handshake_completed_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, void *user_data) {
+ isc_quic_conn_t *conn = user_data;
+
+ CHECK_STATE(conn, QUIC_CONN_STATE_HANDSHAKE);
+ conn->state = QUIC_CONN_STATE_CONNECTED;
+
+ if (conn->cb != NULL && conn->cb->handshake_completed != NULL) {
+ conn->cb->handshake_completed(conn->cbarg);
+ }
+
+ return 0;
+}
+
+static int
+encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *ngaead,
+ const ngtcp2_crypto_aead_ctx *ngctx, const uint8_t *plaintext,
+ size_t plaintextlen, const uint8_t *nonceptr, size_t noncelen,
+ const uint8_t *aad, size_t aadlen) {
+ size_t out_sealed_len;
+
+ if (isc_crypto_aead_seal(
+ ngctx->native_handle,
+ (isc_constregion_t){ noncelen, nonceptr },
+ (isc_constregion_t){ plaintextlen, plaintext },
+ (isc_region_t){ plaintextlen + ngaead->max_overhead, dest },
+ &out_sealed_len,
+ (isc_constregion_t){ aadlen, aad }) != ISC_R_SUCCESS)
+ {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+decrypt_cb(uint8_t *out_ptr, const ngtcp2_crypto_aead *ngaead,
+ const ngtcp2_crypto_aead_ctx *ngctx, const uint8_t *ciphertext_ptr,
+ size_t ciphertext_len, const uint8_t *nonce_ptr, size_t nonce_len,
+ const uint8_t *aad_ptr, size_t aad_len) {
+ size_t out_opened_len;
+
+ if (isc_crypto_aead_open(
+ ngctx->native_handle,
+ (isc_constregion_t){ nonce_len, nonce_ptr },
+ (isc_constregion_t){ ciphertext_len, ciphertext_ptr },
+ (isc_region_t){ ciphertext_len - ngaead->max_overhead,
+ out_ptr },
+ &out_opened_len,
+ (isc_constregion_t){ aad_len, aad_ptr }) != ISC_R_SUCCESS)
+ {
+ return NGTCP2_ERR_DECRYPT;
+ }
+
+ return 0;
+}
+
+static int
+hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *nghp ISC_ATTR_UNUSED,
+ const ngtcp2_crypto_cipher_ctx *ngctx, const uint8_t *sample) {
+ isc_crypto_quic_hp_protect_t *hp = ngctx->native_handle;
+
+ if (isc_crypto_quic_hp_protect_mask(hp, dest, sample) != ISC_R_SUCCESS)
+ {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+recv_stream_data_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, uint32_t flags,
+ int64_t stream_id, uint64_t offset ISC_ATTR_UNUSED,
+ const uint8_t *data, size_t datalen, void *user_data,
+ void *stream_user_data ISC_ATTR_UNUSED) {
+ isc_quic_conn_t *conn = user_data;
+ const isc_quic_stream_data_info_t info = {
+ .final = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) != 0x00,
+ .zerortt = (flags & NGTCP2_STREAM_DATA_FLAG_0RTT) != 0x00,
+ .stream_id = stream_id,
+ };
+
+ if (conn->cb != NULL && conn->cb->data_read != NULL) {
+ if (conn->cb->data_read(conn, conn->cbarg, info,
+ (isc_constregion_t){ datalen, data }) !=
+ ISC_R_SUCCESS)
+ {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ return 0;
+}
+
+static int
+acked_stream_data_offset_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ int64_t stream_id, uint64_t offset,
+ uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ isc__quic_stream_t *stream = stream_user_data;
+ isc_quic_conn_t *conn = user_data;
+ uint64_t last_acked_offset = stream->last_acked_offset;
+
+ INSIST(stream->id == stream_id);
+
+ ISC_LIST_FOREACH(stream->ack_data, data, link) {
+ if (last_acked_offset + data->length > offset + datalen) {
+ break;
+ }
+ last_acked_offset += data->length;
+ ISC_LIST_UNLINK(stream->ack_data, data, link);
+ isc_mem_put(conn->mem.user_data, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+ stream->last_acked_offset = offset;
+
+ return 0;
+}
+
+static int
+stream_open_cb(ngtcp2_conn *ngconn, int64_t stream_id, void *user_data) {
+ isc_quic_conn_t *conn = user_data;
+ isc__quic_stream_t *stream;
+ isc_result_t result;
+
+ if (conn->cb != NULL && conn->cb->stream_opened != NULL) {
+ result = conn->cb->stream_opened(conn, conn->cbarg, stream_id);
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ stream = isc_mem_get(conn->mem.user_data, sizeof(*stream));
+ *stream = (isc__quic_stream_t){
+ .id = stream_id,
+ .ack_data = ISC_LIST_INITIALIZER,
+ .link = ISC_LINK_INITIALIZER,
+ };
+
+ if (ngtcp2_conn_set_stream_user_data(ngconn, stream_id, stream) != 0) {
+ isc_mem_put(conn->mem.user_data, stream, sizeof(*stream));
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ ISC_LIST_APPEND(conn->streams, stream, link);
+
+ return 0;
+}
+
+#ifndef NGTCP2_CALLBACKS_V5
+static int
+stream_close_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, uint32_t flags,
+ int64_t stream_id, uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ isc_quic_application_error_kind_t kind;
+ isc__quic_stream_t *stream = stream_user_data;
+ isc_quic_conn_t *conn = user_data;
+ isc_result_t result;
+
+ ISC_LIST_FOREACH(stream->ack_data, data, link) {
+ ISC_LIST_DEQUEUE(stream->ack_data, data, link);
+ isc_mem_put(conn->mem.user_data, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+ if (conn->cb != NULL && conn->cb->stream_closed != NULL) {
+ if (flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET) {
+ kind = ISC_QUIC_APPLICATION_ERROR_UNKNOWN;
+ } else {
+ kind = ISC_QUIC_APPLICATION_ERROR_NONE;
+ }
+
+ result = conn->cb->stream_closed(
+ conn, conn->cbarg, stream_id, stream->data, kind,
+ app_error_code, app_error_code);
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ return 0;
+}
+#endif /* !NGTCP2_CALLBACKS_V5 */
+
+static int
+recv_retry_cb(ngtcp2_conn *ngconn, const ngtcp2_pkt_hd *header,
+ void *user_data ISC_ATTR_UNUSED) {
+ if (setup_initial_key(ngconn, &header->dcid) != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static void
+rand_cb(uint8_t *dest, size_t destlen,
+ const ngtcp2_rand_ctx *rand_ctx ISC_ATTR_UNUSED) {
+ isc_random_buf(dest, destlen);
+}
+
+#ifndef NGTCP2_CALLBACKS_V3
+static int
+get_new_connection_id_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, ngtcp2_cid *ngcid,
+ uint8_t *token, size_t cidlen, void *user_data) {
+ isc_quic_conn_t *conn = user_data;
+ uint8_t buffer[ISC_QUIC_CID_MAX_LENGTH];
+ isc_constregion_t cid = { cidlen, buffer };
+ isc_result_t result;
+
+ for (;;) {
+ isc_random_buf(buffer, sizeof(buffer));
+ result = isc_quic_router_add_cid(conn->router, cid, isc_tid(),
+ conn);
+ if (result == ISC_R_SUCCESS) {
+ isc_quic_router_add_stateless_reset(conn->router, token,
+ isc_tid(), conn);
+ ngcid->datalen = cidlen;
+ memmove(ngcid->data, buffer, cidlen);
+ return 0;
+ }
+ }
+
+ UNREACHABLE();
+}
+#endif /* NGTCP2_CALLBACKS_V3 */
+
+static int
+remove_connection_id_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ const ngtcp2_cid *ngcid, void *user_data) {
+ isc_constregion_t cid = { ngcid->datalen, ngcid->data };
+ isc_quic_conn_t *conn = user_data;
+
+ if (isc_quic_router_del_cid(conn->router, cid) != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+update_key_cb(ngtcp2_conn *ngconn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t len,
+ void *user_data ISC_ATTR_UNUSED) {
+ uint8_t rx_key[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH],
+ tx_key[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH];
+ const ngtcp2_crypto_ctx *ctx;
+ isc_crypto_aead_algorithm_t aead_algorithm;
+ isc_crypto_aead_t *aead = NULL;
+ isc_result_t result;
+ isc_md_type_t md;
+ uint32_t version;
+ size_t nonce_len, key_len;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ ctx = ngtcp2_conn_get_crypto_ctx2(ngconn);
+ version = ngtcp2_conn_get_client_chosen_version2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ ctx = ngtcp2_conn_get_crypto_ctx(ngconn);
+ version = ngtcp2_conn_get_client_chosen_version(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ md = (uintptr_t)(void *)ctx->md.native_handle;
+ INSIST(md < ISC_MD_MAX);
+
+ aead_algorithm = (uintptr_t)(void *)ctx->aead.native_handle;
+ switch (aead_algorithm) {
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM:
+ nonce_len = isc_crypto_aes128gcm_nonce_length;
+ key_len = isc_crypto_aes128gcm_key_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM:
+ nonce_len = isc_crypto_aes256gcm_nonce_length;
+ key_len = isc_crypto_aes256gcm_key_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305:
+ nonce_len = isc_crypto_chacha20poly1305_nonce_length;
+ key_len = isc_crypto_chacha20poly1305_key_length;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ CHECK(derive_traffic_update(
+ (isc_region_t){ len, rx_secret }, version, md,
+ (isc_constregion_t){ len, current_rx_secret }));
+
+ CHECK(derive_packet_keys(
+ version, md, (isc_constregion_t){ len, rx_secret },
+ (isc_region_t){ key_len, rx_key },
+ (isc_region_t){ nonce_len, rx_iv }, (isc_region_t){ 0, NULL }));
+
+ CHECK(isc_crypto_aead_create(aead_algorithm,
+ (isc_constregion_t){ key_len, rx_key },
+ ISC_CRYPTO_AEAD_DIRECTION_OPEN, &aead));
+ rx_aead_ctx->native_handle = MOVE_OWNERSHIP(aead);
+
+ CHECK(derive_traffic_update(
+ (isc_region_t){ len, tx_secret }, version, md,
+ (isc_constregion_t){ len, current_tx_secret }));
+
+ CHECK(derive_packet_keys(
+ version, md, (isc_constregion_t){ len, tx_secret },
+ (isc_region_t){ key_len, tx_key },
+ (isc_region_t){ nonce_len, tx_iv }, (isc_region_t){ 0, NULL }));
+
+ CHECK(isc_crypto_aead_create(aead_algorithm,
+ (isc_constregion_t){ key_len, tx_key },
+ ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead));
+ tx_aead_ctx->native_handle = MOVE_OWNERSHIP(aead);
+
+ return 0;
+
+cleanup:
+ if (aead != NULL) {
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ if (rx_aead_ctx->native_handle != NULL) {
+ aead = MOVE_OWNERSHIP(rx_aead_ctx->native_handle);
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+}
+
+static void
+delete_crypto_aead_ctx_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ ngtcp2_crypto_aead_ctx *ngctx,
+ void *user_data ISC_ATTR_UNUSED) {
+ isc_crypto_aead_t *aead = MOVE_OWNERSHIP(ngctx->native_handle);
+ isc_crypto_aead_destroy(&aead);
+}
+
+static void
+delete_crypto_cipher_ctx_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ ngtcp2_crypto_cipher_ctx *ngctx,
+ void *user_data ISC_ATTR_UNUSED) {
+ isc_crypto_quic_hp_protect_t *hp = MOVE_OWNERSHIP(ngctx->native_handle);
+ isc_crypto_quic_hp_protect_destroy(&hp);
+}
+
+#ifndef NGTCP2_CALLBACKS_V3
+static int
+get_path_challenge_data_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, uint8_t *data,
+ void *user_data ISC_ATTR_UNUSED) {
+ isc_random_buf(data, NGTCP2_PATH_CHALLENGE_DATALEN);
+ return 0;
+}
+#endif /* NGTCP2_CALLBACKS_V3 */
+
+static int
+version_negotiation_cb(ngtcp2_conn *ngconn, uint32_t ngversion,
+ const ngtcp2_cid *client_dcid,
+ void *user_data ISC_ATTR_UNUSED) {
+ uint8_t self_secret[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH],
+ self_key[QUIC_AES128_KEY_LENGTH],
+ self_iv[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ self_hp[QUIC_AES128_KEY_LENGTH];
+ uint8_t peer_secret[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH],
+ peer_key[QUIC_AES128_KEY_LENGTH],
+ peer_iv[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ peer_hp[QUIC_AES128_KEY_LENGTH];
+ uint8_t initial_secret[QUIC_INITIAL_SECRET_LENGTH];
+
+ ngtcp2_crypto_aead_ctx self_aead_ctx = { 0 }, peer_aead_ctx = { 0 };
+ ngtcp2_crypto_cipher_ctx self_hp_ctx = { 0 }, peer_hp_ctx = { 0 };
+ isc_crypto_quic_hp_protect_t *hp = NULL;
+ isc_crypto_aead_t *aead = NULL;
+ isc_result_t result;
+ bool is_server;
+ int r;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ is_server = ngtcp2_conn_is_server2(ngconn);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ is_server = ngtcp2_conn_is_server(ngconn);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ CHECK(derive_initial_secret(
+ (isc_region_t){ sizeof(initial_secret), initial_secret },
+ (isc_region_t){ sizeof(self_secret), self_secret },
+ (isc_region_t){ sizeof(peer_secret), peer_secret }, ngversion,
+ is_server, client_dcid));
+
+ CHECK(derive_packet_keys(
+ ngversion, ISC_MD_SHA256,
+ (isc_constregion_t){ sizeof(self_secret), self_secret },
+ (isc_region_t){ sizeof(self_key), self_key },
+ (isc_region_t){ sizeof(self_iv), self_iv },
+ (isc_region_t){ sizeof(self_hp), self_hp }));
+
+ CHECK(isc_crypto_aead_create(
+ ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM,
+ (isc_constregion_t){ sizeof(self_key), self_key },
+ ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead));
+ self_aead_ctx.native_handle = MOVE_OWNERSHIP(aead);
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, (isc_constregion_t){ sizeof(self_hp), self_hp },
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128, &hp));
+ self_hp_ctx.native_handle = MOVE_OWNERSHIP(hp);
+
+ CHECK(derive_packet_keys(
+ ngversion, ISC_MD_SHA256,
+ (isc_constregion_t){ sizeof(peer_secret), peer_secret },
+ (isc_region_t){ sizeof(peer_key), peer_key },
+ (isc_region_t){ sizeof(peer_iv), peer_iv },
+ (isc_region_t){ sizeof(peer_hp), peer_hp }));
+
+ CHECK(isc_crypto_aead_create(
+ ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM,
+ (isc_constregion_t){ sizeof(peer_key), peer_key },
+ ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead));
+ peer_aead_ctx.native_handle = MOVE_OWNERSHIP(aead);
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, (isc_constregion_t){ sizeof(peer_hp), peer_hp },
+ ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128, &hp));
+ peer_hp_ctx.native_handle = MOVE_OWNERSHIP(hp);
+
+ r = ngtcp2_conn_install_vneg_initial_key(
+ ngconn, ngversion, &peer_aead_ctx, peer_iv, &peer_hp_ctx,
+ &self_aead_ctx, self_iv, &self_hp_ctx,
+ isc_crypto_aes128gcm_nonce_length);
+ if (r != 0) {
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+ if (hp != NULL) {
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (self_hp_ctx.native_handle != NULL) {
+ hp = self_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (peer_hp_ctx.native_handle != NULL) {
+ hp = peer_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+ if (aead != NULL) {
+ isc_crypto_aead_destroy(&aead);
+ }
+ if (self_aead_ctx.native_handle != NULL) {
+ aead = self_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+ if (peer_aead_ctx.native_handle != NULL) {
+ aead = peer_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+}
+
+#ifdef NGTCP2_CALLBACKS_V3
+static int
+get_new_connection_id2_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ ngtcp2_cid *ngcid,
+ ngtcp2_stateless_reset_token *token, size_t cidlen,
+ void *user_data) {
+ isc_quic_conn_t *conn = user_data;
+ uint8_t buffer[ISC_QUIC_CID_MAX_LENGTH];
+ isc_constregion_t cid = { cidlen, buffer };
+ isc_result_t result;
+
+ for (;;) {
+ isc_random_buf(buffer, sizeof(buffer));
+ result = isc_quic_router_add_cid(conn->router, cid, isc_tid(),
+ conn);
+ if (result == ISC_R_SUCCESS) {
+ isc_quic_router_add_stateless_reset(
+ conn->router, token->data, isc_tid(), conn);
+ ngcid->datalen = cidlen;
+ memmove(ngcid->data, buffer, cidlen);
+ return 0;
+ }
+ }
+
+ UNREACHABLE();
+}
+
+static int
+dcid_status2_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ ngtcp2_connection_id_status_type type,
+ uint64_t seq ISC_ATTR_UNUSED, const ngtcp2_cid *ngcid,
+ const ngtcp2_stateless_reset_token *ngtoken, void *user_data) {
+ isc_constregion_t cid = { ngcid->datalen, ngcid->data };
+ isc_quic_conn_t *conn = user_data;
+ isc_result_t result;
+ isc_tid_t tid = isc_tid();
+
+ switch (type) {
+ case NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE:
+ result = isc_quic_router_add_cid(conn->router, cid, tid, conn);
+ if (result == ISC_R_SUCCESS && ngtoken != NULL) {
+ result = isc_quic_router_add_stateless_reset(
+ conn->router, ngtoken->data, tid, conn);
+ if (result != ISC_R_SUCCESS) {
+ (void)isc_quic_router_del_cid(conn->router,
+ cid);
+ }
+ }
+ break;
+ case NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE:
+ result = isc_quic_router_del_cid(conn->router, cid);
+ if (result == ISC_R_SUCCESS && ngtoken != NULL) {
+ result = isc_quic_router_del_stateless_reset(
+ conn->router, ngtoken->data);
+ /* TODO(aydin) do we need to re-add the CID? */
+ }
+ break;
+ default:
+ /*
+ * Other values might be added in the future so it is best to
+ * not crash.
+ *
+ * https://nghttp2.org/ngtcp2/types.html#c.ngtcp2_connection_id_status2
+ */
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int
+get_path_challenge_data2_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ ngtcp2_path_challenge_data *data,
+ void *user_data ISC_ATTR_UNUSED) {
+ isc_random_buf(data->data, NGTCP2_PATH_CHALLENGE_DATALEN);
+ return 0;
+}
+
+#endif /* NGTCP2_CALLBACKS_V3 */
+
+#ifdef NGTCP2_CALLBACKS_V4
+static int
+recv_stop_sending_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED,
+ int64_t stream_id ISC_ATTR_UNUSED,
+ uint64_t app_error_code ISC_ATTR_UNUSED, void *user_data,
+ void *stream_user_data) {
+ isc__quic_stream_t *stream = stream_user_data;
+ isc_quic_conn_t *conn = user_data;
+ isc_mem_t *mctx = conn->mem.user_data;
+
+ ISC_LIST_UNLINK(conn->streams, stream, link);
+ ISC_LIST_FOREACH(stream->ack_data, data, link) {
+ ISC_LIST_DEQUEUE(stream->ack_data, data, link);
+ isc_mem_put(mctx, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+ isc_mem_put(mctx, stream, sizeof(*stream));
+
+ return 0;
+}
+#endif /* NGTCP2_CALLBACKS_V4 */
+
+#ifdef NGTCP2_CALLBACKS_V5
+static int
+stream_close2_cb(ngtcp2_conn *ngconn ISC_ATTR_UNUSED, uint32_t flags,
+ int64_t stream_id, uint64_t rx_app_error_code,
+ uint64_t tx_app_error_code, void *user_data,
+ void *stream_user_data) {
+ isc_quic_application_error_kind_t kind;
+ isc__quic_stream_t *stream = stream_user_data;
+ isc_quic_conn_t *conn = user_data;
+ isc_result_t result;
+ bool rx, tx;
+
+ ISC_LIST_FOREACH(stream->ack_data, data, link) {
+ ISC_LIST_DEQUEUE(stream->ack_data, data, link);
+ isc_mem_put(conn->mem.user_data, data,
+ STRUCT_FLEX_SIZE(data, bytes, data->length));
+ }
+
+ if (conn->cb != NULL && conn->cb->stream_closed != NULL) {
+ rx = flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET;
+ tx = flags & NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET;
+
+ if (rx && tx) {
+ kind = ISC_QUIC_APPLICATION_ERROR_RX_AND_TX;
+ } else if (rx) {
+ kind = ISC_QUIC_APPLICATION_ERROR_RX_ONLY;
+ } else if (tx) {
+ kind = ISC_QUIC_APPLICATION_ERROR_TX_ONLY;
+ } else {
+ kind = ISC_QUIC_APPLICATION_ERROR_NONE;
+ }
+
+ result = conn->cb->stream_closed(conn, conn->cbarg, stream_id,
+ kind, rx_app_error_code,
+ tx_app_error_code);
+ if (result != ISC_R_SUCCESS) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+
+ return 0;
+}
+#endif /* NGTCP2_CALLBACKS_V5 */
+
+isc_result_t
+isc__quic_setup_read_key(isc_quic_conn_t *conn, bool is_server,
+ ngtcp2_encryption_level nglevel,
+ isc_constregion_t secret) {
+ isc_crypto_quic_hp_protect_algorithm_t hp_algorithm;
+ isc_crypto_quic_hp_protect_t *hp = NULL;
+ isc_crypto_aead_algorithm_t aead_algorithm;
+ ngtcp2_crypto_cipher_ctx ng_hp_ctx = { 0 };
+ const ngtcp2_crypto_ctx *ngctx = NULL;
+ ngtcp2_crypto_aead_ctx ng_aead_ctx = { 0 };
+ isc_crypto_aead_t *aead = NULL;
+ const SSL_CIPHER *cipher;
+ isc_md_type_t md;
+ isc_result_t result;
+ isc_tls_t *tls;
+ uint32_t version;
+ size_t key_len, nonce_len;
+ uint8_t key_buffer[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH],
+ nonce_buffer[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ hp_buffer[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH];
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ tls = ngtcp2_conn_get_tls_native_handle2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ tls = ngtcp2_conn_get_tls_native_handle(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ switch (nglevel) {
+ case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+ if (!is_server) {
+ return ISC_R_SUCCESS;
+ }
+
+ cipher = SSL_get_current_cipher(tls);
+ if (cipher == NULL) {
+ return ISC_R_FAILURE;
+ }
+
+ switch (SSL_CIPHER_get_id(cipher)) {
+ case TLS1_3_CK_AES_128_GCM_SHA256:
+ ngctx = &initial_aes128gcm_sha256_ctx;
+ break;
+ case TLS1_3_CK_AES_256_GCM_SHA384:
+ ngctx = &initial_aes256_sha384_ctx;
+ break;
+ case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+ ngctx = &initial_chacha20poly1305_sha256_ctx;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ngtcp2_conn_set_0rtt_crypto_ctx(conn->inner, ngctx);
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ ngctx = ngtcp2_conn_get_0rtt_crypto_ctx2(conn->inner);
+ version = ngtcp2_conn_get_client_chosen_version2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ ngctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn->inner);
+ version = ngtcp2_conn_get_client_chosen_version(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ version = ngtcp2_conn_get_negotiated_version2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ version = ngtcp2_conn_get_negotiated_version(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ if (is_server && (version == 0)) {
+ RETERR(isc__quic_set_remote_transport_params(conn,
+ tls));
+ }
+ FALLTHROUGH;
+ case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ version = ngtcp2_conn_get_negotiated_version2(conn->inner);
+ ngctx = ngtcp2_conn_get_crypto_ctx2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ version = ngtcp2_conn_get_negotiated_version(conn->inner);
+ ngctx = ngtcp2_conn_get_crypto_ctx(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ if (ngctx->aead.native_handle == NULL) {
+ cipher = SSL_get_current_cipher(tls);
+ if (cipher == NULL) {
+ CLEANUP(ISC_R_FAILURE);
+ }
+
+ switch (SSL_CIPHER_get_id(cipher)) {
+ case TLS1_3_CK_AES_128_GCM_SHA256:
+ ngctx = &initial_aes128gcm_sha256_ctx;
+ break;
+ case TLS1_3_CK_AES_256_GCM_SHA384:
+ ngctx = &initial_aes256_sha384_ctx;
+ break;
+ case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+ ngctx = &initial_chacha20poly1305_sha256_ctx;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ngtcp2_conn_set_crypto_ctx(conn->inner, ngctx);
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ ngctx = ngtcp2_conn_get_crypto_ctx2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ ngctx = ngtcp2_conn_get_crypto_ctx(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ aead_algorithm = (uintptr_t)(void *)ngctx->aead.native_handle;
+ switch (aead_algorithm) {
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128;
+ key_len = isc_crypto_aes128gcm_key_length;
+ nonce_len = isc_crypto_aes128gcm_nonce_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256;
+ key_len = isc_crypto_aes256gcm_key_length;
+ nonce_len = isc_crypto_aes256gcm_nonce_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20;
+ key_len = isc_crypto_chacha20poly1305_key_length;
+ nonce_len = isc_crypto_chacha20poly1305_nonce_length;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ md = (uintptr_t)(void *)ngctx->md.native_handle;
+ hp_algorithm = (uintptr_t)(void *)ngctx->hp.native_handle;
+
+ CHECK(derive_packet_keys(version, md, secret,
+ (isc_region_t){ key_len, key_buffer },
+ (isc_region_t){ nonce_len, nonce_buffer },
+ (isc_region_t){ key_len, hp_buffer }));
+
+ CHECK(isc_crypto_aead_create(aead_algorithm,
+ (isc_constregion_t){ key_len, key_buffer },
+ ISC_CRYPTO_AEAD_DIRECTION_OPEN, &aead));
+ ng_aead_ctx = (ngtcp2_crypto_aead_ctx){
+ .native_handle = MOVE_OWNERSHIP(aead),
+ };
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, (isc_constregion_t){ key_len, hp_buffer },
+ hp_algorithm, &hp));
+ ng_hp_ctx = (ngtcp2_crypto_cipher_ctx){
+ .native_handle = MOVE_OWNERSHIP(hp),
+ };
+
+ switch (nglevel) {
+ case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+ if (ngtcp2_conn_install_0rtt_key(conn->inner, &ng_aead_ctx,
+ nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+ if (ngtcp2_conn_install_rx_handshake_key(
+ conn->inner, &ng_aead_ctx, nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+ if (!is_server) {
+ CHECK(isc__quic_set_remote_transport_params(conn, tls));
+ }
+
+ if (ngtcp2_conn_install_rx_key(conn->inner, secret.base,
+ secret.length, &ng_aead_ctx,
+ nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_safe_memwipe(key_buffer, sizeof(key_buffer));
+ isc_safe_memwipe(nonce_buffer, sizeof(nonce_buffer));
+ isc_safe_memwipe(hp_buffer, sizeof(hp_buffer));
+ return ISC_R_SUCCESS;
+
+cleanup:
+ if (aead != NULL) {
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ if (ng_aead_ctx.native_handle != NULL) {
+ aead = ng_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ if (hp != NULL) {
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+
+ if (ng_hp_ctx.native_handle != NULL) {
+ hp = ng_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+
+ isc_safe_memwipe(key_buffer, sizeof(key_buffer));
+ isc_safe_memwipe(nonce_buffer, sizeof(nonce_buffer));
+ isc_safe_memwipe(hp_buffer, sizeof(hp_buffer));
+
+ return result;
+}
+
+isc_result_t
+isc__quic_setup_write_key(isc_quic_conn_t *conn, bool is_server,
+ ngtcp2_encryption_level nglevel,
+ isc_constregion_t secret) {
+ isc_crypto_quic_hp_protect_algorithm_t hp_algorithm;
+ isc_crypto_aead_algorithm_t aead_algorithm;
+ isc_crypto_quic_hp_protect_t *hp = NULL;
+ ngtcp2_crypto_cipher_ctx ng_hp_ctx = { 0 };
+ const ngtcp2_crypto_ctx *ngctx;
+ ngtcp2_crypto_aead_ctx ng_aead_ctx = { 0 };
+ isc_crypto_aead_t *aead = NULL;
+ const SSL_CIPHER *cipher;
+ isc_md_type_t md;
+ isc_result_t result;
+ isc_tls_t *tls;
+ uint32_t version;
+ size_t key_len, nonce_len;
+ uint8_t key_buffer[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH],
+ nonce_buffer[ISC_CRYPTO_AEAD_MAX_NONCE_LENGTH],
+ hp_buffer[ISC_CRYPTO_AEAD_MAX_KEY_LENGTH];
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ tls = ngtcp2_conn_get_tls_native_handle2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ tls = ngtcp2_conn_get_tls_native_handle(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ switch (nglevel) {
+ case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+ if (!is_server) {
+ return ISC_R_SUCCESS;
+ }
+
+ cipher = SSL_get_current_cipher(tls);
+ if (cipher == NULL) {
+ return ISC_R_FAILURE;
+ }
+
+ switch (SSL_CIPHER_get_id(cipher)) {
+ case TLS1_3_CK_AES_128_GCM_SHA256:
+ ngctx = &initial_aes128gcm_sha256_ctx;
+ break;
+ case TLS1_3_CK_AES_256_GCM_SHA384:
+ ngctx = &initial_aes256_sha384_ctx;
+ break;
+ case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+ ngctx = &initial_chacha20poly1305_sha256_ctx;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ngtcp2_conn_set_0rtt_crypto_ctx(conn->inner, ngctx);
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ ngctx = ngtcp2_conn_get_0rtt_crypto_ctx2(conn->inner);
+ version = ngtcp2_conn_get_client_chosen_version2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ ngctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn->inner);
+ version = ngtcp2_conn_get_client_chosen_version(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ version = ngtcp2_conn_get_negotiated_version2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ version = ngtcp2_conn_get_negotiated_version(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ if (is_server && (version == 0)) {
+ RETERR(isc__quic_set_remote_transport_params(conn,
+ tls));
+ }
+ FALLTHROUGH;
+ case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ version = ngtcp2_conn_get_negotiated_version2(conn->inner);
+ ngctx = ngtcp2_conn_get_crypto_ctx2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ version = ngtcp2_conn_get_negotiated_version(conn->inner);
+ ngctx = ngtcp2_conn_get_crypto_ctx(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ if (ngctx->aead.native_handle == NULL) {
+ cipher = SSL_get_current_cipher(tls);
+ if (cipher == NULL) {
+ CLEANUP(ISC_R_FAILURE);
+ }
+
+ switch (SSL_CIPHER_get_id(cipher)) {
+ case TLS1_3_CK_AES_128_GCM_SHA256:
+ ngctx = &initial_aes128gcm_sha256_ctx;
+ break;
+ case TLS1_3_CK_AES_256_GCM_SHA384:
+ ngctx = &initial_aes256_sha384_ctx;
+ break;
+ case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+ ngctx = &initial_chacha20poly1305_sha256_ctx;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ngtcp2_conn_set_crypto_ctx(conn->inner, ngctx);
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ ngctx = ngtcp2_conn_get_crypto_ctx2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ ngctx = ngtcp2_conn_get_crypto_ctx(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ aead_algorithm = (uintptr_t)(void *)ngctx->aead.native_handle;
+ switch (aead_algorithm) {
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES128;
+ key_len = isc_crypto_aes128gcm_key_length;
+ nonce_len = isc_crypto_aes128gcm_nonce_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_AES256;
+ key_len = isc_crypto_aes256gcm_key_length;
+ nonce_len = isc_crypto_aes256gcm_nonce_length;
+ break;
+ case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305:
+ hp_algorithm = ISC_CRYPTO_QUIC_HP_PROTECT_ALGORITHM_CHACHA20;
+ key_len = isc_crypto_chacha20poly1305_key_length;
+ nonce_len = isc_crypto_chacha20poly1305_nonce_length;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ md = (uintptr_t)(void *)ngctx->md.native_handle;
+ hp_algorithm = (uintptr_t)(void *)ngctx->hp.native_handle;
+
+ CHECK(derive_packet_keys(version, md, secret,
+ (isc_region_t){ key_len, key_buffer },
+ (isc_region_t){ nonce_len, nonce_buffer },
+ (isc_region_t){ key_len, hp_buffer }));
+
+ CHECK(isc_crypto_aead_create(aead_algorithm,
+ (isc_constregion_t){ key_len, key_buffer },
+ ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead));
+ ng_aead_ctx = (ngtcp2_crypto_aead_ctx){
+ .native_handle = MOVE_OWNERSHIP(aead),
+ };
+
+ CHECK(isc_crypto_quic_hp_protect_create(
+ isc_g_mctx, (isc_constregion_t){ key_len, hp_buffer },
+ hp_algorithm, &hp));
+ ng_hp_ctx = (ngtcp2_crypto_cipher_ctx){
+ .native_handle = MOVE_OWNERSHIP(hp),
+ };
+
+ switch (nglevel) {
+ case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+ if (ngtcp2_conn_install_0rtt_key(conn->inner, &ng_aead_ctx,
+ nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+ if (ngtcp2_conn_install_tx_handshake_key(
+ conn->inner, &ng_aead_ctx, nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+
+ if (is_server) {
+ CHECK(isc__quic_set_local_transport_params(conn, tls));
+ }
+ break;
+ case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+ if (ngtcp2_conn_install_tx_key(conn->inner, secret.base,
+ secret.length, &ng_aead_ctx,
+ nonce_buffer, nonce_len,
+ &ng_hp_ctx) != 0)
+ {
+ CLEANUP(ISC_R_FAILURE);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_safe_memwipe(key_buffer, sizeof(key_buffer));
+ isc_safe_memwipe(nonce_buffer, sizeof(nonce_buffer));
+ isc_safe_memwipe(hp_buffer, sizeof(hp_buffer));
+ return ISC_R_SUCCESS;
+
+cleanup:
+ if (aead != NULL) {
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ if (ng_aead_ctx.native_handle != NULL) {
+ aead = ng_aead_ctx.native_handle;
+ isc_crypto_aead_destroy(&aead);
+ }
+
+ if (hp != NULL) {
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+
+ if (ng_hp_ctx.native_handle != NULL) {
+ hp = ng_hp_ctx.native_handle;
+ isc_crypto_quic_hp_protect_destroy(&hp);
+ }
+
+ isc_safe_memwipe(key_buffer, sizeof(key_buffer));
+ isc_safe_memwipe(nonce_buffer, sizeof(nonce_buffer));
+ isc_safe_memwipe(hp_buffer, sizeof(hp_buffer));
+
+ return result;
+}
+
ISC_REFCOUNT_IMPL(isc_quic_conn, destroy);
isc_result_t
diff --git a/lib/isc/quic/meson.build b/lib/isc/quic/meson.build
index 2223bd501f..65ad1820f6 100644
--- a/lib/isc/quic/meson.build
+++ b/lib/isc/quic/meson.build
@@ -16,3 +16,9 @@ isc_srcset.add(
'router.c',
),
)
+
+isc_srcset.add(
+ when: 'HAVE_OPENSSL_3',
+ if_true: files('ossl.c'),
+ if_false: files('boringlike.c'),
+)
diff --git a/lib/isc/quic/ossl.c b/lib/isc/quic/ossl.c
new file mode 100644
index 0000000000..e95fb2b2dc
--- /dev/null
+++ b/lib/isc/quic/ossl.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdint.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <isc/crypto.h>
+#include <isc/ossl_wrap.h>
+
+#include "quic_p.h" /* IWYU pragma: keep */
+
+/**
+ * \brief
+ * Lookup table for translating a OpenSSL encryption level to its ngtcp2
+ * counterpart.
+ */
+static ngtcp2_encryption_level libssl_to_ngtcp2_level_lut[] = {
+ /* clang-format off */
+ [OSSL_RECORD_PROTECTION_LEVEL_NONE] = NGTCP2_ENCRYPTION_LEVEL_INITIAL,
+ [OSSL_RECORD_PROTECTION_LEVEL_EARLY] = NGTCP2_ENCRYPTION_LEVEL_0RTT,
+ [OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE] = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE,
+ [OSSL_RECORD_PROTECTION_LEVEL_APPLICATION] = NGTCP2_ENCRYPTION_LEVEL_1RTT,
+ /* clang-format on */
+};
+
+/**
+ * \brief
+ * `SSL` object index to point back to its `isc_quic_conn_t`
+ *
+ * This is only really used inside the ALPN selection callback since the
+ * callback argument is global for all connections created by the same
+ * `SSL_CTX`.
+ */
+static int quic_index = -1;
+
+/*
+ * https://docs.openssl.org/master/man3/SSL_set_quic_tls_cbs/
+ */
+
+static int
+crypto_send_cb(isc_tls_t *tls, const uint8_t *buf, size_t len, size_t *consumed,
+ void *arg);
+
+static int
+crypto_recv_rcd_cb(isc_tls_t *tls, const unsigned char **buf, size_t *read,
+ void *arg);
+
+static int
+crypto_release_rcd_cb(isc_tls_t *tls, size_t released, void *arg);
+
+static int
+yield_secret_cb(isc_tls_t *tls, uint32_t prot_level, int direction,
+ const unsigned char *secret, size_t len, void *arg);
+
+static int
+got_transport_params_cb(isc_tls_t *tls, const uint8_t *params, size_t len,
+ void *arg);
+
+static int
+alert_cb(isc_tls_t *tls, uint8_t code, void *arg);
+
+static const OSSL_DISPATCH dispatch[] = {
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND,
+ .function = (OSSL_FUNC)crypto_send_cb,
+ },
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET,
+ .function = (OSSL_FUNC)yield_secret_cb,
+ },
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD,
+ .function = (OSSL_FUNC)crypto_recv_rcd_cb,
+ },
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD,
+ .function = (OSSL_FUNC)crypto_release_rcd_cb,
+ },
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS,
+ .function = (OSSL_FUNC)got_transport_params_cb,
+ },
+ {
+ .function_id = OSSL_FUNC_SSL_QUIC_TLS_ALERT,
+ .function = (OSSL_FUNC)alert_cb,
+ },
+ {
+ .function_id = 0,
+ .function = NULL,
+ },
+};
+
+isc_result_t
+isc__quic_setup_tls(isc_tls_t *tls, isc_quic_conn_t *conn) {
+ SSL_set_ex_data(tls, quic_index, conn);
+
+ /*
+ * There is no SSL_CTX_set_quic_tls_cbs
+ */
+ if (SSL_set_quic_tls_cbs(tls, dispatch, conn) != 1) {
+ return ISC_R_TLSERROR;
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+static int
+crypto_send_cb(isc_tls_t *tls ISC_ATTR_UNUSED, const uint8_t *buf, size_t len,
+ size_t *consumed, void *arg) {
+ isc_quic_conn_t *conn;
+ int r;
+
+ conn = arg;
+ r = ngtcp2_conn_submit_crypto_data(conn->inner, conn->level, buf, len);
+ if (r != 0) {
+ ngtcp2_conn_set_tls_error(conn->inner, r);
+ return 0;
+ }
+
+ *consumed = len;
+
+ return 1;
+}
+
+static int
+crypto_recv_rcd_cb(isc_tls_t *tls ISC_ATTR_UNUSED, const unsigned char **bufp,
+ size_t *readp, void *arg) {
+ isc__quic_crypto_frame_data_t *frame;
+ isc_quic_conn_t *conn;
+
+ conn = arg;
+ if (conn == NULL) {
+ *bufp = NULL;
+ *readp = 0;
+ return 1;
+ }
+
+ frame = ISC_LIST_HEAD(conn->crypto_buffered_frames);
+ if (frame == NULL) {
+ *bufp = NULL;
+ *readp = 0;
+ return 1;
+ }
+ ISC_LIST_DEQUEUE(conn->crypto_buffered_frames, frame, link);
+ ISC_LIST_ENQUEUE(conn->crypto_awaiting_frames, frame, link);
+
+ *bufp = frame->data;
+ *readp = frame->len;
+
+ return 1;
+}
+
+static int
+crypto_release_rcd_cb(isc_tls_t *tls ISC_ATTR_UNUSED, size_t released,
+ void *arg) {
+ isc__quic_crypto_frame_data_t *frame;
+ isc_quic_conn_t *conn;
+
+ conn = arg;
+ INSIST(conn != NULL);
+
+ if (released == 0) {
+ return 1;
+ }
+
+ frame = ISC_LIST_HEAD(conn->crypto_awaiting_frames);
+ INSIST(frame != NULL);
+ ISC_LIST_DEQUEUE(conn->crypto_awaiting_frames, frame, link);
+ isc_mem_put(conn->mem.user_data, frame,
+ STRUCT_FLEX_SIZE(frame, data, frame->len));
+
+ return 1;
+}
+
+static int
+yield_secret_cb(isc_tls_t *tls ISC_ATTR_UNUSED, uint32_t level, int direction,
+ const unsigned char *secret, size_t len, void *arg) {
+ ngtcp2_encryption_level nglevel;
+ isc_quic_conn_t *conn;
+ isc_result_t result;
+ bool is_server;
+
+ nglevel = libssl_to_ngtcp2_level_lut[level];
+ conn = arg;
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ is_server = ngtcp2_conn_is_server2(conn->inner);
+#else /* NGTCP2_VERSION_NUM >= 0x011700 */
+ is_server = ngtcp2_conn_is_server(conn->inner);
+#endif /* NGTCP2_VERSION_NUM >= 0x011700 */
+
+ switch (direction) {
+ case 0:
+ result = isc__quic_setup_read_key(
+ conn, is_server, nglevel,
+ (isc_constregion_t){ len, secret });
+ if (result != ISC_R_SUCCESS) {
+ return 0;
+ }
+ break;
+ case 1:
+ result = isc__quic_setup_write_key(
+ conn, is_server, nglevel,
+ (isc_constregion_t){ len, secret });
+ if (result != ISC_R_SUCCESS) {
+ return 0;
+ }
+ conn->level = nglevel;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return 1;
+}
+
+static int
+got_transport_params_cb(isc_tls_t *tls ISC_ATTR_UNUSED, const uint8_t *params,
+ size_t len, void *arg) {
+ isc_quic_conn_t *conn;
+ int r;
+
+ conn = arg;
+ r = ngtcp2_conn_decode_and_set_remote_transport_params(conn->inner,
+ params, len);
+ if (r != 0) {
+ ngtcp2_conn_set_tls_error(conn->inner, r);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+alert_cb(isc_tls_t *tls ISC_ATTR_UNUSED, uint8_t code, void *arg) {
+ isc_quic_conn_t *conn = arg;
+ ngtcp2_conn_set_tls_alert(conn->inner, code);
+ return 1;
+}
+
+static int
+quic_alpn_select(isc_tls_t *tls, const unsigned char **outp,
+ unsigned char *lenp, const unsigned char *in, unsigned int len,
+ void *arg ISC_ATTR_UNUSED) {
+ isc_quic_conn_t *conn = SSL_get_ex_data(tls, quic_index);
+ INSIST(conn != NULL);
+
+ int r = SSL_select_next_proto(UNCONST(outp), lenp, in, len,
+ conn->alpn.data, conn->alpn.len);
+ if (r != OPENSSL_NPN_NEGOTIATED) {
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+/*
+ * This is a no-op here because OpenSSL expects the remote transport parameters
+ * to be set inside the `got_transport_params_cb`.
+ */
+isc_result_t
+isc__quic_set_remote_transport_params(isc_quic_conn_t *conn ISC_ATTR_UNUSED,
+ isc_tls_t *tls ISC_ATTR_UNUSED) {
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc__quic_set_local_transport_params(isc_quic_conn_t *conn, isc_tls_t *tls) {
+ /*
+ * I don't think there is a well defined limit but I see that 256 bytes
+ * is common in the wild so this should be good enough
+ *
+ * (famous last words)
+ *
+ * - Aydin
+ */
+ uint8_t buffer[512];
+ ngtcp2_ssize len;
+ int r;
+
+ INSIST(conn->local_transport_params == NULL);
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ len = ngtcp2_conn_encode_local_transport_params2(conn->inner, buffer,
+ sizeof(buffer));
+#else
+ len = ngtcp2_conn_encode_local_transport_params(conn->inner, buffer,
+ sizeof(buffer));
+#endif
+ if (len < 0) {
+ return ISC_R_NOSPACE;
+ }
+
+ conn->local_transport_params = isc_mem_allocate(conn->mem.user_data,
+ len);
+ memcpy(conn->local_transport_params, buffer, len);
+
+ r = SSL_set_quic_tls_transport_params(tls, conn->local_transport_params,
+ len);
+ if (r != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_set_quic_tls_transport_params", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc__quic_do_tls(isc_quic_conn_t *conn, isc_tls_t *tls,
+ ngtcp2_encryption_level nglevel ISC_ATTR_UNUSED,
+ isc_constregion_t data) {
+ isc__quic_crypto_frame_data_t *frame;
+ isc_result_t result;
+ bool need_handshake;
+ int r;
+
+ ERR_set_mark();
+
+ if (data.length != 0) {
+ frame = isc_mem_get(conn->mem.user_data,
+ STRUCT_FLEX_SIZE(frame, data, data.length));
+ *frame = (isc__quic_crypto_frame_data_t){
+ .len = data.length,
+ .link = ISC_LINK_INITIALIZER,
+ };
+ memcpy(frame->data, data.base, data.length);
+ ISC_LIST_ENQUEUE(conn->crypto_buffered_frames, frame, link);
+ }
+
+#if NGTCP2_VERSION_NUM >= 0x011700 /* 1.23.0 */
+ need_handshake = ngtcp2_conn_get_handshake_completed2(conn->inner) == 0;
+#else
+ need_handshake = ngtcp2_conn_get_handshake_completed(conn->inner) == 0;
+#endif
+
+ if (need_handshake) {
+ r = SSL_do_handshake(tls);
+ if (r <= 0) {
+ switch (SSL_get_error(tls, r)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ case SSL_ERROR_WANT_CLIENT_HELLO_CB:
+ CLEANUP(ISC_R_SUCCESS);
+ default:
+ CLEANUP(ISC_R_TLSERROR);
+ }
+ }
+
+ ngtcp2_conn_tls_handshake_completed(conn->inner);
+ }
+
+ r = SSL_read(tls, NULL, 0);
+ if (r != 1) {
+ switch (SSL_get_error(tls, r)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ CLEANUP(ISC_R_SUCCESS);
+ default:
+ CLEANUP(ISC_R_TLSERROR);
+ }
+ }
+
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ ERR_pop_to_mark();
+ return result;
+}
+
+isc_result_t
+isc_quic_tlsctx_client_configure(isc_tlsctx_t *tlsctx) {
+ REQUIRE(tlsctx != NULL);
+
+ if (SSL_CTX_set_min_proto_version(tlsctx, TLS1_3_VERSION) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_min_proto_version", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+isc_result_t
+isc_quic_tlsctx_server_configure(isc_tlsctx_t *tlsctx) {
+ REQUIRE(tlsctx != NULL);
+
+ SSL_CTX_set_alpn_select_cb(tlsctx, quic_alpn_select, NULL);
+
+ if (SSL_CTX_set_min_proto_version(tlsctx, TLS1_3_VERSION) != 1) {
+ return isc_ossl_wrap_logged_toresult(
+ ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
+ "SSL_CTX_set_min_proto_version", ISC_R_TLSERROR);
+ }
+
+ return ISC_R_SUCCESS;
+}
+
+void
+isc__quic_initialize(void) {
+ quic_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ INSIST(quic_index != -1);
+}
+
+void
+isc__quic_shutdown(void) {
+ /* noop */
+}
diff --git a/lib/isc/quic/quic_p.h b/lib/isc/quic/quic_p.h
index 4f16c5480a..ecb8329a20 100644
--- a/lib/isc/quic/quic_p.h
+++ b/lib/isc/quic/quic_p.h
@@ -21,14 +21,114 @@
#include <isc/quic.h>
#include <isc/types.h>
+typedef struct isc__quic_stream isc__quic_stream_t;
+typedef struct isc__quic_stream_data isc__quic_stream_data_t;
+
+#ifdef HAVE_OPENSSL_3
+typedef struct isc__quic_crypto_frame_data isc__quic_crypto_frame_data_t;
+#endif /* HAVE_OPENSSL_3 */
+
+typedef enum isc__quic_conn_state {
+ QUIC_CONN_STATE_INVALID = 0x00,
+ QUIC_CONN_STATE_HANDSHAKE = 0x01,
+ QUIC_CONN_STATE_CONNECTED = 0x02,
+ QUIC_CONN_STATE_CLOSED = 0x03,
+ QUIC_CONN_STATE_TERMINATED = 0x04,
+} isc__quic_conn_state_t;
+
struct isc_quic_conn {
uint32_t magic;
isc_refcount_t references;
+
isc_quic_router_t *router;
const isc_quic_conn_callbacks_t *cb;
void *cbarg;
+ ISC_LIST(isc__quic_stream_t) streams;
+ ISC_LIST(isc__quic_stream_data_t) outgoing_stream_data;
+
/* isc_mem_t resides inside `mem.user_data` */
ngtcp2_mem mem;
+
+ /*
+ * This fits "\x3doq\x2h3" and fits perfectly with the struct's 8 byte
+ * member alignment.
+ */
+ struct {
+ uint8_t len;
+ uint8_t data[7];
+ } alpn;
+
+ isc__quic_conn_state_t state;
+
+/*
+ * OpenSSL (not the forks) wants us to keep some information alive and
+ * allocated.
+ */
+#ifdef HAVE_OPENSSL_3
+ ngtcp2_encryption_level level;
+ uint8_t *local_transport_params;
+ ISC_LIST(isc__quic_crypto_frame_data_t) crypto_buffered_frames;
+ ISC_LIST(isc__quic_crypto_frame_data_t) crypto_awaiting_frames;
+#endif /* HAVE_OPENSSL_3 */
+
+ ngtcp2_conn *inner;
};
+
+#ifdef HAVE_OPENSSL_3
+struct isc__quic_crypto_frame_data {
+ size_t len;
+ ISC_LINK(isc__quic_crypto_frame_data_t) link;
+ uint8_t data[] ISC_ATTR_COUNTED_BY(len);
+};
+#endif /* HAVE_OPENSSL_3 */
+
+isc_result_t
+isc__quic_setup_read_key(isc_quic_conn_t *conn, bool is_server,
+ ngtcp2_encryption_level nglevel,
+ isc_constregion_t secret);
+/**<
+ * \brief
+ * Derive and install key for RX operations.
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_FAILURE on opaque failure
+ */
+
+isc_result_t
+isc__quic_setup_write_key(isc_quic_conn_t *conn, bool is_server,
+ ngtcp2_encryption_level nglevel,
+ isc_constregion_t secret);
+/**<
+ * \brief
+ * Derive and install key for TX operations.
+ *
+ * \retval ISC_R_SUCCESS on success
+ * \retval ISC_R_FAILURE on opaque failure
+ */
+
+isc_result_t
+isc__quic_set_remote_transport_params(isc_quic_conn_t *conn, isc_tls_t *tls);
+
+isc_result_t
+isc__quic_set_local_transport_params(isc_quic_conn_t *conn, isc_tls_t *tls);
+
+isc_result_t
+isc__quic_setup_tls(isc_tls_t *tls, isc_quic_conn_t *conn);
+/**<
+ * \brief
+ * Setup for the connection.
+ *
+ * \retval ISC_R_SUCCESS on success.
+ */
+
+isc_result_t
+isc__quic_do_tls(isc_quic_conn_t *conn, isc_tls_t *tls,
+ ngtcp2_encryption_level nglevel, isc_constregion_t data);
+/**<
+ * \brief
+ * Drive the TLS integration of the connection.
+ *
+ * \retval ISC_R_SUCCESS on success
+ */
diff --git a/meson.build b/meson.build
index f1533e906f..041e41bfb8 100644
--- a/meson.build
+++ b/meson.build
@@ -670,10 +670,28 @@ config.set('OPENSSL_NO_DEPRECATED', true)
if openssl_version_number.version_compare('>=3.0.0')
config.set('HAVE_OPENSSL_3', true)
config.set('OPENSSL_API_COMPAT', 30000)
+
+ config.set(
+ 'HAVE_OPENSSL_QUIC_INTEGRATION',
+ cc.has_function(
+ 'SSL_set_quic_tls_cbs',
+ prefix: '#include <openssl/ssl.h>',
+ dependencies: openssl_dep,
+ ),
+ )
else
config.set('HAVE_OPENSSL_3', false)
config.set('OPENSSL_API_COMPAT', 10100)
+ config.set(
+ 'HAVE_OPENSSL_QUIC_INTEGRATION',
+ cc.has_function(
+ 'SSL_set_quic_method',
+ prefix: '#include <openssl/ssl.h>',
+ dependencies: openssl_dep,
+ ),
+ )
+
have_openssl_aead_h = cc.has_header('openssl/aead.h', dependencies: openssl_dep)
config.set('HAVE_OPENSSL_AEAD_H', have_openssl_aead_h)
if have_openssl_aead_h
@@ -862,7 +880,7 @@ endif
## DNS-over-QUIC (DoQ) [IN PROGRESS, DEVELOPER MODE ONLY]
ngtcp2_dep = null_dep
-if developer_mode
+if developer_mode and config.get('HAVE_OPENSSL_QUIC_INTEGRATION')
ngtcp2_dep = dependency('libngtcp2', version: '>=1.11.0', required: false)
config.set('HAVE_LIBNGTCP2', ngtcp2_dep.found())
endif