@@ -0,0 +1,16 @@
1+module main
2+
3+import net.mbedtls
4+import os
5+
6+fn test_mbedtls_ssl_listener_preserves_ipv6_literal_by_default() ! {
7+ $if macos || linux {
8+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
9+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
10+ mut listener := mbedtls.new_ssl_listener('[::1]:0', mbedtls.SSLConnectConfig{
11+ cert: cert_path
12+ cert_key: key_path
13+ })!
14+ listener.shutdown()!
15+ }
16+}
@@ -165,8 +165,9 @@ pub mut:
165165
166166// SSLListener listens on a TCP port and accepts connection secured with TLS
167167pub struct SSLListener {
168- saddr string
169- config SSLConnectConfig
168+ saddr string
169+ config SSLConnectConfig
170+ options SSLListenerOptions
170171mut:
171172 server_fd C.mbedtls_net_context
172173 ssl C.mbedtls_ssl_context
@@ -183,11 +184,19 @@ mut:
183184 // duration time.Duration
184185}
185186
186-// create a new SSLListener binding to `saddr`
187-pub fn new_ssl_listener(saddr string, config SSLConnectConfig) !&SSLListener {
187+// SSLListenerOptions configures the TCP listener used by an SSLListener.
188+@[params]
189+pub struct SSLListenerOptions {
190+pub:
191+ family net.AddrFamily = .unspec
192+}
193+
194+// new_ssl_listener creates a new SSLListener binding to `saddr`.
195+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {
188196 mut listener := &SSLListener{
189- saddr: saddr
190- config: config
197+ saddr: saddr
198+ config: config
199+ options: options
191200 }
192201 listener.init()!
193202 listener.opened = true
@@ -216,13 +225,23 @@ pub fn (mut l SSLListener) shutdown() ! {
216225 }
217226}
218227
228+fn ssl_listener_family(saddr string, family net.AddrFamily) net.AddrFamily {
229+ if family != .unspec {
230+ return family
231+ }
232+ address, _ := net.split_address(saddr) or { return .ip }
233+ if address == '::' || address.contains(':') {
234+ return .ip6
235+ }
236+ return .ip
237+}
238+
219239// internal function to init and bind the listener
220240fn (mut l SSLListener) init() ! {
221241 $if trace_ssl ? {
222242 eprintln(@METHOD)
223243 }
224244
225- lhost, lport := net.split_address(l.saddr)!
226245 if l.config.cert == '' || l.config.cert_key == '' {
227246 return error('net.mbedtls SSLListener.init, no certificate or key provided')
228247 }
@@ -259,18 +278,10 @@ fn (mut l SSLListener) init() ! {
259278 C.mbedtls_ssl_conf_authmode(&l.conf, C.MBEDTLS_SSL_VERIFY_REQUIRED)
260279 }
261280
262- mut bind_ip := unsafe { nil }
263- if lhost != '' {
264- bind_ip = voidptr(lhost.str)
265- }
266- bind_port := lport.str()
267-
268- ret = C.mbedtls_net_bind(&l.server_fd, bind_ip, voidptr(bind_port.str), C.MBEDTLS_NET_PROTO_TCP)
269-
270- if ret != 0 {
271- return error_with_code("net.mbedtls SSLListener.init, mbedtls_net_bind can't bind to ${l.saddr} error ret: ${ret}",
272- ret)
273- }
281+ tcp_listener := net.listen_tcp(ssl_listener_family(l.saddr, l.options.family), l.saddr, net.ListenOptions{
282+ backlog: C.MBEDTLS_NET_LISTEN_BACKLOG
283+ }) or { return error('net.mbedtls SSLListener.init, listen_tcp failed for ${l.saddr}: ${err}') }
284+ l.server_fd.fd = tcp_listener.sock.handle
274285
275286 ret = C.mbedtls_ssl_config_defaults(&l.conf, C.MBEDTLS_SSL_IS_SERVER,
276287 C.MBEDTLS_SSL_TRANSPORT_STREAM, C.MBEDTLS_SSL_PRESET_DEFAULT)
@@ -103,14 +103,26 @@ fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options i32)
103103
104104fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth i32)
105105
106+fn C.SSL_CTX_set_verify(ctx &C.SSL_CTX, mode i32, callback voidptr)
107+
106108fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, const_file &char, ca_path &char) i32
107109
108110fn C.SSL_CTX_free(ctx &C.SSL_CTX)
109111
110112fn C.SSL_CTX_use_certificate_file(ctx &C.SSL_CTX, const_file &char, file_type i32) i32
111113
114+fn C.SSL_CTX_use_certificate_chain_file(ctx &C.SSL_CTX, const_file &char) i32
115+
112116fn C.SSL_CTX_use_PrivateKey_file(ctx &C.SSL_CTX, const_file &char, file_type i32) i32
113117
118+fn C.v_net_openssl_SSL_CTX_use_certificate_chain_memory(ctx &C.SSL_CTX, data &u8, len usize) i32
119+
120+fn C.v_net_openssl_SSL_CTX_extra_chain_certs_count(ctx &C.SSL_CTX) int
121+
122+fn C.v_net_openssl_SSL_CTX_use_PrivateKey_memory(ctx &C.SSL_CTX, data &u8, len usize) i32
123+
124+fn C.v_net_openssl_SSL_CTX_load_verify_memory(ctx &C.SSL_CTX, data &u8, len usize) i32
125+
114126fn C.SSL_new(&C.SSL_CTX) &C.SSL
115127
116128fn C.SSL_set_fd(ssl &C.SSL, fd i32) i32
@@ -147,6 +159,8 @@ fn C.v_net_openssl_set_alpn_protos(ssl &C.SSL, protos &u8, protos_len u32) i32
147159
148160fn C.v_net_openssl_get0_alpn_selected(ssl &C.SSL, data voidptr, len &u32)
149161
162+fn C.v_net_openssl_SSL_CTX_set_alpn_select_protos(ctx &C.SSL_CTX, protos &u8, protos_len u32) i32
163+
150164fn C.SSL_shutdown(&C.SSL) i32
151165
152166fn C.SSL_free(&C.SSL)
@@ -159,8 +173,16 @@ fn C.SSLv23_client_method() &C.SSL_METHOD
159173
160174fn C.TLS_method() voidptr
161175
176+fn C.v_net_openssl_TLS_server_method() &C.SSL_METHOD
177+
162178fn C.TLSv1_2_method() voidptr
163179
180+fn C.SSL_CTX_check_private_key(ctx &C.SSL_CTX) i32
181+
182+fn C.SSL_accept(ssl &C.SSL) i32
183+
184+fn C.ERR_print_errors_fp(fp voidptr)
185+
164186fn C.v_net_openssl_init_ssl() i32
165187
166188fn init() {
@@ -1,3 +1,9 @@
1+#include <limits.h>
2+#include <stdlib.h>
3+#include <string.h>
4+#include <openssl/pem.h>
5+#include <openssl/x509_vfy.h>
6+
17// Match the init API to the OpenSSL headers that are actually available.
28#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \
39 || OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -11,6 +17,91 @@ static int v_net_openssl_init_ssl(void) {
1117}
1218#endif
1319
20+static int v_net_openssl_SSL_CTX_use_certificate_chain_memory(SSL_CTX *ctx,
21+ const unsigned char *data, size_t len) {
22+ if (len > INT_MAX) {
23+ return 0;
24+ }
25+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);
26+ if (bio == NULL) {
27+ return 0;
28+ }
29+ X509 *cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
30+ if (cert == NULL) {
31+ BIO_free(bio);
32+ return 0;
33+ }
34+ int result = SSL_CTX_use_certificate(ctx, cert);
35+ X509_free(cert);
36+ if (result != 1) {
37+ BIO_free(bio);
38+ return 0;
39+ }
40+ while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
41+ // SSL_CTX_add_extra_chain_cert takes ownership of cert on success.
42+ if (SSL_CTX_add_extra_chain_cert(ctx, cert) != 1) {
43+ X509_free(cert);
44+ BIO_free(bio);
45+ return 0;
46+ }
47+ }
48+ // Reaching the end of a PEM stream leaves PEM_R_NO_START_LINE queued.
49+ ERR_clear_error();
50+ BIO_free(bio);
51+ return 1;
52+}
53+
54+static int v_net_openssl_SSL_CTX_extra_chain_certs_count(SSL_CTX *ctx) {
55+ STACK_OF(X509) *chain = NULL;
56+ SSL_CTX_get_extra_chain_certs(ctx, &chain);
57+ return chain == NULL ? 0 : sk_X509_num(chain);
58+}
59+
60+static int v_net_openssl_SSL_CTX_use_PrivateKey_memory(SSL_CTX *ctx,
61+ const unsigned char *data, size_t len) {
62+ if (len > INT_MAX) {
63+ return 0;
64+ }
65+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);
66+ if (bio == NULL) {
67+ return 0;
68+ }
69+ EVP_PKEY *key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
70+ BIO_free(bio);
71+ if (key == NULL) {
72+ return 0;
73+ }
74+ int result = SSL_CTX_use_PrivateKey(ctx, key);
75+ EVP_PKEY_free(key);
76+ return result;
77+}
78+
79+static int v_net_openssl_SSL_CTX_load_verify_memory(SSL_CTX *ctx,
80+ const unsigned char *data, size_t len) {
81+ if (len > INT_MAX) {
82+ return 0;
83+ }
84+ BIO *bio = BIO_new_mem_buf((const void *)data, (int)len);
85+ if (bio == NULL) {
86+ return 0;
87+ }
88+ X509_STORE *store = SSL_CTX_get_cert_store(ctx);
89+ X509 *cert = NULL;
90+ int loaded = 0;
91+ while ((cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL)) != NULL) {
92+ if (X509_STORE_add_cert(store, cert) != 1) {
93+ X509_free(cert);
94+ BIO_free(bio);
95+ return 0;
96+ }
97+ X509_free(cert);
98+ loaded++;
99+ }
100+ ERR_clear_error();
101+ BIO_free(bio);
102+ return loaded > 0 ? 1 : 0;
103+}
104+
14105// SSL_get1_peer_certificate is only available in OpenSSL 3.x.
15106#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \
16107 || OPENSSL_VERSION_NUMBER < 0x30000000L
@@ -40,7 +131,70 @@ static void v_net_openssl_get0_alpn_selected(SSL *ssl, const unsigned char **dat
40131 *data = NULL;
41132 *len = 0;
42133}
134+static int v_net_openssl_SSL_CTX_set_alpn_select_protos(SSL_CTX *ctx,
135+ const unsigned char *protos, unsigned int protos_len) {
136+ (void)ctx;
137+ (void)protos;
138+ (void)protos_len;
139+ return -1;
140+}
43141#else
142+typedef struct {
143+ unsigned char *protos;
144+ unsigned int protos_len;
145+} v_net_openssl_alpn_select_state;
146+
147+static void v_net_openssl_free_alpn_select_state(void *parent, void *ptr,
148+ CRYPTO_EX_DATA *ad, int idx, long argl, void *argp) {
149+ (void)parent;
150+ (void)ad;
151+ (void)idx;
152+ (void)argl;
153+ (void)argp;
154+ v_net_openssl_alpn_select_state *selection =
155+ (v_net_openssl_alpn_select_state *)ptr;
156+ if (selection != NULL) {
157+ free(selection->protos);
158+ free(selection);
159+ }
160+}
161+
162+static int v_net_openssl_alpn_select_cb(SSL *ssl, const unsigned char **out,
163+ unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) {
164+ (void)ssl;
165+ v_net_openssl_alpn_select_state *state = (v_net_openssl_alpn_select_state *)arg;
166+ if (SSL_select_next_proto((unsigned char **)out, outlen, state->protos,
167+ state->protos_len, in, inlen) == OPENSSL_NPN_NEGOTIATED) {
168+ return SSL_TLSEXT_ERR_OK;
169+ }
170+ return SSL_TLSEXT_ERR_NOACK;
171+}
172+
173+static int v_net_openssl_SSL_CTX_set_alpn_select_protos(SSL_CTX *ctx,
174+ const unsigned char *protos, unsigned int protos_len) {
175+ v_net_openssl_alpn_select_state *selection =
176+ (v_net_openssl_alpn_select_state *)malloc(sizeof(*selection));
177+ if (selection == NULL) {
178+ return -1;
179+ }
180+ selection->protos = (unsigned char *)malloc(protos_len);
181+ if (selection->protos == NULL) {
182+ free(selection);
183+ return -1;
184+ }
185+ memcpy(selection->protos, protos, protos_len);
186+ selection->protos_len = protos_len;
187+ int state_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
188+ v_net_openssl_free_alpn_select_state);
189+ if (state_index < 0 || SSL_CTX_set_ex_data(ctx, state_index, selection) != 1) {
190+ free(selection->protos);
191+ free(selection);
192+ return -1;
193+ }
194+ SSL_CTX_set_alpn_select_cb(ctx, v_net_openssl_alpn_select_cb, selection);
195+ return 0;
196+}
197+
44198static int v_net_openssl_set_alpn_protos(SSL *ssl, const unsigned char *protos, unsigned int protos_len) {
45199 return SSL_set_alpn_protos(ssl, protos, protos_len);
46200}
@@ -58,3 +212,16 @@ static void v_net_openssl_get0_alpn_selected(SSL *ssl, const unsigned char **dat
58212#ifndef SSL_ERROR_WANT_ASYNC_JOB
59213#define SSL_ERROR_WANT_ASYNC_JOB 10
60214#endif
215+
216+// TLS_server_method() is only available in OpenSSL 1.1.0 and newer.
217+// On older OpenSSL/LibreSSL, fall back to SSLv23_server_method().
218+#if defined(LIBRESSL_VERSION_NUMBER) || !defined(OPENSSL_VERSION_NUMBER) \
219+ || OPENSSL_VERSION_NUMBER < 0x10100000L
220+static const SSL_METHOD *v_net_openssl_TLS_server_method(void) {
221+ return SSLv23_server_method();
222+}
223+#else
224+static const SSL_METHOD *v_net_openssl_TLS_server_method(void) {
225+ return TLS_server_method();
226+}
227+#endif
@@ -0,0 +1,60 @@
1+// vtest build: present_openssl? && !(windows && tinyc)
2+import time
3+import context
4+import net.openssl
5+
6+fn server() ! {
7+ cfg := openssl.SSLConnectConfig{
8+ cert: '-----BEGIN CERTIFICATE-----\nMIIEOTCCAyECFG64Q2g46jZb3kRbDOJWX/BwjSp6MA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwODAyMTcyOTQyWhgPMjA1MDEyMTcx\nNzI5NDJaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYD\nVQQHDAtMb3MgQW5nZWxlczEdMBsGA1UECgwUQ2F0YWx5c3QgRGV2ZWxvcG1lbnQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC\nggIBALqAI4fqUi+QBVWcsXglouLdOML5+w0+1hSR1KdO0Q5XPdQAs/yYWJ+KUkDw\nG++rfy9DUPq7FNRBVurXQkcAtn6gXdllGUSjwUiDo/N4mMOyS/2sufBuaeww7jVi\nrppH+zwP1tUnjRd6khl6bi1Ian9VSzr3Iy9CkXIg1GU4CPXkOydLeoQfepXxWoK1\nOUNwT3VKC/stAfY3j/NIIeiJYkyuRGFCkxn/BUjN+AsXiTugRcYKEFHdIPkOuCXp\nYbhf+lLsczpxCs3rdZG9b/N6mEDCzXTmeHkmsjdPTf+1k5DZZvKzVBBrgdxCgBb7\n5RwjF5v9WmnIc33wWgfJC6FaUzj9NYxYUbPHD+jTz0rJB/jj4u/xJlM/e5NRmXdW\n70pOMKXtWjRSolLOFIPKLY1qs3KMTAZxKKWPDDF7WlMJxMRt7nnnks5yw43Nog4C\njDLk1ZgETnPpLgo3jbmJdIv+OHKTJrBlVvDq7VTyixCoS5G8KoOmyQJhaXG6NwE2\niVhH5JIKgzgCfetfDsnjxqJ/qtrFXPa8FF2TsomD0NK/GZmIcs+9OeVB75Jn5uhF\nfLHScpiTbuu5w3P/LI/MqihLRB6RRNnRzPH8fIg5bYC9b770ta/8GcFRuYE8t+UR\nGtqXJoIKixbDlqV54kal8FQzYzhETf9+NM6Kb/lKEfG/pslvAgMBAAEwDQYJKoZI\nhvcNAQELBQADggEBALI3uNiNO0QE1brA3QYFK+d9ZroB72NrJ0UNkzYHDg2Fc6xg\n4aVVfaxY08+TmKc0JlMOW+pUxeCW/+UBSngdQiR9EE9xm0k0XIrAsy9RXxRvEtPu\nM1VI2h7ayp1Y2BrnQinevTSgtqLRyS1VbOFRl1FiyVvinw2I0KsDdAMNevAPXcOa\nQ8pUgUq6f56DkhocQaj+hxD/uV8HryNxuoSXnPhvfTN3z4YRGzsaWevJ9EYJliOM\n+XugcqfFJ+W7/QCEcAHCL+Bw6OydG5NFORr3p57PXjjcL/uKmxPBrWg2Bz6uT4uR\nMhj0zttiFHLAt9jGfyk6W57UNUja1e1ggftJJhs=\n-----END CERTIFICATE-----\n'
9+ cert_key: '-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----\n'
10+ validate: false
11+ in_memory_verification: true
12+ }
13+ mut srv := openssl.new_ssl_listener('127.0.0.1:64445', cfg) or {
14+ eprintln('Listen: ${err}')
15+ return err
16+ }
17+ eprintln('[+] Listening')
18+ mut cli := srv.accept() or {
19+ eprintln('Accept: ${err}')
20+ return err
21+ }
22+ eprintln('[+] Accepted connection')
23+ cli.shutdown()!
24+}
25+
26+// 用 _test.v 内的 test_ 函数
27+fn test_shutdown_does_not_panic() {
28+ _ := spawn server()
29+ time.sleep(1 * time.second)
30+ mut client := openssl.new_ssl_conn(validate: false)!
31+ client.dial('127.0.0.1', 64445) or {
32+ eprintln('Connect: ${err}')
33+ return
34+ }
35+ eprintln('[+] Connected')
36+ time.sleep(1 * time.second)
37+ mut background := context.background()
38+ mut ctx, cancel := context.with_timeout(mut background, 2 * time.second)
39+ spawn fn () {
40+ mut i := 0
41+ for {
42+ i++
43+ print('\r${i}...')
44+ flush_stdout()
45+ time.sleep(1 * time.second)
46+ }
47+ }()
48+ mut done := ctx.done()
49+ for {
50+ select {
51+ _ := <-done {
52+ break
53+ }
54+ }
55+ }
56+
57+ eprintln('\nTimeout without panic - OK')
58+
59+ assert true
60+}
@@ -70,7 +70,17 @@ fn ssl_remaining_timeout(deadline time.Time) time.Duration {
7070 if deadline.unix() == 0 {
7171 return net.infinite_timeout
7272 }
73- return deadline - time.now()
73+ remaining := deadline - time.now()
74+ if remaining <= 0 {
75+ // The finite deadline has already expired. Return a minimal positive
76+ // duration so select()/wait_for() perform an immediate, zero-length wait
77+ // and report net.err_timed_out, instead of misreading a non-positive
78+ // remaining time as net.infinite_timeout (== "wait forever"). Without
79+ // this, a peer that stalls mid-handshake/read could park the caller
80+ // indefinitely despite a finite timeout being configured.
81+ return time.nanosecond
82+ }
83+ return remaining
7484}
7585
7686// close closes the ssl connection and does cleanup
@@ -0,0 +1,200 @@
1+module openssl
2+
3+import net
4+
5+// SSLListener is the SSL listener implementation for OpenSSL.
6+pub struct SSLListener {
7+ saddr string
8+ config SSLConnectConfig
9+ options SSLListenerOptions
10+mut:
11+ tcp_listener &net.TcpListener = unsafe { nil }
12+ sslctx &C.SSL_CTX = unsafe { nil }
13+}
14+
15+// SSLListenerOptions configures the TCP listener used by an SSLListener.
16+@[params]
17+pub struct SSLListenerOptions {
18+pub:
19+ family net.AddrFamily = .ip
20+}
21+
22+// new_ssl_listener creates a new SSLListener binding to `saddr` with `config`.
23+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {
24+ mut listener := &SSLListener{
25+ saddr: saddr
26+ config: config
27+ options: options
28+ }
29+ listener.init()!
30+ return listener
31+}
32+
33+fn (mut l SSLListener) init() ! {
34+ if l.config.cert == '' || l.config.cert_key == '' {
35+ return error('net.openssl SSLListener.init, no certificate or key provided')
36+ }
37+ if l.config.validate && l.config.verify == '' {
38+ return error('net.openssl SSLListener.init, no root CA provided')
39+ }
40+
41+ l.tcp_listener = net.listen_tcp(l.options.family, l.saddr, net.ListenOptions{})!
42+
43+ l.sslctx = unsafe { C.SSL_CTX_new(C.v_net_openssl_TLS_server_method()) }
44+ if l.sslctx == 0 {
45+ l.tcp_listener.close() or {}
46+ return error('net.openssl SSLListener.init, could not get ssl context')
47+ }
48+
49+ if l.config.in_memory_verification {
50+ mut res := C.v_net_openssl_SSL_CTX_use_certificate_chain_memory(l.sslctx,
51+ l.config.cert.str, usize(l.config.cert.len))
52+ if res != 1 {
53+ l.shutdown() or {}
54+ return error('net.openssl SSLListener.init, could not load certificate from memory')
55+ }
56+ res = C.v_net_openssl_SSL_CTX_use_PrivateKey_memory(l.sslctx, l.config.cert_key.str,
57+ usize(l.config.cert_key.len))
58+ if res != 1 {
59+ l.shutdown() or {}
60+ return error('net.openssl SSLListener.init, could not load private key from memory')
61+ }
62+ if l.config.validate {
63+ res = C.v_net_openssl_SSL_CTX_load_verify_memory(l.sslctx, l.config.verify.str,
64+ usize(l.config.verify.len))
65+ if res != 1 {
66+ l.shutdown() or {}
67+ return error('net.openssl SSLListener.init, could not load root CA from memory')
68+ }
69+ }
70+ } else {
71+ mut res := C.SSL_CTX_use_certificate_chain_file(voidptr(l.sslctx), &char(l.config.cert.str))
72+ if res != 1 {
73+ C.ERR_print_errors_fp(C.stderr)
74+ l.shutdown() or {}
75+ return error('net.openssl SSLListener.init, SSL_CTX_use_certificate_chain_file failed')
76+ }
77+ res = C.SSL_CTX_use_PrivateKey_file(voidptr(l.sslctx), &char(l.config.cert_key.str),
78+ C.SSL_FILETYPE_PEM)
79+ if res != 1 {
80+ l.shutdown() or {}
81+ return error('net.openssl SSLListener.init, SSL_CTX_use_PrivateKey_file failed')
82+ }
83+ }
84+
85+ mut res := C.SSL_CTX_check_private_key(voidptr(l.sslctx))
86+ if res != 1 {
87+ l.shutdown() or {}
88+ return error('net.openssl SSLListener.init, SSL_CTX_check_private_key failed')
89+ }
90+
91+ if l.config.validate {
92+ if !l.config.in_memory_verification {
93+ res = C.SSL_CTX_load_verify_locations(voidptr(l.sslctx), &char(l.config.verify.str),
94+ unsafe { nil })
95+ if res != 1 {
96+ l.shutdown() or {}
97+ return error('net.openssl SSLListener.init, SSL_CTX_load_verify_locations failed')
98+ }
99+ }
100+ C.SSL_CTX_set_verify(voidptr(l.sslctx),
101+ C.SSL_VERIFY_PEER | C.SSL_VERIFY_FAIL_IF_NO_PEER_CERT, unsafe { nil })
102+ }
103+
104+ if l.config.alpn_protocols.len > 0 {
105+ mut wire := []u8{cap: 64}
106+ for proto in l.config.alpn_protocols {
107+ if proto.len == 0 || proto.len > 255 {
108+ l.shutdown() or {}
109+ return error('net.openssl SSLListener.init, invalid ALPN protocol "${proto}"')
110+ }
111+ wire << u8(proto.len)
112+ wire << proto.bytes()
113+ }
114+ if C.v_net_openssl_SSL_CTX_set_alpn_select_protos(l.sslctx, wire.data, u32(wire.len)) != 0 {
115+ l.shutdown() or {}
116+ return error('net.openssl SSLListener.init, failed to configure ALPN protocols (requires OpenSSL >= 1.0.2)')
117+ }
118+ }
119+}
120+
121+// accept accepts a new TCP connection and performs the SSL handshake.
122+pub fn (mut l SSLListener) accept() !&SSLConn {
123+ mut conn := l.accept_without_handshake()!
124+ conn.accept_handshake() or {
125+ conn.shutdown() or {}
126+ return err
127+ }
128+ return conn
129+}
130+
131+// accept_without_handshake accepts a new TCP connection but does not perform the handshake yet.
132+pub fn (mut l SSLListener) accept_without_handshake() !&SSLConn {
133+ mut tcp_conn := l.tcp_listener.accept()!
134+
135+ ssl := unsafe { &C.SSL(C.SSL_new(l.sslctx)) }
136+ if ssl == 0 {
137+ tcp_conn.close() or {}
138+ return error('net.openssl SSLListener.accept, could not create SSL instance')
139+ }
140+
141+ if C.SSL_set_fd(voidptr(ssl), tcp_conn.sock.handle) != 1 {
142+ C.SSL_free(voidptr(ssl))
143+ tcp_conn.close() or {}
144+ return error('net.openssl SSLListener.accept, could not assign ssl to socket')
145+ }
146+ // OpenSSL only returns SSL_ERROR_WANT_READ/WRITE while the socket is non-blocking.
147+ // The SSLConn retry loops use those results to enforce the configured deadline.
148+ net.set_blocking(tcp_conn.sock.handle, false) or {
149+ C.SSL_free(voidptr(ssl))
150+ tcp_conn.close() or {}
151+ return error('net.openssl SSLListener.accept, could not make socket non-blocking: ${err}')
152+ }
153+
154+ mut conn := &SSLConn{
155+ config: l.config
156+ sslctx: unsafe { nil } // 设为 nil 避免连接关闭时意外释放共享的 sslctx
157+ ssl: ssl
158+ handle: tcp_conn.sock.handle
159+ duration: tcp_conn.read_timeout()
160+ owns_socket: true
161+ }
162+
163+ return conn
164+}
165+
166+// accept_handshake performs the SSL handshake on the connection.
167+pub fn (mut conn SSLConn) accept_handshake() ! {
168+ // 执行 SSL 握手过程
169+ deadline := ssl_timeout_deadline(conn.duration)
170+ for {
171+ res := C.SSL_accept(voidptr(conn.ssl))
172+ if res == 1 {
173+ break
174+ }
175+
176+ err_res := ssl_error(res, conn.ssl)!
177+ if err_res == .ssl_error_want_read {
178+ conn.wait_for_read(ssl_remaining_timeout(deadline))!
179+ continue
180+ }
181+ if err_res == .ssl_error_want_write {
182+ conn.wait_for_write(ssl_remaining_timeout(deadline))!
183+ continue
184+ }
185+
186+ // 握手失败
187+ return error('net.openssl SSLListener.accept, SSL handshake failed: ${err_res}')
188+ }
189+}
190+
191+// shutdown shuts down the SSL listener and releases the context.
192+pub fn (mut l SSLListener) shutdown() ! {
193+ if l.sslctx != 0 {
194+ C.SSL_CTX_free(l.sslctx)
195+ l.sslctx = unsafe { nil }
196+ }
197+ if l.tcp_listener != unsafe { nil } {
198+ l.tcp_listener.close()!
199+ }
200+}
@@ -0,0 +1,146 @@
1+// vtest build: present_openssl? && !(windows && tinyc)
2+module openssl
3+
4+import net
5+import os
6+import time
7+
8+fn new_test_ssl_listener(address string, family net.AddrFamily) !&SSLListener {
9+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
10+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
11+ return new_ssl_listener(address, SSLConnectConfig{
12+ cert: cert_path
13+ cert_key: key_path
14+ },
15+ family: family
16+ )
17+}
18+
19+fn load_test_certificate_pem() !string {
20+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
21+ return os.read_file(cert_path)
22+}
23+
24+fn load_test_private_key_pem() !string {
25+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
26+ return os.read_file(key_path)
27+}
28+
29+fn accept_one_ssl_connection(mut listener SSLListener) {
30+ mut conn := listener.accept() or { return }
31+ conn.duration = 500 * time.millisecond
32+ mut buffer := []u8{len: 1}
33+ _ := conn.read(mut buffer) or { 0 }
34+ conn.shutdown() or {}
35+}
36+
37+fn close_tcp_connection_after(mut conn net.TcpConn, delay time.Duration) {
38+ time.sleep(delay)
39+ conn.close() or {}
40+}
41+
42+fn test_ssl_listener_honors_ipv6_family() ! {
43+ $if macos || linux {
44+ mut listener := new_test_ssl_listener('[::1]:0', .ip6)!
45+ defer {
46+ listener.shutdown() or {}
47+ }
48+ assert listener.tcp_listener.addr()!.family() == .ip6
49+ }
50+}
51+
52+fn test_ssl_listener_handshake_honors_timeout() ! {
53+ mut listener := new_test_ssl_listener('127.0.0.1:0', .ip)!
54+ defer {
55+ listener.shutdown() or {}
56+ }
57+ address := listener.tcp_listener.addr()!.str()
58+ mut tcp_client := net.dial_tcp(address)!
59+ close_thread := spawn close_tcp_connection_after(mut tcp_client, time.second)
60+
61+ mut ssl_conn := listener.accept_without_handshake()!
62+ defer {
63+ ssl_conn.shutdown() or {}
64+ }
65+ ssl_conn.duration = 100 * time.millisecond
66+ stopwatch := time.new_stopwatch()
67+ ssl_conn.accept_handshake() or {
68+ assert stopwatch.elapsed() < 500 * time.millisecond
69+ close_thread.wait()
70+ return
71+ }
72+ close_thread.wait()
73+ assert false, 'TLS handshake unexpectedly succeeded without receiving client TLS data'
74+}
75+
76+fn test_ssl_remaining_timeout_clamps_expired_finite_deadline() {
77+ // A finite deadline that has already expired must report an immediate
78+ // timeout (a minimal positive duration), not net.infinite_timeout, so that
79+ // select()/wait_for() return net.err_timed_out instead of blocking forever
80+ // after a peer stalls mid-handshake/read past the configured timeout.
81+ expired := time.now().add(-time.second)
82+ remaining := ssl_remaining_timeout(expired)
83+ assert remaining > 0
84+ assert remaining != net.infinite_timeout
85+ // An unset deadline still means "wait forever".
86+ assert ssl_remaining_timeout(time.unix(0)) == net.infinite_timeout
87+ // A live finite deadline returns its actual remaining time.
88+ live := ssl_remaining_timeout(time.now().add(5 * time.second))
89+ assert live > 4 * time.second
90+ assert live <= 5 * time.second
91+}
92+
93+fn test_ssl_listener_loads_in_memory_credentials_without_temp_files() ! {
94+ start := time.now().unix()
95+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{
96+ cert: load_test_certificate_pem()!
97+ cert_key: load_test_private_key_pem()!
98+ in_memory_verification: true
99+ })!
100+ listener.shutdown()!
101+ finish := time.now().unix()
102+ for timestamp in start .. finish + 1 {
103+ for name in ['v_srv_cert', 'v_srv_cert_key', 'v_srv_ca'] {
104+ assert !os.exists(os.join_path(os.temp_dir(), name + timestamp.str()))
105+ }
106+ }
107+}
108+
109+fn test_ssl_listener_file_credentials_load_full_certificate_chain() ! {
110+ cert_path := os.join_path(os.temp_dir(),
111+ 'v_openssl_listener_fullchain_${time.now().unix_nano()}.pem')
112+ os.write_file(cert_path, load_test_certificate_pem()! + load_test_certificate_pem()!)!
113+ defer {
114+ os.rm(cert_path) or {}
115+ }
116+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{
117+ cert: cert_path
118+ cert_key: os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
119+ })!
120+ defer {
121+ listener.shutdown() or {}
122+ }
123+ assert C.v_net_openssl_SSL_CTX_extra_chain_certs_count(listener.sslctx) == 1
124+}
125+
126+fn test_ssl_listener_negotiates_alpn() ! {
127+ mut listener := new_ssl_listener('127.0.0.1:0', SSLConnectConfig{
128+ cert: load_test_certificate_pem()!
129+ cert_key: load_test_private_key_pem()!
130+ in_memory_verification: true
131+ alpn_protocols: ['h2', 'http/1.1']
132+ })!
133+ defer {
134+ listener.shutdown() or {}
135+ }
136+ port := listener.tcp_listener.addr()!.port()!
137+ server_thread := spawn accept_one_ssl_connection(mut listener)
138+ mut client := new_ssl_conn(SSLConnectConfig{
139+ alpn_protocols: ['http/1.1']
140+ })!
141+ client.dial('127.0.0.1', port)!
142+ assert client.negotiated_alpn() == 'http/1.1'
143+ client.duration = 500 * time.millisecond
144+ client.shutdown() or {}
145+ server_thread.wait()
146+}
@@ -3,6 +3,8 @@ module ssl
33import net.openssl
44
55pub type SSLConn = openssl.SSLConn
6+pub type SSLListener = openssl.SSLListener
7+pub type SSLListenerOptions = openssl.SSLListenerOptions
68
79@[params]
810pub struct SSLConnectConfig {
@@ -13,3 +15,8 @@ pub struct SSLConnectConfig {
1315pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
1416 return openssl.new_ssl_conn(config.SSLConnectConfig) or { return err }
1517}
18+
19+// new_ssl_listener returns a new SSLListener with the given config.
20+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {
21+ return openssl.new_ssl_listener(saddr, config.SSLConnectConfig, options) or { return err }
22+}
@@ -0,0 +1,18 @@
1+module main
2+
3+import net.ssl
4+import os
5+
6+fn test_net_ssl_listener_preserves_ipv6_options() ! {
7+ $if macos || linux {
8+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
9+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
10+ mut listener := ssl.new_ssl_listener('[::1]:0', ssl.SSLConnectConfig{
11+ cert: cert_path
12+ cert_key: key_path
13+ }, ssl.SSLListenerOptions{
14+ family: .ip6
15+ })!
16+ listener.shutdown()!
17+ }
18+}
@@ -3,6 +3,8 @@ module ssl
33import net.mbedtls
44
55pub type SSLConn = mbedtls.SSLConn
6+pub type SSLListener = mbedtls.SSLListener
7+pub type SSLListenerOptions = mbedtls.SSLListenerOptions
68
79@[params]
810pub struct SSLConnectConfig {
@@ -13,3 +15,8 @@ pub struct SSLConnectConfig {
1315pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
1416 return mbedtls.new_ssl_conn(config.SSLConnectConfig) or { return err }
1517}
18+
19+// new_ssl_listener returns a new SSLListener with the given config.
20+pub fn new_ssl_listener(saddr string, config SSLConnectConfig, options SSLListenerOptions) !&SSLListener {
21+ return mbedtls.new_ssl_listener(saddr, config.SSLConnectConfig, options) or { return err }
22+}
@@ -0,0 +1,20 @@
1+// vtest build: present_openssl? && !(windows && tinyc)
2+// vtest vflags: -d use_openssl
3+module main
4+
5+import net.ssl
6+import os
7+
8+fn test_net_ssl_openssl_listener_preserves_ipv6_options() ! {
9+ $if macos || linux {
10+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
11+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
12+ mut listener := ssl.new_ssl_listener('[::1]:0', ssl.SSLConnectConfig{
13+ cert: cert_path
14+ cert_key: key_path
15+ }, ssl.SSLListenerOptions{
16+ family: .ip6
17+ })!
18+ listener.shutdown()!
19+ }
20+}
@@ -1,19 +1,12 @@
11module veb
22
3+import io
34import net
5+import net.http
6+import net.openssl
7+import os
48import time
59
6-@[params]
7-pub struct SSLConnectConfig {
8-pub:
9- verify string
10- cert string
11- cert_key string
12- validate bool
13- in_memory_verification bool
14- read_timeout time.Duration
15-}
16-
1710@[params]
1811pub struct RunParams {
1912pub:
@@ -26,9 +19,119 @@ pub:
2619 timeout_in_seconds int = 30
2720 max_request_buffer_size int = 8192
2821 benchmark_page_generation bool // for the "page rendered in X ms"
29- ssl_config SSLConnectConfig
22+ ssl_config openssl.SSLConnectConfig
23+}
24+
25+fn run_at_with_ssl[A, X](mut global_app A, params RunParams) ! {
26+ routes := generate_routes[A, X](global_app)!
27+ controllers_sorted := check_duplicate_routes_in_controllers[A](global_app, routes)!
28+ if params.show_startup_message {
29+ println('[veb] Running app on https://${startup_host(params)}:${params.port}/')
30+ }
31+ flush_stdout()
32+ mut ssl_listener := openssl.new_ssl_listener(listen_addr(params), params.ssl_config,
33+ family: params.family
34+ )!
35+ defer {
36+ ssl_listener.shutdown() or {}
37+ }
38+ ssl_params := &SslRequestParams{
39+ global_app: unsafe { voidptr(&global_app) }
40+ controllers_sorted: controllers_sorted
41+ routes: &routes
42+ benchmark_page_generation: params.benchmark_page_generation
43+ max_request_buffer_size: if params.max_request_buffer_size > 0 {
44+ params.max_request_buffer_size
45+ } else {
46+ max_read
47+ }
48+ }
49+ $if A is BeforeAcceptApp {
50+ global_app.before_accept_loop()
51+ }
52+ for {
53+ mut ssl_conn := ssl_listener.accept_without_handshake() or {
54+ eprintln('[veb] accept() failed, reason: ${err}; skipping')
55+ continue
56+ }
57+ ssl_conn.duration = params.timeout_in_seconds * time.second
58+ spawn handle_ssl_connection[A, X](mut ssl_conn, ssl_params)
59+ }
60+}
61+
62+fn handle_ssl_connection[A, X](mut ssl_conn openssl.SSLConn, params &SslRequestParams) {
63+ defer {
64+ ssl_conn.shutdown() or {}
65+ }
66+ ssl_conn.accept_handshake() or { return }
67+ mut reader := io.new_buffered_reader(
68+ reader: ssl_conn
69+ cap: params.max_request_buffer_size
70+ )
71+ defer {
72+ unsafe {
73+ reader.free()
74+ }
75+ }
76+ for {
77+ req := read_request_from_buffered_reader(mut reader) or {
78+ if err !is io.Eof {
79+ write_ssl_response(mut ssl_conn, http_400) or {}
80+ }
81+ return
82+ }
83+ completed_context := handle_ssl_request[A, X](req, params) or {
84+ write_ssl_response(mut ssl_conn, http_400) or {}
85+ return
86+ }
87+ if completed_context.takeover_mode != .none {
88+ eprintln('[veb] HTTPS connections do not support takeover connections yet; closing the connection after this response.')
89+ }
90+ write_ssl_context_response(mut ssl_conn, completed_context) or {
91+ eprintln('[veb] error sending HTTPS response: ${err}')
92+ return
93+ }
94+ if completed_context.takeover_mode != .none
95+ || should_close_connection(completed_context.req, completed_context.res, completed_context.client_wants_to_close) {
96+ return
97+ }
98+ }
99+}
100+
101+fn write_ssl_context_response(mut ssl_conn openssl.SSLConn, completed_context &Context) ! {
102+ if !completed_context.done && completed_context.return_type == .normal {
103+ return error('context did not send a response')
104+ }
105+ match completed_context.return_type {
106+ .normal {
107+ write_ssl_response(mut ssl_conn, completed_context.res)!
108+ }
109+ .file {
110+ write_ssl_response(mut ssl_conn, completed_context.res)!
111+ if completed_context.return_file == '' {
112+ return error('missing file response path')
113+ }
114+ mut file := os.open(completed_context.return_file)!
115+ defer {
116+ file.close()
117+ }
118+ mut buf := []u8{len: max_read}
119+ for {
120+ n := file.read(mut buf) or {
121+ if err is io.Eof {
122+ break
123+ }
124+ return err
125+ }
126+ if n <= 0 {
127+ break
128+ }
129+ ssl_conn.write(buf[..n])!
130+ }
131+ }
132+ }
30133}
31134
32-fn run_at_with_ssl[A, X](mut _global_app A, _params RunParams) ! {
33- return error('veb HTTPS server requires net.mbedtls; -d use_openssl only avoids mbedtls for HTTP apps')
135+fn write_ssl_response(mut ssl_conn openssl.SSLConn, resp http.Response) ! {
136+ ssl_conn.write(resp.bytes())!
34137}
@@ -29,7 +29,9 @@ fn run_at_with_ssl[A, X](mut global_app A, params RunParams) ! {
2929 println('[veb] Running app on https://${startup_host(params)}:${params.port}/')
3030 }
3131 flush_stdout()
32- mut ssl_listener := mbedtls.new_ssl_listener(listen_addr(params), params.ssl_config)!
32+ mut ssl_listener := mbedtls.new_ssl_listener(listen_addr(params), params.ssl_config,
33+ family: params.family
34+ )!
3335 defer {
3436 ssl_listener.shutdown() or {}
3537 }
@@ -1,4 +1,4 @@
1-// vtest build: !sanitized_job?
1+// vtest build: !sanitized_job? && !use_openssl?
22import net.http
33import net.mbedtls
44import os
@@ -11,9 +11,14 @@ pub struct Context {
1111}
1212
1313pub struct App {
14+ veb.Middleware[Context]
1415 started chan bool
1516}
1617
18+fn passthrough_middleware(mut _ctx Context) bool {
19+ return true
20+}
21+
1722pub fn (mut app App) before_accept_loop() {
1823 app.started <- true
1924}
@@ -22,10 +27,11 @@ pub fn (app &App) index(mut ctx Context) veb.Result {
2227 return ctx.text('secure')
2328}
2429
25-fn test_veb_serves_https_requests() {
30+fn test_veb_serves_https_requests() ! {
2631 cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
2732 key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
2833 mut app := &App{}
34+ app.use(handler: passthrough_middleware)
2935 spawn veb.run_at[App, Context](mut app,
3036 host: '127.0.0.1'
3137 port: https_port
@@ -0,0 +1,53 @@
1+// vtest build: present_openssl? && !sanitized_job? && !(windows && tinyc)
2+// vtest vflags: -d use_openssl
3+import net.http
4+import net.openssl
5+import os
6+import veb
7+
8+const https_port = 13014
9+
10+pub struct Context {
11+ veb.Context
12+}
13+
14+pub struct App {
15+ veb.Middleware[Context]
16+ started chan bool
17+}
18+
19+fn passthrough_middleware(mut _ctx Context) bool {
20+ return true
21+}
22+
23+pub fn (mut app App) before_accept_loop() {
24+ app.started <- true
25+}
26+
27+pub fn (app &App) index(mut ctx Context) veb.Result {
28+ return ctx.text('secure')
29+}
30+
31+fn test_veb_serves_https_requests() ! {
32+ cert_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.crt')
33+ key_path := os.join_path(@VMODROOT, 'examples', 'ssl_server', 'cert', 'server.key')
34+ mut app := &App{}
35+ app.use(handler: passthrough_middleware)
36+ spawn veb.run_at[App, Context](mut app,
37+ host: '127.0.0.1'
38+ port: https_port
39+ family: .ip
40+ timeout_in_seconds: 2
41+ ssl_config: openssl.SSLConnectConfig{
42+ cert: cert_path
43+ cert_key: key_path
44+ }
45+ )
46+ _ := <-app.started
47+ res := http.fetch(
48+ url: 'https://127.0.0.1:${https_port}/'
49+ validate: false
50+ )!
51+ assert res.status_code == 200
52+ assert res.body == 'secure'
53+}
@@ -349,6 +349,8 @@ fn handle_route[A, X](mut app A, mut user_context X, url urllib.URL, host string
349349 if !user_context.Context.done {
350350 validate_middleware[X](mut user_context, get_handlers_for_method(route.after_middlewares,
351351 user_context.Context.req.method))
352+ // Preserve an after-middleware response, or restore the handler response state.
353+ user_context.Context.done = user_context.Context.done || was_done
352354 }
353355 }
354356 }