quic 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ #ifndef QUIC_ENDPOINT_H
2
+ #define QUIC_ENDPOINT_H 1
3
+
4
+ #include "connection_common.h"
5
+
6
+ extern VALUE rb_cQUICEndpoint;
7
+
8
+ /* The CID table, for the get_new_connection_id2 and remove_connection_id
9
+ callbacks of a connection the Endpoint holds. conn is that connection's
10
+ object. Neither touches Ruby objects, so the callbacks need no
11
+ rb_protect. dissociate leaves an entry that maps to another connection
12
+ alone. */
13
+ void quic_endpoint_associate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid, VALUE conn);
14
+ void quic_endpoint_dissociate_cid(quic_endpoint_t *ep, const ngtcp2_cid *cid, VALUE conn);
15
+
16
+ /* The Stateless Reset token for cid, derived from the Endpoint's secret so
17
+ that it can be made again for a CID no longer in the table. token is
18
+ NGTCP2_STATELESS_RESET_TOKENLEN bytes. Returns 0, or -1 on failure. */
19
+ int quic_endpoint_generate_reset_token(const quic_endpoint_t *ep, uint8_t *token,
20
+ const ngtcp2_cid *cid);
21
+
22
+ /* Connection#close of a connection the Endpoint holds: build the
23
+ CONNECTION_CLOSE carrying ccerr, queue it for Endpoint#write_pkt and
24
+ start the closing period. When ngtcp2 cannot build the packet, the
25
+ connection leaves the table at once. */
26
+ void quic_endpoint_close_conn(quic_endpoint_t *ep, quic_conn_t *c, const ngtcp2_ccerr *ccerr);
27
+
28
+ #endif /* QUIC_ENDPOINT_H */
@@ -0,0 +1,112 @@
1
+ #include "connection_common.h"
2
+
3
+ /* The quic_ext_api table of quic/ext.h. A quic_ext_conn * is a quic_conn_t *
4
+ behind the opaque type. None of these functions raises a Ruby exception
5
+ except quic_ext_conn_get; they return 0 or a QUIC_EXT_ERR_* code. */
6
+
7
+ static quic_conn_t *
8
+ quic_ext_conn_struct(quic_ext_conn *ec)
9
+ {
10
+ return (quic_conn_t *)ec;
11
+ }
12
+
13
+ /* Raises TypeError unless conn is a QUIC::Connection::Client or Server. */
14
+ static quic_ext_conn *
15
+ quic_ext_conn_get(VALUE conn)
16
+ {
17
+ return (quic_ext_conn *)rb_check_typeddata(conn, &quic_conn_data_type);
18
+ }
19
+
20
+ /* Hand every stream of the connection to h. Allowed once, and only before
21
+ the connection has a QUIC::Stream: the two modes cannot share streams.
22
+ Every member of the ABI 1.0 handler is called, so h must be at least that
23
+ large. @stream_handler marks the connection for the Ruby side, which then
24
+ refuses #accept_stream. */
25
+ static int
26
+ quic_ext_set_stream_handler(quic_ext_conn *ec, const quic_ext_stream_handler *h,
27
+ void *ud, VALUE owner)
28
+ {
29
+ quic_conn_t *c = quic_ext_conn_struct(ec);
30
+ if (h == NULL || h->size < sizeof(quic_ext_stream_handler)) return QUIC_EXT_ERR_OTHER;
31
+ if (c->handler != NULL) return QUIC_EXT_ERR_INVALID_STATE;
32
+ VALUE streams = rb_ivar_get(c->owner, rb_intern("@streams"));
33
+ if (RB_TYPE_P(streams, T_HASH) && RHASH_SIZE(streams) > 0) return QUIC_EXT_ERR_INVALID_STATE;
34
+
35
+ c->handler = h;
36
+ c->handler_ud = ud;
37
+ c->handler_owner = owner;
38
+ rb_ivar_set(c->owner, rb_intern("@stream_handler"), Qtrue);
39
+ return 0;
40
+ }
41
+
42
+ static int
43
+ quic_ext_open_uni_stream(quic_ext_conn *ec, int64_t *id)
44
+ {
45
+ quic_conn_t *c = quic_ext_conn_struct(ec);
46
+ int rv = ngtcp2_conn_open_uni_stream(c->conn, id, NULL);
47
+ if (rv == NGTCP2_ERR_STREAM_ID_BLOCKED) return QUIC_EXT_ERR_STREAM_ID_BLOCKED;
48
+ return rv == 0 ? 0 : QUIC_EXT_ERR_OTHER;
49
+ }
50
+
51
+ /* Give the peer n more bytes of credit on the stream and the connection,
52
+ for data the handler has now consumed (for HTTP/3, request body handed
53
+ to the application). */
54
+ static int
55
+ quic_ext_consume(quic_ext_conn *ec, int64_t id, uint64_t n)
56
+ {
57
+ quic_conn_t *c = quic_ext_conn_struct(ec);
58
+ if (ngtcp2_conn_extend_max_stream_offset(c->conn, id, n) != 0) return QUIC_EXT_ERR_OTHER;
59
+ ngtcp2_conn_extend_max_offset(c->conn, n);
60
+ return 0;
61
+ }
62
+
63
+ /* Both return 0 for a stream ngtcp2 no longer knows. */
64
+ static int
65
+ quic_ext_shutdown_stream_read(quic_ext_conn *ec, int64_t id, uint64_t app_error_code)
66
+ {
67
+ quic_conn_t *c = quic_ext_conn_struct(ec);
68
+ return ngtcp2_conn_shutdown_stream_read(c->conn, 0, id, app_error_code) == 0
69
+ ? 0 : QUIC_EXT_ERR_OTHER;
70
+ }
71
+
72
+ static int
73
+ quic_ext_shutdown_stream_write(quic_ext_conn *ec, int64_t id, uint64_t app_error_code)
74
+ {
75
+ quic_conn_t *c = quic_ext_conn_struct(ec);
76
+ return ngtcp2_conn_shutdown_stream_write(c->conn, 0, id, app_error_code) == 0
77
+ ? 0 : QUIC_EXT_ERR_OTHER;
78
+ }
79
+
80
+ /* This endpoint's initial_max_streams_bidi. nghttp3 needs it at setup;
81
+ later raises arrive through the extend_max_remote_streams_bidi handler. */
82
+ static uint64_t
83
+ quic_ext_local_max_streams_bidi(quic_ext_conn *ec)
84
+ {
85
+ quic_conn_t *c = quic_ext_conn_struct(ec);
86
+ return ngtcp2_conn_get_local_transport_params2(c->conn)->initial_max_streams_bidi;
87
+ }
88
+
89
+ /* Remember the application error to close the connection with after the
90
+ handler reports a failure (for HTTP/3, the H3_* code nghttp3 gave). */
91
+ static int
92
+ quic_ext_set_application_error(quic_ext_conn *ec, uint64_t app_error_code)
93
+ {
94
+ quic_conn_t *c = quic_ext_conn_struct(ec);
95
+ ngtcp2_ccerr_set_application_error(&c->close_error, app_error_code, NULL, 0);
96
+ c->close_error_set = true;
97
+ return 0;
98
+ }
99
+
100
+ const quic_ext_api quic_ext_api_table = {
101
+ .abi_major = QUIC_EXT_ABI_MAJOR,
102
+ .abi_minor = 0,
103
+ .size = sizeof(quic_ext_api),
104
+ .conn_get = quic_ext_conn_get,
105
+ .set_stream_handler = quic_ext_set_stream_handler,
106
+ .open_uni_stream = quic_ext_open_uni_stream,
107
+ .consume = quic_ext_consume,
108
+ .shutdown_stream_read = quic_ext_shutdown_stream_read,
109
+ .shutdown_stream_write = quic_ext_shutdown_stream_write,
110
+ .local_max_streams_bidi = quic_ext_local_max_streams_bidi,
111
+ .set_application_error = quic_ext_set_application_error,
112
+ };
data/ext/quic/extconf.rb CHANGED
@@ -2,53 +2,170 @@
2
2
 
