diff --git a/examples/http3_client.c b/examples/http3_client.c index 217e5ce7..b8b47a3b 100644 --- a/examples/http3_client.c +++ b/examples/http3_client.c @@ -84,7 +84,7 @@ void on_stream_headers(us_quic_stream_t *s) { //print_current_headers(); /* Make a new stream */ - us_quic_socket_create_stream(us_quic_stream_socket(s)); + us_quic_socket_create_stream(us_quic_stream_socket(s), 0); } /* And this would be the body of the request */ @@ -109,7 +109,7 @@ void on_start(struct us_timer_t *t) { if (num_sockets < 10) { - us_quic_socket_t *connect_socket = us_quic_socket_context_connect(context, "::1", 9004); + us_quic_socket_t *connect_socket = us_quic_socket_context_connect(context, "::1", 9004, 0); } else { if (!ignore) { @@ -120,7 +120,7 @@ void on_start(struct us_timer_t *t) { printf("Starting now\n"); for (int i = 0; i < num_sockets; i++) { for (int j = 0; j < 32; j++) { - us_quic_socket_create_stream(sockets[i]); + us_quic_socket_create_stream(sockets[i], 0); } } } @@ -171,7 +171,7 @@ int main() { }; /* Create quic socket context (assumes h3 for now) */ - context = us_create_quic_socket_context(loop, options); + context = us_create_quic_socket_context(loop, options, 0); /* Specify application callbacks */ us_quic_socket_context_on_stream_data(context, on_stream_data); @@ -187,7 +187,7 @@ int main() { /* We also establish a client connection that sends requests */ //for (int i = 0; i < 4; i++) { - //us_quic_socket_t *connect_socket = us_quic_socket_context_connect(context, "::1", 9004); + //us_quic_socket_t *connect_socket = us_quic_socket_context_connect(context, "::1", 9004, 0); //} /* Run the event loop */ diff --git a/examples/http3_server.c b/examples/http3_server.c index a47f9363..9518e89f 100644 --- a/examples/http3_server.c +++ b/examples/http3_server.c @@ -44,11 +44,8 @@ void on_stream_headers(us_quic_stream_t *s) { //us_quic_socket_context_set_header(context, 2, "content-type", 12, "text/html", 9); us_quic_socket_context_send_headers(context, s, 1, 1); - /* Write body and shutdown (unknown if content-length must be present?) */ + /* Write body (unknown if content-length must be present?) */ us_quic_stream_write(s, "Hello quic!", 11); - - /* Every request has its own stream, so we conceptually serve requests like in HTTP 1.0 */ - us_quic_stream_shutdown(s); } /* And this would be the body of the request */ @@ -56,6 +53,12 @@ void on_stream_data(us_quic_stream_t *s, char *data, int length) { printf("Body length is: %d\n", length); } +/* Request finished */ +void on_stream_end(us_quic_stream_t *s) { + /* Every request has its own stream, so we conceptually serve requests like in HTTP 1.0 */ + us_quic_stream_shutdown(s); +} + void on_stream_writable(us_quic_stream_t *s) { } @@ -89,10 +92,11 @@ int main() { }; /* Create quic socket context (assumes h3 for now) */ - context = us_create_quic_socket_context(loop, options); + context = us_create_quic_socket_context(loop, options, 0); /* Specify application callbacks */ us_quic_socket_context_on_stream_data(context, on_stream_data); + us_quic_socket_context_on_stream_end(context, on_stream_end); us_quic_socket_context_on_stream_open(context, on_stream_open); us_quic_socket_context_on_stream_close(context, on_stream_close); us_quic_socket_context_on_stream_writable(context, on_stream_writable); @@ -101,7 +105,7 @@ int main() { us_quic_socket_context_on_close(context, on_close); /* The listening socket is the actual UDP socket used */ - us_quic_listen_socket_t *listen_socket = us_quic_socket_context_listen(context, "::1", 9004); + us_quic_listen_socket_t *listen_socket = us_quic_socket_context_listen(context, "::1", 9004, 0); /* Run the event loop */ us_loop_run(loop); diff --git a/misc/gen_test_certs.sh b/misc/gen_test_certs.sh old mode 100755 new mode 100644 diff --git a/misc/manual.md b/misc/manual.md index 13d3dc6c..66ea1646 100644 --- a/misc/manual.md +++ b/misc/manual.md @@ -37,18 +37,24 @@ WIN32_EXPORT long long us_loop_iteration_number(struct us_loop_t *loop); # us_socket_context_t - The per-behavior group of networking sockets ```c struct us_socket_context_options_t { - const char *key_file_name; - const char *cert_file_name; + union{ const char *key_file_name, *key_file; }; + union{ const char *cert_file_name, *cert_file; }; const char *passphrase; - const char *dh_params_file_name; - const char *ca_file_name; + union{ const char *dh_params_file_name, *dh_params_file; }; + union{ const char *ca_file_name, *ca_file; }; const char *ssl_ciphers; - int ssl_prefer_low_memory_usage; + char ssl_prefer_low_memory_usage; + char key_data_inline; + char cert_data_inline; + char dh_params_data_inline; }; /* A socket context holds shared callbacks and user data extension for associated sockets */ WIN32_EXPORT struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options); +/* Update socket context options, for example, to load a new certificate without creating a new socket */ +WIN32_EXPORT int us_update_socket_context(int ssl, struct us_socket_context_t* ctx, const struct us_socket_context_options_t* options); + /* Delete resources allocated at creation time. */ WIN32_EXPORT void us_socket_context_free(int ssl, struct us_socket_context_t *context); @@ -71,8 +77,17 @@ WIN32_EXPORT struct us_listen_socket_t *us_socket_context_listen(int ssl, struct /* listen_socket.c/.h */ WIN32_EXPORT void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls); +/* DNS lookup */ +WIN32_EXPORT struct addrinfo *us_get_addr(const char* host, int port); + +/* free data returned by us_get_addr() */ +WIN32_EXPORT void us_free_addr(struct addrinfo *addr); + /* Land in on_open or on_close or return null or return socket */ -WIN32_EXPORT struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size); +WIN32_EXPORT struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size); + +/* Same as above but use addrinfo object */ +WIN32_EXPORT struct us_socket_t *us_socket_context_connect_addr(int ssl, struct us_socket_context_t *context, const struct addrinfo *host, const const char *source_host, int options, int socket_ext_size); /* Returns the loop for this socket context. */ WIN32_EXPORT struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context); diff --git a/src/bsd.c b/src/bsd.c index b06b30d2..455a53c4 100644 --- a/src/bsd.c +++ b/src/bsd.c @@ -463,6 +463,25 @@ int bsd_would_block() { #endif } +struct addrinfo *us_get_addr(const char* host, int port){ + struct addrinfo hints, *result; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + char port_string[16]; + snprintf(port_string, 16, "%d", port); + + if (getaddrinfo(host, port_string, &hints, &result) != 0) { + return NULL; + } + return result; +} + +void us_free_addr(struct addrinfo *addr){ + freeaddrinfo(addr); +} + // return LIBUS_SOCKET_ERROR or the fd that represents listen socket // listen both on ipv6 and ipv4 LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket(const char *host, int port, int options) { @@ -709,6 +728,30 @@ int bsd_udp_packet_buffer_ecn(void *msgvec, int index) { return 0; // no ecn defaults to 0 } +LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket_addr(const struct addrinfo *host, const char *source_host, int options) { + + LIBUS_SOCKET_DESCRIPTOR fd = bsd_create_socket(host->ai_family, host->ai_socktype, host->ai_protocol); + if (fd == LIBUS_SOCKET_ERROR) { + return LIBUS_SOCKET_ERROR; + } + + if (source_host) { + struct addrinfo *interface_result; + if (!getaddrinfo(source_host, NULL, NULL, &interface_result)) { + int ret = bind(fd, interface_result->ai_addr, (socklen_t) interface_result->ai_addrlen); + freeaddrinfo(interface_result); + if (ret == LIBUS_SOCKET_ERROR) { + bsd_close_socket(fd); + return LIBUS_SOCKET_ERROR; + } + } + } + + connect(fd, host->ai_addr, (socklen_t) host->ai_addrlen); + + return fd; +} + LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, const char *source_host, int options) { struct addrinfo hints, *result; memset(&hints, 0, sizeof(struct addrinfo)); diff --git a/src/context.c b/src/context.c index 61e2f14d..c5f2cbf8 100644 --- a/src/context.c +++ b/src/context.c @@ -234,6 +234,15 @@ struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t * return context; } +int us_update_socket_context(int ssl, struct us_socket_context_t* ctx, const struct us_socket_context_options_t* options) { +#ifndef LIBUS_NO_SSL + if(ssl){ + return us_internal_update_ssl_socket_context((struct us_internal_ssl_socket_context_t*) ctx, options); + } +#endif + return 1; +} + void us_socket_context_free(int ssl, struct us_socket_context_t *context) { #ifndef LIBUS_NO_SSL if (ssl) { @@ -312,6 +321,35 @@ struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_sock return ls; } +struct us_socket_t *us_socket_context_connect_addr(int ssl, struct us_socket_context_t *context, const struct addrinfo *host, const char *source_host, int options, int socket_ext_size) { + #ifndef LIBUS_NO_SSL + if (ssl) { + return (struct us_socket_t *) us_internal_ssl_socket_context_connect_addr((struct us_internal_ssl_socket_context_t *) context, host, source_host, options, socket_ext_size); + } +#endif + + LIBUS_SOCKET_DESCRIPTOR connect_socket_fd = bsd_create_connect_socket_addr(host, source_host, options); + if (connect_socket_fd == LIBUS_SOCKET_ERROR) { + return 0; + } + + /* Connect sockets are semi-sockets just like listen sockets */ + struct us_poll_t *p = us_create_poll(context->loop, 0, sizeof(struct us_socket_t) + socket_ext_size); + us_poll_init(p, connect_socket_fd, POLL_TYPE_SEMI_SOCKET); + us_poll_start(p, context->loop, LIBUS_SOCKET_WRITABLE); + + struct us_socket_t *connect_socket = (struct us_socket_t *) p; + + /* Link it into context so that timeout fires properly */ + connect_socket->context = context; + connect_socket->timeout = 255; + connect_socket->long_timeout = 255; + connect_socket->low_prio_state = 0; + us_internal_socket_context_link_socket(context, connect_socket); + + return connect_socket; +} + struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size) { #ifndef LIBUS_NO_SSL if (ssl) { diff --git a/src/crypto/openssl.c b/src/crypto/openssl.c index 4473679a..2a35cf1f 100644 --- a/src/crypto/openssl.c +++ b/src/crypto/openssl.c @@ -43,6 +43,9 @@ void *sni_find(void *sni, const char *hostname); #include #endif +// SSL_*_pointer methods +#include "ssl_from_pointer.h" + struct loop_ssl_data { char *ssl_read_input, *ssl_read_output; unsigned int ssl_read_input_length; @@ -448,7 +451,7 @@ SSL_CTX *create_ssl_context_from_options(struct us_socket_context_options_t opti /* Important option for lowering memory usage, but lowers performance slightly */ if (options.ssl_prefer_low_memory_usage) { - SSL_CTX_set_mode(ssl_context, SSL_MODE_RELEASE_BUFFERS); + SSL_CTX_set_mode(ssl_context, SSL_MODE_RELEASE_BUFFERS); } if (options.passphrase) { @@ -458,50 +461,57 @@ SSL_CTX *create_ssl_context_from_options(struct us_socket_context_options_t opti SSL_CTX_set_default_passwd_cb(ssl_context, passphrase_cb); } - /* This one most probably do not need the cert_file_name string to be kept alive */ - if (options.cert_file_name) { - if (SSL_CTX_use_certificate_chain_file(ssl_context, options.cert_file_name) != 1) { + /* This one most probably do not need the cert_file string to be kept alive */ + if (options.cert_file) { + if ( + options.cert_data_inline ? + SSL_CTX_use_certificate_chain_pointer(ssl_context, options.cert_file, -1) != 1 + : SSL_CTX_use_certificate_chain_file(ssl_context, options.cert_file_name) != 1 + ) { free_ssl_context(ssl_context); return NULL; } } /* Same as above - we can discard this string afterwards I suppose */ - if (options.key_file_name) { - if (SSL_CTX_use_PrivateKey_file(ssl_context, options.key_file_name, SSL_FILETYPE_PEM) != 1) { + if (options.key_file) { + if ( + options.key_data_inline ? + SSL_CTX_use_PrivateKey_pointer(ssl_context, options.key_file, -1) != 1 + : SSL_CTX_use_PrivateKey_file(ssl_context, options.key_file_name, SSL_FILETYPE_PEM) != 1 + ) { free_ssl_context(ssl_context); return NULL; } } - if (options.ca_file_name) { - STACK_OF(X509_NAME) *ca_list; - ca_list = SSL_load_client_CA_file(options.ca_file_name); + if (options.ca_file) { + STACK_OF(X509_NAME) *ca_list = + options.cert_data_inline ? + SSL_load_client_CA_pointer(options.ca_file, -1) + : SSL_load_client_CA_file(options.ca_file_name); if(ca_list == NULL) { free_ssl_context(ssl_context); return NULL; } SSL_CTX_set_client_CA_list(ssl_context, ca_list); - if (SSL_CTX_load_verify_locations(ssl_context, options.ca_file_name, NULL) != 1) { + if ( + options.cert_data_inline ? + SSL_CTX_load_verify_pointer(ssl_context, options.ca_file, -1) != 1 + : SSL_CTX_load_verify_locations(ssl_context, options.ca_file_name, NULL) != 1 + ) { free_ssl_context(ssl_context); return NULL; } SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL); } - if (options.dh_params_file_name) { + if (options.dh_params_file) { /* Set up ephemeral DH parameters. */ - DH *dh_2048 = NULL; - FILE *paramfile; - paramfile = fopen(options.dh_params_file_name, "r"); - - if (paramfile) { - dh_2048 = PEM_read_DHparams(paramfile, NULL, NULL, NULL); - fclose(paramfile); - } else { - free_ssl_context(ssl_context); - return NULL; - } + DH *dh_2048 = + options.dh_params_data_inline ? + PEM_read_DHparams_pointer(options.dh_params_file, -1, NULL, NULL, NULL) + : PEM_read_DHparams_file(options.dh_params_file_name, NULL, NULL, NULL); if (dh_2048 == NULL) { free_ssl_context(ssl_context); @@ -534,6 +544,99 @@ SSL_CTX *create_ssl_context_from_options(struct us_socket_context_options_t opti return ssl_context; } +int us_internal_update_ssl_socket_context(struct us_internal_ssl_socket_context_t* ctx, const struct us_socket_context_options_t* options){ + /* Get the context */ + SSL_CTX *ssl_context = ctx->ssl_context; + + int ret = 1; + + if (options->ssl_prefer_low_memory_usage) { + SSL_CTX_set_mode(ssl_context, SSL_MODE_RELEASE_BUFFERS); + }else{ + SSL_CTX_clear_mode(ssl_context, SSL_MODE_RELEASE_BUFFERS); + } + + if (options->passphrase) { + /* Free old password */ + void *oldPassword = SSL_CTX_get_default_passwd_cb_userdata(ssl_context); + /* OpenSSL returns NULL if we have no set password */ + free(oldPassword); + + SSL_CTX_set_default_passwd_cb_userdata(ssl_context, (void *) strdup(options->passphrase)); + SSL_CTX_set_default_passwd_cb(ssl_context, passphrase_cb); + } + + /* This one most probably do not need the cert_file string to be kept alive */ + if (options->cert_file) { + if ( + options->cert_data_inline ? + SSL_CTX_use_certificate_chain_pointer(ssl_context, options->cert_file, -1) != 1 + : SSL_CTX_use_certificate_chain_file(ssl_context, options->cert_file_name) != 1 + ) ret = 0; + } + + /* Same as above - we can discard this string afterwards I suppose */ + if (options->key_file) { + if ( + options->key_data_inline ? + SSL_CTX_use_PrivateKey_pointer(ssl_context, options->key_file, -1) != 1 + : SSL_CTX_use_PrivateKey_file(ssl_context, options->key_file_name, SSL_FILETYPE_PEM) != 1 + ) ret = 0; + } + + if (options->ca_file) { + STACK_OF(X509_NAME) *ca_list = + options->cert_data_inline ? + SSL_load_client_CA_pointer(options->ca_file, -1) + : SSL_load_client_CA_file(options->ca_file_name); + if(ca_list == NULL){ + ret = 0; + goto ca_end; + } + SSL_CTX_set_client_CA_list(ssl_context, ca_list); + if ( + options->cert_data_inline ? + SSL_CTX_load_verify_pointer(ssl_context, options->ca_file, -1) != 1 + : SSL_CTX_load_verify_locations(ssl_context, options->ca_file_name, NULL) != 1 + ) { + ret = 0; + goto ca_end; + } + SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL); + } + ca_end: + + if (options->dh_params_file) { + /* Set up ephemeral DH parameters. */ + DH *dh_2048 = + options->dh_params_data_inline ? + PEM_read_DHparams_pointer(options->dh_params_file, -1, NULL, NULL, NULL) + : PEM_read_DHparams_file(options->dh_params_file_name, NULL, NULL, NULL); + + if (dh_2048 == NULL){ + ret = 0; + goto dh_end; + } + + const long set_tmp_dh = SSL_CTX_set_tmp_dh(ssl_context, dh_2048); + DH_free(dh_2048); + + if (set_tmp_dh != 1){ + ret = 0; + goto dh_end; + } + + /* OWASP Cipher String 'A+' (https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet) */ + if (SSL_CTX_set_cipher_list(ssl_context, "DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256") != 1) ret = 0; + } + dh_end: + + if (options->ssl_ciphers) { + if (SSL_CTX_set_cipher_list(ssl_context, options->ssl_ciphers) != 1) ret = 0; + } + return ret; +} + /* Returns a servername's userdata if any */ void *us_internal_ssl_socket_context_find_server_name_userdata(struct us_internal_ssl_socket_context_t *context, const char *hostname_pattern) { printf("finding %s\n", hostname_pattern); @@ -644,7 +747,7 @@ struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context(s return NULL; } - /* Otherwise ee continue by creating a non-SSL context, but with larger ext to hold our SSL stuff */ + /* Otherwise we continue by creating a non-SSL context, but with larger ext to hold our SSL stuff */ struct us_internal_ssl_socket_context_t *context = (struct us_internal_ssl_socket_context_t *) us_create_socket_context(0, loop, sizeof(struct us_internal_ssl_socket_context_t) + context_ext_size, options); /* I guess this is the only optional callback */ @@ -702,6 +805,9 @@ struct us_internal_ssl_socket_t *us_internal_ssl_adopt_accepted_socket(struct us struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect(struct us_internal_ssl_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size) { return (struct us_internal_ssl_socket_t *) us_socket_context_connect(0, &context->sc, host, port, source_host, options, sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + socket_ext_size); } +struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_addr(struct us_internal_ssl_socket_context_t *context, const struct addrinfo *host, const char *source_host, int options, int socket_ext_size) { + return (struct us_internal_ssl_socket_t *) us_socket_context_connect_addr(0, &context->sc, host, source_host, options, sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + socket_ext_size); +} struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_unix(struct us_internal_ssl_socket_context_t *context, const char *server_path, int options, int socket_ext_size) { return (struct us_internal_ssl_socket_t *) us_socket_context_connect_unix(0, &context->sc, server_path, options, sizeof(struct us_internal_ssl_socket_t) - sizeof(struct us_socket_t) + socket_ext_size); diff --git a/src/crypto/ssl_from_pointer.c b/src/crypto/ssl_from_pointer.c new file mode 100644 index 00000000..254dcfa9 --- /dev/null +++ b/src/crypto/ssl_from_pointer.c @@ -0,0 +1,122 @@ +/* + * Authored by Matthew Reiner, 2024. + * Intellectual property of third-party. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int SSL_CTX_use_certificate_chain_pointer(SSL_CTX *ctx, const char *file, int len){ + BIO *bio = BIO_new_mem_buf((void*)file, len); + if(!bio) return 0; + X509* cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + + // Load top cert. SSL_CTX_use_certificate() fails gracefully if cert == NULL + int ret = SSL_CTX_use_certificate(ctx, cert); + X509_free(cert); + if(ret != 1) goto end; + + // In case there was previously a certificate + SSL_CTX_clear_chain_certs(ctx); + + // Keep reading until there are no more + while((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL))){ + if((ret = SSL_CTX_add0_chain_cert(ctx, cert)) != 1){ + X509_free(cert); + goto end; + } + // If above succeeded then ownership of X509* cert is taken (refcount is not increased) + } + ret = 1; + end: + BIO_free(bio); + return ret; +} + +int SSL_CTX_use_PrivateKey_pointer(SSL_CTX *ctx, const char *file, int len){ + BIO *bio = BIO_new_mem_buf((void*)file, len); + if(!bio) return 0; + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL); + BIO_free(bio); + + // Load private key. SSL_CTX_use_PrivateKey() fails gracefully if cert == NULL + int ret = SSL_CTX_use_PrivateKey(ctx, pkey); + + // Always free, ownership of pkey is not taken + EVP_PKEY_free(pkey); + return ret; +} + +STACK_OF(X509_NAME) *SSL_load_client_CA_pointer(const char *file, int len){ + BIO *bio = BIO_new_mem_buf((void*)file, len); + if(!bio) return 0; + STACK_OF(X509_NAME) *ca_list = sk_X509_NAME_new_null(); + if(!ca_list) return 0; + X509 *x509; + + // Keep reading until there are no more + while((x509 = PEM_read_bio_X509(bio, NULL, 0, NULL))){ + X509_NAME *name = X509_get_subject_name(x509); + if(name && (name = X509_NAME_dup(name))) sk_X509_NAME_push(ca_list, name); + X509_free(x509); + } + BIO_free(bio); + + // Free & return if no CAs were added + if(!sk_X509_NAME_num(ca_list)){ + sk_X509_NAME_free(ca_list); + return NULL; + } + return ca_list; +} + +int SSL_CTX_load_verify_pointer(SSL_CTX *ctx, const char *file, int len){ + BIO *bio = BIO_new_mem_buf((void*)file, len); + if(!bio) return 0; + int ret; + X509 *cert; + // In case there was previously a certificate + SSL_CTX_clear_chain_certs(ctx); + // Load each certificate from the CA data and add to the trust store + while((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL))){ + if((ret = SSL_CTX_add0_chain_cert(ctx, cert)) != 1){ + X509_free(cert); + goto end; + } + // If above succeeded then ownership of X509* cert is taken (refcount is not increased) + } + ret = 1; + end: + BIO_free(bio); + return ret; +} + +DH* PEM_read_DHparams_file(const char *file, DH **x, pem_password_cb *cb, void *u){ + BIO *bio = BIO_new(BIO_s_file()); + if(!bio) return 0; + FILE* fp = fopen(file, "r"); + if(!fp) return 0; + BIO_set_fp(bio, fp, BIO_CLOSE); + DH* ret = PEM_read_bio_DHparams(bio, x, cb, u); + BIO_free(bio); + return ret; +} + +DH* PEM_read_DHparams_pointer(const char *file, int len, DH **x, pem_password_cb *cb, void *u){ + BIO *bio = BIO_new_mem_buf((void*)file, len); + if(!bio) return 0; + DH* ret = PEM_read_bio_DHparams(bio, x, cb, u); + BIO_free(bio); + return ret; +} \ No newline at end of file diff --git a/src/crypto/ssl_from_pointer.h b/src/crypto/ssl_from_pointer.h new file mode 100644 index 00000000..3084b099 --- /dev/null +++ b/src/crypto/ssl_from_pointer.h @@ -0,0 +1,32 @@ +/* + * Authored by Matthew Reiner, 2024. + * Intellectual property of third-party. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __INTELLISENSE__ +#include +#endif + +int SSL_CTX_use_certificate_chain_pointer(SSL_CTX *ctx, const char *file, int len); + +int SSL_CTX_use_PrivateKey_pointer(SSL_CTX *ctx, const char *file, int len); + +STACK_OF(X509_NAME) *SSL_load_client_CA_pointer(const char *file, int len); + +int SSL_CTX_load_verify_pointer(SSL_CTX *ctx, const char *file, int len); + +DH* PEM_read_DHparams_file(const char *file, DH **x, pem_password_cb *cb, void *u); + +DH* PEM_read_DHparams_pointer(const char *file, int len, DH **x, pem_password_cb *cb, void *u); \ No newline at end of file diff --git a/src/eventing/epoll_kqueue.c b/src/eventing/epoll_kqueue.c index c4444b7f..c2074b5c 100644 --- a/src/eventing/epoll_kqueue.c +++ b/src/eventing/epoll_kqueue.c @@ -287,11 +287,11 @@ unsigned int us_internal_accept_poll_event(struct us_poll_t *p) { /* Timer */ #ifdef LIBUS_USE_EPOLL struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) { - struct us_poll_t *p = us_create_poll(loop, fallthrough, sizeof(struct us_internal_callback_t) + ext_size); int timerfd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); if (timerfd == -1) { - return NULL; + return NULL; } + struct us_poll_t *p = us_create_poll(loop, fallthrough, sizeof(struct us_internal_callback_t) + ext_size); us_poll_init(p, timerfd, POLL_TYPE_CALLBACK); struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p; diff --git a/src/internal/internal.h b/src/internal/internal.h index ce7a24c2..7bf50fe9 100644 --- a/src/internal/internal.h +++ b/src/internal/internal.h @@ -164,6 +164,8 @@ void *us_internal_ssl_socket_context_get_native_handle(struct us_internal_ssl_so struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context(struct us_loop_t *loop, int context_ext_size, struct us_socket_context_options_t options); +int us_internal_update_ssl_socket_context(struct us_internal_ssl_socket_context_t* ctx, const struct us_socket_context_options_t* options); + void us_internal_ssl_socket_context_free(struct us_internal_ssl_socket_context_t *context); void us_internal_ssl_socket_context_on_open(struct us_internal_ssl_socket_context_t *context, struct us_internal_ssl_socket_t *(*on_open)(struct us_internal_ssl_socket_t *s, int is_client, char *ip, int ip_length)); @@ -201,6 +203,9 @@ struct us_internal_ssl_socket_t *us_internal_ssl_adopt_accepted_socket(struct us struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect(struct us_internal_ssl_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size); +struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_addr(struct us_internal_ssl_socket_context_t *context, + const struct addrinfo *host, const char *source_host, int options, int socket_ext_size); + struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_unix(struct us_internal_ssl_socket_context_t *context, const char *server_path, int options, int socket_ext_size); diff --git a/src/internal/networking/bsd.h b/src/internal/networking/bsd.h index 485784a7..1dc20fcd 100644 --- a/src/internal/networking/bsd.h +++ b/src/internal/networking/bsd.h @@ -104,6 +104,8 @@ LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port); LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, const char *source_host, int options); +LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket_addr(const struct addrinfo *host, const char *source_host, int options); + LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket_unix(const char *server_path, int options); #endif // BSD_H diff --git a/src/io_uring/io_context.c b/src/io_uring/io_context.c index e3d216e7..91e772f0 100644 --- a/src/io_uring/io_context.c +++ b/src/io_uring/io_context.c @@ -129,6 +129,15 @@ struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t * return context; } +int us_update_socket_context(int ssl, struct us_socket_context_t* ctx, const struct us_socket_context_options_t* options) { +#ifndef LIBUS_NO_SSL + if(ssl){ + return us_internal_update_ssl_socket_context((struct us_internal_ssl_socket_context_t*) ctx, options); + } +#endif + return 1; +} + void us_socket_context_free(int ssl, struct us_socket_context_t *context) { } @@ -181,8 +190,54 @@ struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_co struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_socket_context_t *context, const char *path, int options, int socket_ext_size) { return 0; } +static int num_sockets; + +struct us_socket_t *us_socket_context_connect_addr(int ssl, struct us_socket_context_t *context, const struct addrinfo *host, const char *source_host, int options, int socket_ext_size) { + + struct us_socket_t *s = malloc(sizeof(struct us_socket_t) + socket_ext_size); + s->context = context; + + s->timeout = 255; + s->long_timeout = 255; + + us_internal_socket_context_link_socket(context, s); + + LIBUS_SOCKET_DESCRIPTOR fd = bsd_create_socket(host->ai_family, host->ai_socktype, host->ai_protocol); + bsd_socket_nodelay(fd, 1); + + if (fd == LIBUS_SOCKET_ERROR) { + return NULL; + } + + if (source_host) { + struct addrinfo *interface_result; + if (!getaddrinfo(source_host, NULL, NULL, &interface_result)) { + int ret = bind(fd, interface_result->ai_addr, (socklen_t) interface_result->ai_addrlen); + freeaddrinfo(interface_result); + if (ret == LIBUS_SOCKET_ERROR) { + bsd_close_socket(fd); + return LIBUS_SOCKET_ERROR; + } + } + } + + struct io_uring_sqe *sqe = io_uring_get_sqe(&context->loop->ring); + io_uring_prep_connect(sqe, fd, host->ai_addr, (socklen_t) host->ai_addrlen); + + // register this file add + io_uring_register_files_update(&context->loop->ring, num_sockets, &fd, 1); + + s->dd = num_sockets++; + struct iovec iovecs = {s->sendBuf, 16 * 1024}; + //printf("register: %d\n", io_uring_register_buffers_update_tag(&context->loop->ring, s->dd, &iovecs, 0, 1)); + + io_uring_sqe_set_data(sqe, (char *)s + SOCKET_CONNECT); + + return s; +} + struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size) { @@ -230,26 +285,17 @@ struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_ struct io_uring_sqe *sqe = io_uring_get_sqe(&context->loop->ring); io_uring_prep_connect(sqe, fd, result->ai_addr, (socklen_t) result->ai_addrlen); - static int num_sockets; + freeaddrinfo(result); // register this file add io_uring_register_files_update(&context->loop->ring, num_sockets, &fd, 1); - - s->dd = num_sockets++; - struct iovec iovecs = {s->sendBuf, 16 * 1024}; //printf("register: %d\n", io_uring_register_buffers_update_tag(&context->loop->ring, s->dd, &iovecs, 0, 1)); - io_uring_sqe_set_data(sqe, (char *)s + SOCKET_CONNECT); - - - freeaddrinfo(result); - - return s; } diff --git a/src/io_uring/io_loop.c b/src/io_uring/io_loop.c index 6953b066..73d8173a 100644 --- a/src/io_uring/io_loop.c +++ b/src/io_uring/io_loop.c @@ -260,15 +260,15 @@ void us_loop_run(struct us_loop_t *loop) { #include struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) { - struct us_timer_t *timer = malloc(ext_size + sizeof(struct us_timer_t)); - - timer->loop = loop; - int timerfd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC); if (timerfd == -1) { - return NULL; + return NULL; } + struct us_timer_t *timer = malloc(ext_size + sizeof(struct us_timer_t)); + + timer->loop = loop; + timer->fd = timerfd; return timer; diff --git a/src/libusockets.h b/src/libusockets.h index dde4bb67..20690901 100644 --- a/src/libusockets.h +++ b/src/libusockets.h @@ -123,15 +123,17 @@ void us_timer_set(struct us_timer_t *timer, void (*cb)(struct us_timer_t *t), in struct us_loop_t *us_timer_loop(struct us_timer_t *t); /* Public interfaces for contexts */ - struct us_socket_context_options_t { - const char *key_file_name; - const char *cert_file_name; + union{ const char *key_file_name, *key_file; }; + union{ const char *cert_file_name, *cert_file; }; const char *passphrase; - const char *dh_params_file_name; - const char *ca_file_name; + union{ const char *dh_params_file_name, *dh_params_file; }; + union{ const char *ca_file_name, *ca_file; }; const char *ssl_ciphers; - int ssl_prefer_low_memory_usage; /* Todo: rename to prefer_low_memory_usage and apply for TCP as well */ + char ssl_prefer_low_memory_usage; /* Todo: rename to prefer_low_memory_usage and apply for TCP as well */ + char key_data_inline; + char cert_data_inline; + char dh_params_data_inline; }; /* Return 15-bit timestamp for this context */ @@ -151,6 +153,9 @@ void *us_socket_context_get_native_handle(int ssl, struct us_socket_context_t *c struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options); +/* Update socket context options, for example, to load a new certificate without creating a new socket */ +int us_update_socket_context(int ssl, struct us_socket_context_t* ctx, const struct us_socket_context_options_t* options); + /* Delete resources allocated at creation time. */ void us_socket_context_free(int ssl, struct us_socket_context_t *context); @@ -196,10 +201,19 @@ void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls); struct us_socket_t *us_adopt_accepted_socket(int ssl, struct us_socket_context_t *context, LIBUS_SOCKET_DESCRIPTOR client_fd, unsigned int socket_ext_size, char *addr_ip, int addr_ip_length); +/* DNS lookup */ +struct addrinfo *us_get_addr(const char* host, int port); + +/* free data returned by us_get_addr() */ +void us_free_addr(struct addrinfo *addr); + /* Land in on_open or on_connection_error or return null or return socket */ struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size); +struct us_socket_t *us_socket_context_connect_addr(int ssl, struct us_socket_context_t *context, + const struct addrinfo *host, const char *source_host, int options, int socket_ext_size); + struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_context_t *context, const char *server_path, int options, int socket_ext_size); diff --git a/src/quic.c b/src/quic.c index 6ed335be..db77effa 100644 --- a/src/quic.c +++ b/src/quic.c @@ -568,6 +568,9 @@ static void on_stream_close (lsquic_stream_t *s, lsquic_stream_ctx_t *h) { #include "openssl/ssl.h" +// SSL_*_pointer methods +#include "crypto/ssl_from_pointer.h" + static char s_alpn[0x100]; int add_alpn (const char *alpn) @@ -667,8 +670,12 @@ struct ssl_ctx_st *get_ssl_ctx(void *peer_ctx, const struct sockaddr *local) { printf("Key: %s\n", options->key_file_name); printf("Cert: %s\n", options->cert_file_name); - int a = SSL_CTX_use_certificate_chain_file(ctx, options->cert_file_name); - int b = SSL_CTX_use_PrivateKey_file(ctx, options->key_file_name, SSL_FILETYPE_PEM); + int a = options->cert_data_inline ? + SSL_CTX_use_certificate_chain_pointer(ctx, options->cert_file, -1) + : SSL_CTX_use_certificate_chain_file(ctx, options->cert_file_name); + int b = options->key_data_inline ? + SSL_CTX_use_PrivateKey_pointer(ctx, options->key_file, -1) + : SSL_CTX_use_PrivateKey_file(ctx, options->key_file_name, SSL_FILETYPE_PEM); printf("loaded cert and key? %d, %d\n", a, b); diff --git a/src/quic.h b/src/quic.h index 6d33d27b..1907dc82 100644 --- a/src/quic.h +++ b/src/quic.h @@ -8,9 +8,11 @@ #include "libusockets.h" typedef struct { - const char *cert_file_name; - const char *key_file_name; + union{ const char *cert_file_name, *cert_file; }; + union{ const char *key_file_name, *key_file; }; const char *passphrase; + char cert_data_inline; + char key_data_inline; } us_quic_socket_context_options_t;