From ccdd573e901a6f8bb3abae07a6b36254b0c43907 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Fri, 21 Nov 2025 19:24:21 +0100 Subject: [PATCH] Add HTTPS support --- Makefile | 2 +- chttp.c | 408 ++++++++++++++++++++++++++++++++++++++++--- chttp.h | 61 +++++-- src/includes.h | 4 + src/secure_context.c | 188 ++++++++++++++++++-- src/secure_context.h | 33 ++-- src/socket.c | 220 +++++++++++++++++++++-- src/socket.h | 24 ++- 8 files changed, 857 insertions(+), 83 deletions(-) diff --git a/Makefile b/Makefile index 3b7ecc9..dcf5f89 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ chttp.c chttp.h: $(wildcard src/*.c src/*.h) misc/amalg.py Makefile python misc/amalg.py example: main.c chttp.c chttp.h - gcc main.c chttp.c -o example -Wfatal-errors -lws2_32 + gcc main.c chttp.c -o example -Wfatal-errors -DHTTPS_ENABLED -lcrypto -lssl clean: rm chttp.c chttp.h diff --git a/chttp.c b/chttp.c index 077bf7b..0406db4 100644 --- a/chttp.c +++ b/chttp.c @@ -1475,38 +1475,206 @@ int mutex_unlock(Mutex *mutex) int global_secure_context_init(void) { - // TODO +#ifdef HTTPS_ENABLED + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +#endif + return 0; } int global_secure_context_free(void) { - // TODO +#ifdef HTTPS_ENABLED + EVP_cleanup(); +#endif + return 0; } int client_secure_context_init(ClientSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_client_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + SSL_CTX_set_verify(p, SSL_VERIFY_PEER, NULL); + + if (SSL_CTX_set_default_verify_paths(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ctx->p = p; + return 0; +#else + (void) ctx; + return -1; +#endif } -int client_secure_context_free(ClientSecureContext *ctx) +void client_secure_context_free(ClientSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); +#endif } -int server_secure_context_init(ServerSecureContext *ctx) +#ifdef HTTPS_ENABLED +static int servername_callback(SSL *ssl, int *ad, void *arg) { - // TODO + ServerSecureContext *ctx = arg; + + (void) ad; // TODO: use this? + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL) + return SSL_TLSEXT_ERR_NOACK; + + for (int i = 0; i < ctx->num_certs; i++) { + ServerCertificate *cert = &ctx->certs[i]; + if (!strcmp(cert->domain, servername)) { + SSL_set_SSL_CTX(ssl, cert->ctx); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} +#endif + +int server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file) +{ +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + SSL_CTX_set_tlsext_servername_callback(p, servername_callback); + SSL_CTX_set_tlsext_servername_arg(p, ctx); + + ctx->p = p; + ctx->num_certs = 0; + return 0; +#else + (void) ctx; + (void) cert_file; + (void) key_file; + return -1; +#endif } -int server_secure_context_free(ServerSecureContext *ctx) +void server_secure_context_free(ServerSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); + for (int i = 0; i < ctx->num_certs; i++) + SSL_CTX_free(ctx->certs[i].ctx); +#else + (void) ctx; +#endif } int server_secure_context_add_certificate(ServerSecureContext *ctx, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { - // TODO +#ifdef HTTPS_ENABLED + if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT) + return -1; + + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ServerCertificate *cert = &ctx->certs[ctx->num_certs]; + if (domain.len >= (int) sizeof(cert->domain)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert->domain, domain.ptr, domain.len); + cert->domain[domain.len] = '\0'; + cert->ctx = p; + ctx->num_certs++; + return 0; +#else + (void) ctx; + (void) domain; + (void) cert_file; + (void) key_file; + return -1; +#endif } //////////////////////////////////////////////////////////////////////////////////////// @@ -1703,8 +1871,8 @@ int socket_manager_listen_tcp(SocketManager *sm, } int socket_manager_listen_tls(SocketManager *sm, - HTTP_String addr, Port port, HTTP_String cert_file_name, - HTTP_String key_file_name) + HTTP_String addr, Port port, HTTP_String cert_file, + HTTP_String key_file) { if (sm->secure_sock != NATIVE_SOCKET_INVALID) return -1; @@ -1715,7 +1883,8 @@ int socket_manager_listen_tls(SocketManager *sm, if (sm->secure_sock == NATIVE_SOCKET_INVALID) return -1; - if (server_secure_context_init(&sm->server_secure_context) < 0) { + if (server_secure_context_init(&sm->server_secure_context, + cert_file, key_file) < 0) { CLOSE_NATIVE_SOCKET(sm->secure_sock); sm->secure_sock = NATIVE_SOCKET_INVALID; return -1; @@ -1741,7 +1910,7 @@ int socket_manager_add_certificate(SocketManager *sm, static bool is_secure(Socket *s) { #ifdef HTTPS_ENABLED - return (s->ssl != NULL); + return (s->server_secure_context != NULL); #else return false; #endif @@ -1783,6 +1952,24 @@ connect_failed_because_of_peer(void) return connect_failed_because_of_peer_2(err); } +static void free_addr_list(AddressAndPort *addrs, int num_addr) +{ +#ifdef HTTPS_ENABLED + for (int i = 0; i < num_addr; i++) { + RegisteredName *name = addrs[i].name; + if (name) { + assert(name->refs > 0); + name->refs--; + if (name->refs == 0) + free(name); + } + } +#else + (void) addrs; + (void) num_addr; +#endif +} + // This function moves the socket state machine // to the next state until an I/O event would // be required to continue. @@ -1811,6 +1998,13 @@ static void socket_update(Socket *s) if (s->sock != NATIVE_SOCKET_INVALID) { // This is not the first attempt +#ifdef HTTPS_ENABLED + if (s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +#endif + CLOSE_NATIVE_SOCKET(s->sock); s->next_addr++; @@ -1903,18 +2097,76 @@ static void socket_update(Socket *s) case SOCKET_STATE_CONNECTED: { - // We managed to connect to the peer. - // We can free the target array if it - // was allocated dynamically. - if (s->num_addr > 1) - free(s->addrs); - if (!is_secure(s)) { + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr > 1) + free(s->addrs); + s->events = 0; s->state = SOCKET_STATE_ESTABLISHED_READY; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + if (s->ssl == NULL) { + s->ssl = SSL_new(s->server_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + AddressAndPort addr; + if (s->num_addr > 1) + addr = s->addrs[s->next_addr]; + else + addr = s->addr; + + if (addr.name) + SSL_set_tlsext_host_name(s->ssl, addr.name->data); + } + + int ret = SSL_connect(s->ssl); + if (ret == 1) { + // Handshake done + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr == 1) + free_addr_list(&s->addr, 1); + else { + assert(s->num_addr > 1); + free_addr_list(s->addrs, s->num_addr); + free(s->addrs); + } + + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_PENDING; + s->events = 0; + again = true; #endif } } @@ -1927,7 +2179,45 @@ static void socket_update(Socket *s) s->events = 0; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + // Start server-side SSL handshake + if (!s->ssl) { + + s->ssl = SSL_new(s->server_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + } + + int ret = SSL_accept(s->ssl); + if (ret == 1) { + // Handshake done + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + // Server socket error - close the connection + s->state = SOCKET_STATE_DIED; + s->events = 0; #endif } } @@ -1945,7 +2235,26 @@ static void socket_update(Socket *s) s->events = 0; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + int ret = SSL_shutdown(s->ssl); + if (ret == 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_DIED; + s->events = 0; #endif } } @@ -2107,6 +2416,9 @@ static int socket_manager_translate_events_nolock( s->sock = sock; s->events = 0; s->user = NULL; +#ifdef HTTPS_ENABLED + s->ssl = NULL; +#endif socket_update(s); if (s->state == SOCKET_STATE_DIED) { @@ -2192,23 +2504,35 @@ static int resolve_connect_targets(ConnectTarget *targets, { char portstr[16]; int len = snprintf(portstr, sizeof(portstr), "%u", targets[i].port); - if (len < 0 || len >= (int) sizeof(portstr)) - return -1; + assert(len > 1 && len < (int) sizeof(portstr)); struct addrinfo hints = {0}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; +#ifdef HTTPS_ENABLED + RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1); + if (name == NULL) { + free_addr_list(resolved, num_resolved); + return -1; + } + name->refs = 0; + memcpy(name->data, targets[i].name.ptr, targets[i].name.len); + name->data[targets[i].name.len] = '\0'; + char *hostname = name->data; +#else char hostname[1<<9]; // TODO: Is this a good length? if (targets[i].name.len >= (int) sizeof(hostname)) return -1; memcpy(hostname, targets[i].name.ptr, targets[i].name.len); hostname[targets[i].name.len] = '\0'; - +#endif struct addrinfo *res = NULL; int ret = getaddrinfo(hostname, portstr, &hints, &res); - if (ret != 0) - return -1; + if (ret != 0) { + free_addr_list(resolved, num_resolved); + return -1; // TODO: free all allocated registered names + } for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { if (rp->ai_family == AF_INET) { @@ -2217,6 +2541,10 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = ipv4; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif num_resolved++; } } else if (rp->ai_family == AF_INET6) { @@ -2225,11 +2553,20 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = ipv6; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif num_resolved++; } } } +#ifdef HTTPS_ENABLED + if (name->refs == 0) + free(name); +#endif + freeaddrinfo(res); } break; @@ -2238,6 +2575,9 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = targets[i].ipv4; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif num_resolved++; } break; @@ -2246,6 +2586,9 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = targets[i].ipv6; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif num_resolved++; } break; @@ -2262,6 +2605,11 @@ int socket_connect(SocketManager *sm, int num_targets, if (sm->num_used == sm->max_used) return -1; +#ifndef HTTPS_ENABLED + if (secure) + return -1; +#endif + AddressAndPort resolved[MAX_CONNECT_TARGETS]; int num_resolved = resolve_connect_targets( targets, num_targets, resolved, MAX_CONNECT_TARGETS); @@ -2292,6 +2640,10 @@ int socket_connect(SocketManager *sm, int num_targets, s->state = SOCKET_STATE_PENDING; s->sock = NATIVE_SOCKET_INVALID; s->user = user; +#ifdef HTTPS_ENABLED + s->server_secure_context = &sm->server_secure_context; + s->ssl = NULL; +#endif sm->num_used++; return 0; } diff --git a/chttp.h b/chttp.h index aec2e08..52c6801 100644 --- a/chttp.h +++ b/chttp.h @@ -32,6 +32,10 @@ #include #endif +#ifdef HTTPS_ENABLED +#include +#endif + //////////////////////////////////////////////////////////////////////////////////////// // src/basic.h //////////////////////////////////////////////////////////////////////////////////////// @@ -204,33 +208,44 @@ int mutex_unlock(Mutex *mutex); // src/secure_context.h //////////////////////////////////////////////////////////////////////////////////////// +#ifndef SERVER_CERTIFICATE_LIMIT +// Maximum number of certificates that can be +// associated to a TLS server. This doesn't include +// the default certificate. +#define SERVER_CERTIFICATE_LIMIT 8 +#endif + int global_secure_context_init(void); int global_secure_context_free(void); typedef struct { #ifdef HTTPS_ENABLED - // TODO SSL_CTX *p; #endif } ClientSecureContext; -int client_secure_context_init(ClientSecureContext *ctx); -int client_secure_context_free(ClientSecureContext *ctx); - -typedef struct { - -} SecureDomain; +int client_secure_context_init(ClientSecureContext *ctx); +void client_secure_context_free(ClientSecureContext *ctx); + +typedef struct { +#ifdef HTTPS_ENABLED + char domain[128]; + SSL_CTX *ctx; +#endif +} ServerCertificate; typedef struct { #ifdef HTTPS_ENABLED - // TODO SSL_CTX *p; + int num_certs; + ServerCertificate certs[SERVER_CERTIFICATE_LIMIT]; #endif } ServerSecureContext; -int server_secure_context_init(ServerSecureContext *ctx); -int server_secure_context_free(ServerSecureContext *ctx); -int server_secure_context_add_certificate(ServerSecureContext *ctx, +int server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file); +void server_secure_context_free(ServerSecureContext *ctx); +int server_secure_context_add_certificate(ServerSecureContext *ctx, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); //////////////////////////////////////////////////////////////////////////////////////// @@ -366,6 +381,11 @@ typedef enum { SOCKET_STATE_DIED, } SocketState; +typedef struct { + int refs; + char data[]; +} RegisteredName; + // Internal use only typedef struct { union { @@ -374,6 +394,15 @@ typedef struct { }; bool is_ipv4; Port port; + +#ifdef HTTPS_ENABLED + // When connecting to a peer using TLS, if the address + // was resolved from a registered name, that name is + // used to request the correct certificate once the TCP + // handshake is established, and therefore need to + // store it somewhere until that happens. + RegisteredName *name; +#endif } AddressAndPort; // Internal use only @@ -405,6 +434,12 @@ typedef struct { AddressAndPort addr; // When num_addr=1 AddressAndPort *addrs; // Dynamically allocated when num_addr>1 }; + +#ifdef HTTPS_ENABLED + ServerSecureContext *server_secure_context; + SSL *ssl; +#endif + } Socket; // Glorified array of sockets. This structure @@ -476,8 +511,8 @@ int socket_manager_listen_tcp(SocketManager *sm, // and secure connections. // Returns 0 on success, -1 on error. int socket_manager_listen_tls(SocketManager *sm, - HTTP_String addr, Port port, HTTP_String cert_file_name, - HTTP_String key_file_name); + HTTP_String addr, Port port, HTTP_String cert_file, + HTTP_String key_file); // If the socket manager was configures to accept // TLS connections, this adds additional certificates diff --git a/src/includes.h b/src/includes.h index 0e68396..2911f92 100644 --- a/src/includes.h +++ b/src/includes.h @@ -23,3 +23,7 @@ #include #include #endif + +#ifdef HTTPS_ENABLED +#include +#endif diff --git a/src/secure_context.c b/src/secure_context.c index 52cb003..c129d0c 100644 --- a/src/secure_context.c +++ b/src/secure_context.c @@ -1,36 +1,204 @@ int global_secure_context_init(void) { - // TODO +#ifdef HTTPS_ENABLED + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +#endif + return 0; } int global_secure_context_free(void) { - // TODO +#ifdef HTTPS_ENABLED + EVP_cleanup(); +#endif + return 0; } int client_secure_context_init(ClientSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_client_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + SSL_CTX_set_verify(p, SSL_VERIFY_PEER, NULL); + + if (SSL_CTX_set_default_verify_paths(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ctx->p = p; + return 0; +#else + (void) ctx; + return -1; +#endif } -int client_secure_context_free(ClientSecureContext *ctx) +void client_secure_context_free(ClientSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); +#endif } -int server_secure_context_init(ServerSecureContext *ctx) +#ifdef HTTPS_ENABLED +static int servername_callback(SSL *ssl, int *ad, void *arg) { - // TODO + ServerSecureContext *ctx = arg; + + (void) ad; // TODO: use this? + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL) + return SSL_TLSEXT_ERR_NOACK; + + for (int i = 0; i < ctx->num_certs; i++) { + ServerCertificate *cert = &ctx->certs[i]; + if (!strcmp(cert->domain, servername)) { + SSL_set_SSL_CTX(ssl, cert->ctx); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} +#endif + +int server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file) +{ +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + // Copy private key file path to static buffer + char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + // Load certificate and private key + if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + SSL_CTX_set_tlsext_servername_callback(p, servername_callback); + SSL_CTX_set_tlsext_servername_arg(p, ctx); + + ctx->p = p; + ctx->num_certs = 0; + return 0; +#else + (void) ctx; + (void) cert_file; + (void) key_file; + return -1; +#endif } -int server_secure_context_free(ServerSecureContext *ctx) +void server_secure_context_free(ServerSecureContext *ctx) { - // TODO +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); + for (int i = 0; i < ctx->num_certs; i++) + SSL_CTX_free(ctx->certs[i].ctx); +#else + (void) ctx; +#endif } int server_secure_context_add_certificate(ServerSecureContext *ctx, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { - // TODO +#ifdef HTTPS_ENABLED + if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT) + return -1; + + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert_buffer, cert_file.ptr, cert_file.len); + cert_buffer[cert_file.len] = '\0'; + + char key_buffer[1024]; + if (key_file.len >= (int) sizeof(key_buffer)) { + SSL_CTX_free(p); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ServerCertificate *cert = &ctx->certs[ctx->num_certs]; + if (domain.len >= (int) sizeof(cert->domain)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert->domain, domain.ptr, domain.len); + cert->domain[domain.len] = '\0'; + cert->ctx = p; + ctx->num_certs++; + return 0; +#else + (void) ctx; + (void) domain; + (void) cert_file; + (void) key_file; + return -1; +#endif } diff --git a/src/secure_context.h b/src/secure_context.h index 96a1647..fd5f4f4 100644 --- a/src/secure_context.h +++ b/src/secure_context.h @@ -1,29 +1,40 @@ +#ifndef SERVER_CERTIFICATE_LIMIT +// Maximum number of certificates that can be +// associated to a TLS server. This doesn't include +// the default certificate. +#define SERVER_CERTIFICATE_LIMIT 8 +#endif + int global_secure_context_init(void); int global_secure_context_free(void); typedef struct { #ifdef HTTPS_ENABLED - // TODO SSL_CTX *p; #endif } ClientSecureContext; -int client_secure_context_init(ClientSecureContext *ctx); -int client_secure_context_free(ClientSecureContext *ctx); - -typedef struct { - -} SecureDomain; +int client_secure_context_init(ClientSecureContext *ctx); +void client_secure_context_free(ClientSecureContext *ctx); + +typedef struct { +#ifdef HTTPS_ENABLED + char domain[128]; + SSL_CTX *ctx; +#endif +} ServerCertificate; typedef struct { #ifdef HTTPS_ENABLED - // TODO SSL_CTX *p; + int num_certs; + ServerCertificate certs[SERVER_CERTIFICATE_LIMIT]; #endif } ServerSecureContext; -int server_secure_context_init(ServerSecureContext *ctx); -int server_secure_context_free(ServerSecureContext *ctx); -int server_secure_context_add_certificate(ServerSecureContext *ctx, +int server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file); +void server_secure_context_free(ServerSecureContext *ctx); +int server_secure_context_add_certificate(ServerSecureContext *ctx, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); diff --git a/src/socket.c b/src/socket.c index 413a1b1..aef801e 100644 --- a/src/socket.c +++ b/src/socket.c @@ -189,8 +189,8 @@ int socket_manager_listen_tcp(SocketManager *sm, } int socket_manager_listen_tls(SocketManager *sm, - HTTP_String addr, Port port, HTTP_String cert_file_name, - HTTP_String key_file_name) + HTTP_String addr, Port port, HTTP_String cert_file, + HTTP_String key_file) { if (sm->secure_sock != NATIVE_SOCKET_INVALID) return -1; @@ -201,7 +201,8 @@ int socket_manager_listen_tls(SocketManager *sm, if (sm->secure_sock == NATIVE_SOCKET_INVALID) return -1; - if (server_secure_context_init(&sm->server_secure_context) < 0) { + if (server_secure_context_init(&sm->server_secure_context, + cert_file, key_file) < 0) { CLOSE_NATIVE_SOCKET(sm->secure_sock); sm->secure_sock = NATIVE_SOCKET_INVALID; return -1; @@ -227,7 +228,7 @@ int socket_manager_add_certificate(SocketManager *sm, static bool is_secure(Socket *s) { #ifdef HTTPS_ENABLED - return (s->ssl != NULL); + return (s->server_secure_context != NULL); #else return false; #endif @@ -269,6 +270,24 @@ connect_failed_because_of_peer(void) return connect_failed_because_of_peer_2(err); } +static void free_addr_list(AddressAndPort *addrs, int num_addr) +{ +#ifdef HTTPS_ENABLED + for (int i = 0; i < num_addr; i++) { + RegisteredName *name = addrs[i].name; + if (name) { + assert(name->refs > 0); + name->refs--; + if (name->refs == 0) + free(name); + } + } +#else + (void) addrs; + (void) num_addr; +#endif +} + // This function moves the socket state machine // to the next state until an I/O event would // be required to continue. @@ -297,6 +316,13 @@ static void socket_update(Socket *s) if (s->sock != NATIVE_SOCKET_INVALID) { // This is not the first attempt +#ifdef HTTPS_ENABLED + if (s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +#endif + CLOSE_NATIVE_SOCKET(s->sock); s->next_addr++; @@ -389,18 +415,76 @@ static void socket_update(Socket *s) case SOCKET_STATE_CONNECTED: { - // We managed to connect to the peer. - // We can free the target array if it - // was allocated dynamically. - if (s->num_addr > 1) - free(s->addrs); - if (!is_secure(s)) { + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr > 1) + free(s->addrs); + s->events = 0; s->state = SOCKET_STATE_ESTABLISHED_READY; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + if (s->ssl == NULL) { + s->ssl = SSL_new(s->server_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + AddressAndPort addr; + if (s->num_addr > 1) + addr = s->addrs[s->next_addr]; + else + addr = s->addr; + + if (addr.name) + SSL_set_tlsext_host_name(s->ssl, addr.name->data); + } + + int ret = SSL_connect(s->ssl); + if (ret == 1) { + // Handshake done + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr == 1) + free_addr_list(&s->addr, 1); + else { + assert(s->num_addr > 1); + free_addr_list(s->addrs, s->num_addr); + free(s->addrs); + } + + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_PENDING; + s->events = 0; + again = true; #endif } } @@ -413,7 +497,45 @@ static void socket_update(Socket *s) s->events = 0; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + // Start server-side SSL handshake + if (!s->ssl) { + + s->ssl = SSL_new(s->server_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + } + + int ret = SSL_accept(s->ssl); + if (ret == 1) { + // Handshake done + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + // Server socket error - close the connection + s->state = SOCKET_STATE_DIED; + s->events = 0; #endif } } @@ -431,7 +553,26 @@ static void socket_update(Socket *s) s->events = 0; } else { #ifdef HTTPS_ENABLED - assert(0); // TODO + int ret = SSL_shutdown(s->ssl); + if (ret == 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_DIED; + s->events = 0; #endif } } @@ -593,6 +734,9 @@ static int socket_manager_translate_events_nolock( s->sock = sock; s->events = 0; s->user = NULL; +#ifdef HTTPS_ENABLED + s->ssl = NULL; +#endif socket_update(s); if (s->state == SOCKET_STATE_DIED) { @@ -678,23 +822,35 @@ static int resolve_connect_targets(ConnectTarget *targets, { char portstr[16]; int len = snprintf(portstr, sizeof(portstr), "%u", targets[i].port); - if (len < 0 || len >= (int) sizeof(portstr)) - return -1; + assert(len > 1 && len < (int) sizeof(portstr)); struct addrinfo hints = {0}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; +#ifdef HTTPS_ENABLED + RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1); + if (name == NULL) { + free_addr_list(resolved, num_resolved); + return -1; + } + name->refs = 0; + memcpy(name->data, targets[i].name.ptr, targets[i].name.len); + name->data[targets[i].name.len] = '\0'; + char *hostname = name->data; +#else char hostname[1<<9]; // TODO: Is this a good length? if (targets[i].name.len >= (int) sizeof(hostname)) return -1; memcpy(hostname, targets[i].name.ptr, targets[i].name.len); hostname[targets[i].name.len] = '\0'; - +#endif struct addrinfo *res = NULL; int ret = getaddrinfo(hostname, portstr, &hints, &res); - if (ret != 0) - return -1; + if (ret != 0) { + free_addr_list(resolved, num_resolved); + return -1; // TODO: free all allocated registered names + } for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { if (rp->ai_family == AF_INET) { @@ -703,6 +859,10 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = ipv4; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif num_resolved++; } } else if (rp->ai_family == AF_INET6) { @@ -711,11 +871,20 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = ipv6; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif num_resolved++; } } } +#ifdef HTTPS_ENABLED + if (name->refs == 0) + free(name); +#endif + freeaddrinfo(res); } break; @@ -724,6 +893,9 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = targets[i].ipv4; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif num_resolved++; } break; @@ -732,6 +904,9 @@ static int resolve_connect_targets(ConnectTarget *targets, resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = targets[i].ipv6; resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif num_resolved++; } break; @@ -748,6 +923,11 @@ int socket_connect(SocketManager *sm, int num_targets, if (sm->num_used == sm->max_used) return -1; +#ifndef HTTPS_ENABLED + if (secure) + return -1; +#endif + AddressAndPort resolved[MAX_CONNECT_TARGETS]; int num_resolved = resolve_connect_targets( targets, num_targets, resolved, MAX_CONNECT_TARGETS); @@ -778,6 +958,10 @@ int socket_connect(SocketManager *sm, int num_targets, s->state = SOCKET_STATE_PENDING; s->sock = NATIVE_SOCKET_INVALID; s->user = user; +#ifdef HTTPS_ENABLED + s->server_secure_context = &sm->server_secure_context; + s->ssl = NULL; +#endif sm->num_used++; return 0; } diff --git a/src/socket.h b/src/socket.h index 49c9094..049d3b1 100644 --- a/src/socket.h +++ b/src/socket.h @@ -128,6 +128,11 @@ typedef enum { SOCKET_STATE_DIED, } SocketState; +typedef struct { + int refs; + char data[]; +} RegisteredName; + // Internal use only typedef struct { union { @@ -136,6 +141,15 @@ typedef struct { }; bool is_ipv4; Port port; + +#ifdef HTTPS_ENABLED + // When connecting to a peer using TLS, if the address + // was resolved from a registered name, that name is + // used to request the correct certificate once the TCP + // handshake is established, and therefore need to + // store it somewhere until that happens. + RegisteredName *name; +#endif } AddressAndPort; // Internal use only @@ -167,6 +181,12 @@ typedef struct { AddressAndPort addr; // When num_addr=1 AddressAndPort *addrs; // Dynamically allocated when num_addr>1 }; + +#ifdef HTTPS_ENABLED + ServerSecureContext *server_secure_context; + SSL *ssl; +#endif + } Socket; // Glorified array of sockets. This structure @@ -238,8 +258,8 @@ int socket_manager_listen_tcp(SocketManager *sm, // and secure connections. // Returns 0 on success, -1 on error. int socket_manager_listen_tls(SocketManager *sm, - HTTP_String addr, Port port, HTTP_String cert_file_name, - HTTP_String key_file_name); + HTTP_String addr, Port port, HTTP_String cert_file, + HTTP_String key_file); // If the socket manager was configures to accept // TLS connections, this adds additional certificates