3
3
  require "mkmf"
4
4
  require "mini_portile2"
5
+ require "shellwords"
6
+ require "fileutils"
5
7
 
6
- LIBRESSL_VERSION = "4.3.1"
7
- LIBRESSL_SHA256 = "c2db42ace14e7d5419826fab35a742ec6e4d12725a051a51d0cea3c10ba0fa50"
8
- NGTCP2_VERSION = "1.22.1"
9
- NGTCP2_SHA256 = "063d80531acac0ddbbc1b9d12829a824edc2abe8dba2e632fd1ce15cfd5632f9"
8
+ NGTCP2_VERSION = "1.25.0"
9
+ NGTCP2_SHA256 = "1c0843076528a87b65e9a9d455100941f4cb65d44f96c5da6ae56df146043955"
10
+ # The picotls revision that ngtcp2 1.25.0 is tested against (see ngtcp2's README).
11
+ PICOTLS_COMMIT = "f07f1c8c68b237f1468bc1f1fe1b68aba3ff23b4"
12
+ PICOTLS_SHA256 = "6ecb4f8d9946c2b9342300b29627dc34e746a551d438d35af325a4495d62a6e3"
13
+ # Bump when ext/quic/patches/picotls/ or PicotlsRecipe::SOURCES changes;
14
+ # installed? only checks for the archive.
15
+ PICOTLS_PATCH_LEVEL = "p2"
10
16
 
11
- libressl = MiniPortile.new("libressl", LIBRESSL_VERSION)
12
- libressl.files = [{
13
- url: "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-#{LIBRESSL_VERSION}.tar.gz",
14
- sha256: LIBRESSL_SHA256
17
+ # Locate the host's libcrypto. ngtcp2 and picotls are vendored and statically
18
+ # linked, but the crypto primitives and X.509 come from whatever OpenSSL (or
19
+ # LibreSSL) the system provides, linked dynamically. Sharing one libcrypto with
20
+ # Ruby's own openssl extension is the point: two copies in one process
21
+ # interpose on each other's global symbols.
22
+ #
23
+ # Everything downstream resolves OpenSSL through pkg-config -- this file,
24
+ # ngtcp2's configure, and the `openssl` entry in libngtcp2_crypto_picotls.pc's
25
+ # Requires.private -- so --with-openssl-dir is honoured by putting its prefix
26
+ # on PKG_CONFIG_PATH once, here. That keeps all three agreeing on one install
27
+ # instead of each making its own choice.
28
+ _openssl_incdir, openssl_libdir = dir_config("openssl")
29
+ if openssl_libdir
30
+ ENV["PKG_CONFIG_PATH"] =
31
+ [File.join(openssl_libdir, "pkgconfig"), ENV["PKG_CONFIG_PATH"]].compact.join(File::PATH_SEPARATOR)
32
+ end
33
+
34
+ unless pkg_config("openssl")
35
+ abort "OpenSSL not found. Install its development package (libssl-dev, " \
36
+ "openssl-devel, ...), or point at a prefix whose lib/pkgconfig holds " \
37
+ "openssl.pc with --with-openssl-dir=/path."
38
+ end
39
+ # picotls is compiled by hand below and needs the include path on its own.
40
+ openssl_cflags = pkg_config("openssl", "cflags-only-I").to_s.strip
41
+
42
+ # picotls has no release tarballs, and its CMake build needs the picotest
43
+ # submodule, which GitHub archive tarballs do not contain. Build only what
44
+ # quic.so uses into a single libpicotls.a: the core and the OpenSSL-API
45
+ # backend. The core's lib/pembase64.c, a PEM loader, is left out; the server
46
+ # reads its certificates with libcrypto instead.
47
+ class PicotlsRecipe < MiniPortile
48
+ SOURCES = %w[lib/picotls.c lib/hpke.c lib/openssl.c].freeze
49
+
50
+ attr_accessor :openssl_cflags
51
+
52
+ def configure
53
+ end
54
+
55
+ def configured?
56
+ true
57
+ end
58
+
59
+ def compile
60
+ cflags = %w[-std=gnu99 -O2 -fPIC -fvisibility=hidden -Iinclude] + openssl_cflags.to_s.shellsplit
61
+ objects = SOURCES.map do |src|
62
+ obj = File.basename(src, ".c") + ".o"
63
+ execute("compile", [*cc_cmd.shellsplit, *cflags, "-c", src, "-o", obj])
64
+ obj
65
+ end
66
+ ar = (ENV["AR"] || RbConfig::CONFIG["AR"] || "ar").shellsplit
67
+ execute("compile", [*ar, "rcs", "libpicotls.a", *objects])
68
+ end
69
+
70
+ def install
71
+ FileUtils.mkdir_p([lib_path, include_path])
72
+ FileUtils.cp(File.join(work_path, "libpicotls.a"), lib_path)
73
+ FileUtils.cp_r(Dir.glob(File.join(work_path, "include", "*")), include_path)
74
+ end
75
+
76
+ def installed?
77
+ File.exist?(File.join(lib_path, "libpicotls.a"))
78
+ end
79
+ end
80
+
81
+ picotls = PicotlsRecipe.new("picotls", "#{PICOTLS_COMMIT[0, 7]}-#{PICOTLS_PATCH_LEVEL}")
82
+ picotls.files = [{
83
+ url: "https://github.com/h2o/picotls/archive/#{PICOTLS_COMMIT}.tar.gz",
84
+ sha256: PICOTLS_SHA256
15
85
  }]
16
- libressl.configure_options = %w[--disable-shared --with-pic]
17
- libressl.cook
18
- libressl.activate
86
+ picotls.patch_files = Dir.glob(File.join(__dir__, "patches", "picotls", "*.patch")).sort
87
+ picotls.openssl_cflags = openssl_cflags
88
+ picotls.cook
89
+ # Deliberately no picotls.activate: mkmf_config passes -I/-L below, and not
90
+ # activating keeps the extra directory out of CPATH.
19
91
 
20
92
  ngtcp2 = MiniPortile.new("ngtcp2", NGTCP2_VERSION)
21
93
  ngtcp2.files = [{
22
94
  url: "https://github.com/ngtcp2/ngtcp2/releases/download/v#{NGTCP2_VERSION}/ngtcp2-#{NGTCP2_VERSION}.tar.gz",
23
95
  sha256: NGTCP2_SHA256
24
96
  }]
25
- ngtcp2.configure_options = %W[
26
- --enable-lib-only
27
- --disable-shared
28
- --with-pic
29
- PKG_CONFIG_PATH=#{libressl.path}/lib/pkgconfig
97
+ ngtcp2.configure_options = [
98
+ "--enable-lib-only",
99
+ "--disable-shared",
100
+ "--with-pic",
101
+ # Skip the quictls/libressl/ossl helpers; only libngtcp2_crypto_picotls.
102
+ "--without-openssl",
103
+ "--with-picotls",
104
+ # crypto/picotls declares lib_LIBRARIES, not lib_LTLIBRARIES, so it is built
105
+ # by plain ar rather than libtool and --with-pic does not reach it. Without
106
+ # -fPIC the archive cannot be linked into quic.so.
107
+ "CFLAGS=-g -O2 -fPIC",
108
+ "PICOTLS_CFLAGS=-I#{picotls.include_path}",
109
+ "PICOTLS_LIBS=-L#{picotls.lib_path} -lpicotls"
30
110
  ]
111
+ # ngtcp2 still probes for OpenSSL under --without-openssl: its picotls check
112
+ # links the conftest against libcrypto. It finds it through the same
113
+ # PKG_CONFIG_PATH set above, which the configure subprocess inherits.
31
114
  ngtcp2.cook
32
115
  ngtcp2.activate
33
116
 
34
- # Wire static archives into mkmf in reverse dependency order. mkmf_config
35
- # prepends to $libs, so the leftmost entry on the final link line ends up being
36
- # the last one we add here (ngtcp2_crypto_libressl), satisfying static-link
37
- # ordering: ngtcp2_crypto_libressl -> ngtcp2 -> ssl -> crypto.
38
- libressl.mkmf_config(pkg: "libcrypto", static: "crypto")
39
- libressl.mkmf_config(pkg: "libssl", static: "ssl")
40
- ngtcp2.mkmf_config(pkg: "libngtcp2", static: "ngtcp2")
41
- ngtcp2.mkmf_config(pkg: "libngtcp2_crypto_libressl", static: "ngtcp2_crypto_libressl")
117
+ # Wire the vendored static archives into mkmf in reverse dependency order.
118
+ # mkmf_config prepends to $libs, so the leftmost entry on the final link line
119
+ # ends up being the last one added here, satisfying static-link ordering:
120
+ # ngtcp2_crypto_picotls -> ngtcp2 -> picotls -> the -lssl -lcrypto that
121
+ # pkg_config("openssl") already put there.
122
+ #
123
+ # Deliberately not the `pkg:` form. Resolving libngtcp2_crypto_picotls.pc
124
+ # would follow its `Requires.private: libngtcp2, openssl` with
125
+ # `pkg-config --static`, which drags in the host libcrypto's own static
126
+ # dependency list (on Ubuntu: -l:libjitterentropy.a -lz -lzstd). Those belong
127
+ # to a static libcrypto we are not linking, and the archives they name are
128
+ # often not even installed. The recipe's own include/lib paths are all we
129
+ # need.
130
+ picotls.mkmf_config(static: "picotls")
131
+ ngtcp2.mkmf_config(static: "ngtcp2")
132
+ ngtcp2.mkmf_config(static: "ngtcp2_crypto_picotls")
42
133
 
134
+ # picotls's OpenSSL backend, and the X25519 patch in ext/quic/patches/picotls/,
135
+ # both need the raw public key API introduced in OpenSSL 1.1.1. LibreSSL
136
+ # reports 0x20000000L here, so one check covers both.
137
+ abort "OpenSSL 1.1.1 or later is required" unless try_compile(<<~C)
138
+ #include <openssl/opensslv.h>
139
+ #if OPENSSL_VERSION_NUMBER < 0x10101000L
140
+ #error OpenSSL is too old
141
+ #endif
142
+ int main(void) { return 0; }
143
+ C
43
144
  abort "ngtcp2 header missing" unless have_header("ngtcp2/ngtcp2.h")
44
- abort "ngtcp2_crypto_quictls header missing" unless have_header("ngtcp2/ngtcp2_crypto_quictls.h")
45
- abort "openssl/ssl.h missing" unless have_header("openssl/ssl.h")
145
+ abort "picotls header missing" unless have_header("picotls.h")
146
+ abort "picotls/openssl.h missing" unless have_header("picotls/openssl.h")
147
+ abort "ngtcp2_crypto_picotls header missing" unless have_header("ngtcp2/ngtcp2_crypto_picotls.h")
148
+ abort "picotls was built without X25519 (patch not applied?)" unless try_compile(<<~C)
149
+ #include <picotls/openssl.h>
150
+ #if !PTLS_OPENSSL_HAVE_X25519
151
+ #error X25519 unavailable
152
+ #endif
153
+ C
46
154
  # ngtcp2_conn_client_new and most other ngtcp2 entry points are exposed as
47
155
  # ABI-versioned macros, so use non-macro symbols for link verification.
48
156
  abort "libngtcp2 not linkable" unless have_func("ngtcp2_version", "ngtcp2/ngtcp2.h")
49
- abort "libngtcp2_crypto_libressl not linkable" unless have_func("ngtcp2_crypto_quictls_init", "ngtcp2/ngtcp2_crypto_quictls.h")
50
- abort "libssl not linkable" unless have_func("OpenSSL_version", "openssl/opensslv.h")
157
+ abort "libngtcp2_crypto_picotls not linkable" unless
158
+ have_func("ngtcp2_crypto_picotls_configure_client_context", %w[picotls.h ngtcp2/ngtcp2_crypto_picotls.h])
159
+ abort "libcrypto not linkable" unless have_func("OpenSSL_version", "openssl/crypto.h")
51
160
 
161
+ # picotls has neither a version macro nor releases, so QUIC.library_versions
162
+ # reports the commit the extension was built against.
163
+ append_cflags("-DQUIC_PICOTLS_COMMIT=\\\"#{PICOTLS_COMMIT}\\\"")
52
164
  append_cflags("-fvisibility=hidden")
165
+ append_ldflags("-Wl,--exclude-libs,ALL") # Unexport symbols from statically linked libraries
166
+
167
+ # quic/ext.h, the C function table other extensions (the http3 gem) build
168
+ # against. It ships in the gem; QUIC::EXT_INCLUDE_DIR points them here.
169
+ append_cppflags("-I#{File.expand_path("include", __dir__)}")
53
170
 
54
171
  create_makefile("quic/quic")
@@ -0,0 +1,84 @@
1
+ /* quic/ext.h — the C function table quic.so offers to other extensions.
2
+ *
3
+ * Another extension (the http3 gem) registers a stream handler on a
4
+ * connection and drives it through this table instead of QUIC::Stream.
5
+ * Obtain the table after rb_require("quic") with
6
+ *
7
+ * quic_ext_get_api_fn get =
8
+ * (quic_ext_get_api_fn)rb_ext_resolve_symbol("quic/quic", "quic_ext_get_api");
9
+ * const quic_ext_api *api = get ? get(QUIC_EXT_ABI_MAJOR) : NULL;
10
+ *
11
+ * Rules:
12
+ *
13
+ * - Version: quic_ext_get_api(major) returns NULL when major differs from
14
+ * the table's. The structs below grow only by appending, which bumps
15
+ * abi_minor; reordering, removing or retyping a member bumps abi_major.
16
+ * Check `size` before calling a member added after the version you were
17
+ * built against. The handler struct follows the same rules.
18
+ * - Exceptions: no function in the table and no handler function raises a
19
+ * Ruby exception; they return 0 or a negative error code. The one
20
+ * exception is conn_get, which raises TypeError for a wrong object and is
21
+ * meant for initialization called from a Ruby method. A handler function
22
+ * returning a negative value makes the ngtcp2 callback fail with
23
+ * NGTCP2_ERR_CALLBACK_FAILURE.
24
+ * - Lifetime: `owner` passed to set_stream_handler is marked by the
25
+ * connection while it lives. `ud` must not move (allocate it with
26
+ * xmalloc, not as a Ruby object), and `h` must outlive the connection
27
+ * (a static const struct). quic.so never calls a handler from its dfree,
28
+ * and the owner's dfree must not touch the quic_ext_conn.
29
+ * - Where: table functions may be called from inside handler functions.
30
+ * Everything runs with the GVL held.
31
+ */
32
+ #ifndef QUIC_EXT_H
33
+ #define QUIC_EXT_H 1
34
+
35
+ #include <stddef.h>
36
+ #include <stdint.h>
37
+ #include <ruby.h>
38
+
39
+ #define QUIC_EXT_ABI_MAJOR 1
40
+
41
+ #define QUIC_EXT_ERR_INVALID_STATE (-1)
42
+ #define QUIC_EXT_ERR_STREAM_ID_BLOCKED (-2)
43
+ #define QUIC_EXT_ERR_STREAM_NOT_FOUND (-3)
44
+ #define QUIC_EXT_ERR_OTHER (-99)
45
+
46
+ typedef struct { const uint8_t *base; size_t len; } quic_ext_vec; /* same layout as ngtcp2_vec */
47
+ typedef struct quic_ext_conn quic_ext_conn; /* opaque */
48
+
49
+ typedef struct {
50
+ size_t size; /* sizeof(quic_ext_stream_handler); tells which members exist */
51
+ int (*recv_stream_data)(void *ud, int64_t id, const uint8_t *data, size_t len,
52
+ int fin, uint64_t ts, size_t *consumed);
53
+ int (*next_stream_data)(void *ud, int64_t *id, int *fin,
54
+ quic_ext_vec *vecs, size_t cap, size_t *nvecs);
55
+ int (*stream_data_written)(void *ud, int64_t id, size_t n);
56
+ int (*stream_blocked)(void *ud, int64_t id);
57
+ int (*stream_shut_write)(void *ud, int64_t id);
58
+ int (*acked_stream_data)(void *ud, int64_t id, uint64_t n);
59
+ int (*extend_max_stream_data)(void *ud, int64_t id);
60
+ int (*extend_max_remote_streams_bidi)(void *ud, uint64_t max);
61
+ int (*stream_stop_sending)(void *ud, int64_t id, uint64_t app_error_code);
62
+ int (*stream_reset)(void *ud, int64_t id, uint64_t final_size, uint64_t app_error_code);
63
+ int (*stream_close)(void *ud, int64_t id, int has_code, uint64_t app_error_code);
64
+ int (*handshake_completed)(void *ud);
65
+ } quic_ext_stream_handler;
66
+
67
+ typedef struct {
68
+ uint32_t abi_major; /* bumped only to break compatibility */
69
+ uint32_t abi_minor; /* bumped whenever a member is appended */
70
+ size_t size; /* sizeof(quic_ext_api) */
71
+ quic_ext_conn *(*conn_get)(VALUE conn); /* TypeError for a wrong object */
72
+ int (*set_stream_handler)(quic_ext_conn *c, const quic_ext_stream_handler *h,
73
+ void *ud, VALUE owner);
74
+ int (*open_uni_stream)(quic_ext_conn *c, int64_t *id);
75
+ int (*consume)(quic_ext_conn *c, int64_t id, uint64_t n);
76
+ int (*shutdown_stream_read)(quic_ext_conn *c, int64_t id, uint64_t app_error_code);
77
+ int (*shutdown_stream_write)(quic_ext_conn *c, int64_t id, uint64_t app_error_code);
78
+ uint64_t (*local_max_streams_bidi)(quic_ext_conn *c);
79
+ int (*set_application_error)(quic_ext_conn *c, uint64_t app_error_code);
80
+ } quic_ext_api;
81
+
82
+ typedef const quic_ext_api *(*quic_ext_get_api_fn)(uint32_t abi_major);
83
+
84
+ #endif /* QUIC_EXT_H */
@@ -0,0 +1,74 @@
1
+ Subject: [PATCH] openssl: use the raw public key API for X25519
2
+
3
+ LibreSSL does not provide EVP_PKEY_get1_tls_encodedpoint() /
4
+ EVP_PKEY_set1_tls_encodedpoint(), so picotls disables ptls_openssl_x25519
5
+ when built against LibreSSL. Switch the EVP-based X25519 key exchange to
6
+ EVP_PKEY_get_raw_public_key() / EVP_PKEY_new_raw_public_key() and drop the
7
+ LibreSSL exclusion.
8
+
9
+ Verified with LibreSSL 4.3.1 and OpenSSL 3.5.7 (picotls test-openssl.t,
10
+ including x25519-to-minicrypto / x25519-from-minicrypto).
11
+
12
+ Not upstream. Requires OpenSSL >= 1.1.1 (1.1.0 lacks the raw key API).
13
+ Applies to h2o/picotls@b84869f41414b6d0148db7728f1cf12f5b544874.
14
+
15
+ diff --git a/include/picotls/openssl.h b/include/picotls/openssl.h
16
+ index 37a0740..04e4653 100644
17
+ --- a/include/picotls/openssl.h
18
+ +++ b/include/picotls/openssl.h
19
+ @@ -60,7 +60,7 @@ extern ptls_key_exchange_algorithm_t ptls_openssl_secp521r1;
20
+ #ifdef EVP_PKEY_ED25519
21
+ #define PTLS_OPENSSL_HAVE_ED25519 1
22
+ #endif
23
+ -#if defined(NID_X25519) && !defined(LIBRESSL_VERSION_NUMBER)
24
+ +#if defined(NID_X25519)
25
+ #define PTLS_OPENSSL_HAVE_X25519 1
26
+ #define PTLS_OPENSSL_HAS_X25519 1 /* deprecated; use HAVE_ */
27
+ extern ptls_key_exchange_algorithm_t ptls_openssl_x25519;
28
+ diff --git a/lib/openssl.c b/lib/openssl.c
29
+ index d6d073b..1c7e492 100644
30
+ --- a/lib/openssl.c
31
+ +++ b/lib/openssl.c
32
+ @@ -559,15 +559,7 @@ static int evp_keyex_on_exchange(ptls_key_exchange_context_t **_ctx, int release
33
+ #undef X25519_KEY_SIZE
34
+ #endif
35
+
36
+ - if ((evppeer = EVP_PKEY_new()) == NULL) {
37
+ - ret = PTLS_ERROR_NO_MEMORY;
38
+ - goto Exit;
39
+ - }
40
+ - if (EVP_PKEY_copy_parameters(evppeer, ctx->privkey) <= 0) {
41
+ - ret = PTLS_ERROR_LIBRARY;
42
+ - goto Exit;
43
+ - }
44
+ - if (EVP_PKEY_set1_tls_encodedpoint(evppeer, peerkey.base, peerkey.len) <= 0) {
45
+ + if ((evppeer = EVP_PKEY_new_raw_public_key(EVP_PKEY_id(ctx->privkey), NULL, peerkey.base, peerkey.len)) == NULL) {
46
+ ret = PTLS_ERROR_LIBRARY;
47
+ goto Exit;
48
+ }
49
+ @@ -629,10 +621,21 @@ static int evp_keyex_init(ptls_key_exchange_algorithm_t *algo, ptls_key_exchange
50
+ *ctx = (struct st_evp_keyex_context_t){{algo, {NULL}, evp_keyex_on_exchange}, pkey};
51
+
52
+ /* set public key */
53
+ - if ((ctx->super.pubkey.len = EVP_PKEY_get1_tls_encodedpoint(ctx->privkey, &ctx->super.pubkey.base)) == 0) {
54
+ - ctx->super.pubkey.base = NULL;
55
+ - ret = PTLS_ERROR_NO_MEMORY;
56
+ - goto Exit;
57
+ + {
58
+ + size_t publen = 0;
59
+ + if (EVP_PKEY_get_raw_public_key(ctx->privkey, NULL, &publen) != 1 || publen == 0) {
60
+ + ret = PTLS_ERROR_LIBRARY;
61
+ + goto Exit;
62
+ + }
63
+ + if ((ctx->super.pubkey.base = OPENSSL_malloc(publen)) == NULL) {
64
+ + ret = PTLS_ERROR_NO_MEMORY;
65
+ + goto Exit;
66
+ + }
67
+ + if (EVP_PKEY_get_raw_public_key(ctx->privkey, ctx->super.pubkey.base, &publen) != 1) {
68
+ + ret = PTLS_ERROR_LIBRARY;
69
+ + goto Exit;
70
+ + }
71
+ + ctx->super.pubkey.len = publen;
72
+ }
73
+
74
+ *_ctx = &ctx->super;