option(CIVETWEB_SSL_OPENSSL_API_3_0 "Use the OpenSSL 3.0 API" OFF)
message(STATUS "Compile for OpenSSL 3.0 API - ${CIVETWEB_SSL_OPENSSL_API_3_0}")
+option(CIVETWEB_ENABLE_GNUTLS "Use the GnuTls" OFF)
+message(STATUS "SSL support (GnuTLS) - ${CIVETWEB_ENABLE_GNUTLS}")
+
option(CIVETWEB_ENABLE_MBEDTLS "Use the MbedTls" OFF)
-message(STATUS "SSL support - ${CIVETWEB_ENABLE_MBEDTLS}")
+message(STATUS "SSL support (MbedTLS) - ${CIVETWEB_ENABLE_MBEDTLS}")
# Dynamically load or link the SSL libraries
cmake_dependent_option(
endif()
if (NOT CIVETWEB_ENABLE_SSL)
add_definitions(-DNO_SSL)
+elseif (CIVETWEB_ENABLE_GNUTLS)
+ add_definitions(-DUSE_GNUTLS)
elseif (CIVETWEB_ENABLE_MBEDTLS)
add_definitions(-DUSE_MBEDTLS)
elseif (NOT CIVETWEB_ENABLE_SSL_DYNAMIC_LOADING)
ifdef NO_SSL
CFLAGS += -DNO_SSL
+else ifdef WITH_GNUTLS
+ CFLAGS += -DUSE_GNUTLS
+ LIBS += -lgnutls -lhogweed -lgmp -lnettle
else ifdef WITH_MBEDTLS
CFLAGS += -DUSE_MBEDTLS
LIBS += -lmbedcrypto -lmbedtls -lmbedx509
@echo " WITH_CPP=1 build library with c++ classes"
@echo " WITH_EXPERIMENTAL=1 build with experimental features"
@echo " WITH_DAEMONIZE=1 build with daemonize."
+ @echo " WITH_GNUTLS=1 build with GnuTLS support."
@echo " WITH_MBEDTLS=1 build with mbedTLS support."
@echo " WITH_OPENSSL_API_1_0=1 build with OpenSSL 1.0.x support."
@echo " WITH_OPENSSL_API_1_1=1 build with OpenSSL 1.1.x support."
[Mbed TLS](https://github.com/ARMmbed/mbedtls)
+[GNU TLS](https://gnutls.org)
+
Support
-------
| `SSL_ALREADY_INITIALIZED` | do not initialize libcrypto |
| `OPENSSL_API_1_0` | Use OpenSSL V1.0.x interface |
| `OPENSSL_API_1_1` | Use OpenSSL V1.1.x interface |
-| `OPENSSL_API_3_0` | Use OpenSSL V3.0.x interface |
-| `USE_MBEDTLS` | Use MbedTLS (cannot be combined with OPENSSL_API_*) |
+| `OPENSSL_API_3_0` | Use OpenSSL V3.0.x interface |
+| `USE_GNUTLS` | Use GnuTLS (cannot be combined with OPENSSL_API_* or USE_MBEDTLS) |
+| `USE_MBEDTLS` | Use MbedTLS (cannot be combined with OPENSSL_API_* or USE_GNUTLS) |
| | |
| `BUILD_DATE` | define as a string to be used as build id instead of __DATE__ |
| | |
(see [this mapping](https://testssl.sh/openssl-iana.mapping.html)).
In case CivetWeb is built with a TLS library other than OpenSSL
-(e.g., [mbedTLS](https://tls.mbed.org/supported-ssl-ciphersuites)),
+(e.g., [mbedTLS](https://tls.mbed.org/supported-ssl-ciphersuites)
+or [GnuTLS](https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html)),
the cipher names may be different.
### ssl\_default\_verify\_paths `yes`
--- /dev/null
+#### Use GnuTLS instead of OpenSSL
+=====
+
+1 [Build libgmp](https://gmplib.org)
+
+ - 1.1 [Download source](https://gmplib.org/#DOWNLOAD)
+ - 1.2 ./configure && make && make install
+
+2 [Build libhogweed and libnettle](https://www.lysator.liu.se/~nisse/nettle/)
+
+ - 2.1 [Download source](https://ftp.gnu.org/gnu/nettle/)
+ - 2.2 ./configure && make && make install
+
+3 Build civetweb
+
+ - make build WITH_GNUTLS=1
+
+4 Run civetweb
+ - export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH
+ - ./civetweb -listening_ports 8443s -ssl_certificate resources/cert/server.pem -document_root ./test/htmldir/
clang-format -i src/response.inl
clang-format -i src/http2.inl
clang-format -i src/mod_mbedtls.inl
+clang-format -i src/mod_gnutls.inl
clang-format -i src/third_party/civetweb_lua.h
# We need to link OpenSSL if not dynamically loading
if (CIVETWEB_ENABLE_SSL)
- if (CIVETWEB_ENABLE_MBEDTLS)
+ if (CIVETWEB_ENABLE_GNUTLS)
+ find_package(GnuTLS)
+ include_directories(${GNUTLS_INCLUDE_DIR})
+ message(STATUS "GnuTLS include directory: ${GNUTLS_INCLUDE_DIR}")
+ target_link_libraries(civetweb-c-library ${GNUTLS_LIBRARIES})
+ elseif (CIVETWEB_ENABLE_MBEDTLS)
find_package(MbedTLS)
include_directories(${MbedTLS_INCLUDE_DIR})
message(STATUS "MbedTLS include directory: ${MbedTLS_INCLUDE_DIR}")
static int mg_openssl_initialized = 0;
#endif
#if !defined(OPENSSL_API_1_0) && !defined(OPENSSL_API_1_1) \
- && !defined(OPENSSL_API_3_0) && !defined(USE_MBEDTLS)
-#error "Please define OPENSSL_API_#_# or USE_MBEDTLS"
+ && !defined(OPENSSL_API_3_0) && !defined(USE_MBEDTLS) \
+ && !defined(USE_GNUTLS)
+#error "Please define OPENSSL_API_#_# or USE_MBEDTLS or USE_GNUTLS"
#endif
#if defined(OPENSSL_API_1_0) && defined(OPENSSL_API_1_1)
#error "Multiple OPENSSL_API versions defined"
#endif
#if (defined(OPENSSL_API_1_0) || defined(OPENSSL_API_1_1) \
|| defined(OPENSSL_API_3_0)) \
- && defined(USE_MBEDTLS)
+ && (defined(USE_MBEDTLS) || defined(USE_GNUTLS))
+#error "Multiple SSL libraries defined"
+#endif
+#if defined(USE_MBEDTLS) && defined(USE_GNUTLS)
#error "Multiple SSL libraries defined"
#endif
#endif
#endif
-/* SSL: mbedTLS vs. no-ssl vs. OpenSSL */
+/* SSL: mbedTLS vs. GnuTLS vs. no-ssl vs. OpenSSL */
#if defined(USE_MBEDTLS)
/* mbedTLS */
#include "mod_mbedtls.inl"
+#elif defined(USE_GNUTLS)
+/* GnuTLS */
+#include "mod_gnutls.inl"
+
#elif defined(NO_SSL)
/* no SSL */
typedef struct SSL SSL; /* dummy for SSL argument to push/pull */
* versions. For the current definition, see
* mg_get_connection_info_impl */
#endif
-
SSL *ssl; /* SSL descriptor */
struct socket client; /* Connected client */
time_t conn_birth_time; /* Time (wall clock) when connection was
return -2;
}
-#if defined(NO_SSL) && !defined(USE_MBEDTLS)
+#if defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS)
if (ssl) {
return -2;
}
err = 0;
}
} else
+#elif defined(USE_GNUTLS)
+ if (ssl != NULL) {
+ n = gtls_ssl_write(ssl, (const unsigned char *)buf, (size_t) len);
+ if (n < 0) {
+ fprintf(stderr, "SSL write failed (%d): %s", n, gnutls_strerror(n));
+ return -2;
+ } else {
+ err = 0;
+ }
+ } else
#elif !defined(NO_SSL)
if (ssl != NULL) {
ERR_clear_error();
nread = 0;
}
+#elif defined(USE_GNUTLS)
+ } else if (conn->ssl != NULL) {
+ struct mg_pollfd pfd[2];
+ size_t to_read;
+ int pollres;
+ unsigned int num_sock = 1;
+
+ to_read = gnutls_record_check_pending(conn->ssl->sess);
+
+ if (to_read > 0) {
+ /* We already know there is no more data buffered in conn->buf
+ * but there is more available in the SSL layer. So don't poll
+ * conn->client.sock yet. */
+
+ pollres = 1;
+ if (to_read > (size_t)len)
+ to_read = (size_t)len;
+ } else {
+ pfd[0].fd = conn->client.sock;
+ pfd[0].events = POLLIN;
+
+ if (conn->phys_ctx->context_type == CONTEXT_SERVER) {
+ pfd[num_sock].fd =
+ conn->phys_ctx->thread_shutdown_notification_socket;
+ pfd[num_sock].events = POLLIN;
+ num_sock++;
+ }
+
+ to_read = (size_t)len;
+
+ pollres = mg_poll(pfd,
+ num_sock,
+ (int)(timeout * 1000.0),
+ &(conn->phys_ctx->stop_flag));
+
+ if (!STOP_FLAG_IS_ZERO(&conn->phys_ctx->stop_flag)) {
+ return -2;
+ }
+ }
+
+ if (pollres > 0) {
+ nread = gtls_ssl_read(conn->ssl, (unsigned char *)buf, to_read);
+ if (nread < 0) {
+ fprintf(stderr, "SSL read failed (%d): %s", nread, gnutls_strerror(nread));
+ return -2;
+ } else {
+ err = 0;
+ }
+ } else if (pollres < 0) {
+ /* Error */
+ return -2;
+ } else {
+ /* pollres = 0 means timeout */
+ nread = 0;
+ }
+
#elif !defined(NO_SSL)
} else if (conn->ssl != NULL) {
int ssl_pending;
return 0;
}
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(NO_SSL_DL)
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) && !defined(NO_SSL_DL)
#if defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0)
if (use_ssl && (TLS_client_method == NULL)) {
if (error != NULL) {
: 0;
}
+#elif defined(USE_GNUTLS)
+/* Check if SSL is required.
+ * If so, set up ctx->ssl_ctx pointer. */
+static int
+mg_sslctx_init(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx)
+{
+ if (!phys_ctx) {
+ return 0;
+ }
+
+ if (!dom_ctx) {
+ dom_ctx = &(phys_ctx->dd);
+ }
+
+ if (!is_ssl_port_used(dom_ctx->config[LISTENING_PORTS])) {
+ /* No SSL port is set. No need to setup SSL. */
+ return 1;
+ }
+
+ dom_ctx->ssl_ctx = (SSL_CTX *)mg_calloc(1, sizeof(*dom_ctx->ssl_ctx));
+ if (dom_ctx->ssl_ctx == NULL) {
+ fprintf(stderr, "ssl_ctx malloc failed\n");
+ return 0;
+ }
+
+ return gtls_sslctx_init(dom_ctx->ssl_ctx, dom_ctx->config[SSL_CERTIFICATE])
+ == 0
+ ? 1
+ : 0;
+}
+
#elif !defined(NO_SSL)
static int ssl_use_pem_file(struct mg_context *phys_ctx,
#endif /* OPENSSL_API_1_1 || OPENSSL_API_3_0 */
}
}
-#endif /* !defined(NO_SSL) && !defined(USE_MBEDTLS) */
+#endif /* !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) */
#if !defined(NO_FILESYSTEMS)
mbed_ssl_close(conn->ssl);
conn->ssl = NULL;
}
+#elif defined(USE_GNUTLS)
+ if (conn->ssl != NULL) {
+ gtls_ssl_close(conn->ssl);
+ conn->ssl = NULL;
+ }
#elif !defined(NO_SSL)
if (conn->ssl != NULL) {
/* Run SSL_shutdown twice to ensure completely close SSL connection
close_connection(conn);
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
if (((conn->phys_ctx->context_type == CONTEXT_HTTP_CLIENT)
|| (conn->phys_ctx->context_type == CONTEXT_WS_CLIENT))
&& (conn->phys_ctx->dd.ssl_ctx != NULL)) {
return NULL;
}
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
#if (defined(OPENSSL_API_1_1) || defined(OPENSSL_API_3_0)) \
&& !defined(NO_SSL_DL)
error->text_buffer_size,
"Can not create mutex");
}
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
SSL_CTX_free(conn->dom_ctx->ssl_ctx);
#endif
closesocket(sock);
return NULL;
}
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS) // TODO: mbedTLS client
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS) // TODO: mbedTLS client
if (use_ssl) {
/* TODO: Check ssl_verify_peer and ssl_ca_path here.
* SSL_CTX_set_verify call is needed to switch off server
close_connection(conn);
}
+#elif defined(USE_GNUTLS)
+ /* HTTPS connection */
+ if (gtls_ssl_accept(&(conn->ssl),
+ conn->dom_ctx->ssl_ctx,
+ conn->client.sock,
+ conn->phys_ctx)
+ == 0) {
+ /* conn->dom_ctx is set in get_request */
+ /* process HTTPS connection */
+ init_connection(conn);
+ conn->connection_type = CONNECTION_TYPE_REQUEST;
+ conn->protocol_type = PROTOCOL_TYPE_HTTP1;
+ process_new_connection(conn);
+ } else {
+ /* make sure the connection is cleaned up on SSL failure */
+ close_connection(conn);
+ }
+
#elif !defined(NO_SSL)
/* HTTPS connection */
if (sslize(conn, SSL_accept, NULL)) {
ctx->dd.ssl_ctx = NULL;
}
+#elif defined(USE_GNUTLS)
+ if (ctx->dd.ssl_ctx != NULL) {
+ gtls_sslctx_uninit(ctx->dd.ssl_ctx);
+ mg_free(ctx->dd.ssl_ctx);
+ ctx->dd.ssl_ctx = NULL;
+ }
+
#elif !defined(NO_SSL)
/* Deallocate SSL context */
if (ctx->dd.ssl_ctx != NULL) {
}
#endif
-#if defined(USE_MBEDTLS)
+#if defined(USE_MBEDTLS) || defined(USE_GNUTLS)
if (!mg_sslctx_init(ctx, NULL)) {
const char *err_msg = "Error initializing SSL context";
/* Fatal error - abort start. */
new_dom->shared_lua_websockets = NULL;
#endif
-#if !defined(NO_SSL) && !defined(USE_MBEDTLS)
+#if !defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS)
if (!init_ssl_ctx(ctx, new_dom)) {
/* Init SSL failed */
if (error != NULL) {
#if !defined(NO_FILES)
| MG_FEATURES_FILES
#endif
-#if !defined(NO_SSL) || defined(USE_MBEDTLS)
+#if !defined(NO_SSL) || defined(USE_MBEDTLS) || defined(USE_GNUTLS)
| MG_FEATURES_SSL
#endif
#if !defined(NO_CGI)
--- /dev/null
+#if defined(USE_GNUTLS) // USE_GNUTLS used with NO_SSL
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+typedef struct {
+ gnutls_session_t sess;
+} SSL;
+typedef struct {
+ gnutls_certificate_credentials_t cred;
+ gnutls_priority_t prio;
+} SSL_CTX;
+
+
+/* public api */
+CIVETWEB_API int gtls_sslctx_init(SSL_CTX *ctx, const char *crt);
+CIVETWEB_API void gtls_sslctx_uninit(SSL_CTX *ctx);
+CIVETWEB_API void gtls_ssl_close(SSL *ssl);
+CIVETWEB_API int gtls_ssl_accept(SSL **ssl,
+ SSL_CTX *ssl_ctx,
+ int sock,
+ struct mg_context *phys_ctx);
+CIVETWEB_API int gtls_ssl_read(SSL *ssl, unsigned char *buf, size_t len);
+CIVETWEB_API int gtls_ssl_write(SSL *ssl, const unsigned char *buf, size_t len);
+
+
+CIVETWEB_API int
+gtls_sslctx_init(SSL_CTX *ctx, const char *crt)
+{
+ int rc;
+
+ if (ctx == NULL || crt == NULL) {
+ return -1;
+ }
+
+ DEBUG_TRACE("%s", "Initializing GnuTLS SSL");
+
+ rc = gnutls_certificate_allocate_credentials(&ctx->cred);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("Failed to allocate credentials (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ rc = gnutls_priority_init(&ctx->prio, NULL, NULL);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("Failed to allocate priority cache (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ rc = gnutls_certificate_set_x509_key_file2(ctx->cred,
+ crt,
+ crt,
+ GNUTLS_X509_FMT_PEM,
+ NULL,
+ GNUTLS_PKCS_PLAIN
+ | GNUTLS_PKCS_NULL_PASSWORD);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("TLS parse crt/key file failed (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ return 0;
+
+failed:
+ gtls_sslctx_uninit(ctx);
+
+ return -1;
+}
+
+
+CIVETWEB_API void
+gtls_sslctx_uninit(SSL_CTX *ctx)
+{
+ if (ctx != NULL) {
+ gnutls_certificate_free_credentials(ctx->cred);
+ gnutls_priority_deinit(ctx->prio);
+ ctx->cred = NULL;
+ ctx->prio = NULL;
+ }
+}
+
+
+CIVETWEB_API int
+gtls_ssl_accept(SSL **ssl,
+ SSL_CTX *ssl_ctx,
+ int sock,
+ struct mg_context *phys_ctx)
+{
+ int rc;
+
+ if (ssl == NULL || ssl_ctx == NULL) {
+ return -1;
+ }
+
+ DEBUG_TRACE("TLS accept processing %p", ssl);
+
+ *ssl = (SSL *)mg_calloc_ctx(1, sizeof(SSL), phys_ctx);
+ if (*ssl == NULL) {
+ DEBUG_TRACE("Failed to allocate memory for session %zu", sizeof(SSL));
+ return -1;
+ }
+
+ rc = gnutls_init(&(*ssl)->sess, GNUTLS_SERVER);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("Failed to initialize session (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ rc = gnutls_priority_set((*ssl)->sess, ssl_ctx->prio);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("TLS set priortities failed (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ rc = gnutls_credentials_set((*ssl)->sess,
+ GNUTLS_CRD_CERTIFICATE,
+ ssl_ctx->cred);
+ if (rc != GNUTLS_E_SUCCESS) {
+ DEBUG_TRACE("TLS set credentials failed (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ goto failed;
+ }
+
+ gnutls_certificate_send_x509_rdn_sequence((*ssl)->sess, 1);
+ gnutls_certificate_server_set_request((*ssl)->sess, GNUTLS_CERT_IGNORE);
+ gnutls_handshake_set_timeout((*ssl)->sess,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ gnutls_transport_set_int((*ssl)->sess, sock);
+
+ while ((rc = gnutls_handshake((*ssl)->sess)) != GNUTLS_E_SUCCESS) {
+ if (gnutls_error_is_fatal(rc)) {
+ if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+ DEBUG_TRACE("TLS fatal alert received: %s",
+ gnutls_alert_get_name(
+ gnutls_alert_get((*ssl)->sess)));
+ } else {
+ DEBUG_TRACE("TLS handshake failed (%d): %s",
+ rc,
+ gnutls_strerror(rc));
+ }
+
+ goto failed;
+ }
+ }
+
+ DEBUG_TRACE("TLS connection %p accepted", *ssl);
+
+ return 0;
+
+failed:
+ gnutls_deinit((*ssl)->sess);
+ mg_free(*ssl);
+ *ssl = NULL;
+
+ return -1;
+}
+
+
+CIVETWEB_API void
+gtls_ssl_close(SSL *ssl)
+{
+ int rc;
+
+ if (ssl == NULL) {
+ return;
+ }
+
+ while ((rc = gnutls_bye(ssl->sess, GNUTLS_SHUT_RDWR)) != GNUTLS_E_SUCCESS) {
+ switch (rc) {
+ case GNUTLS_E_AGAIN: /* fall through */
+ case GNUTLS_E_INTERRUPTED:
+ continue;
+ default: /* should actually never happen */
+ break;
+ }
+ }
+
+ DEBUG_TRACE("TLS connection %p closed", ssl);
+ gnutls_deinit(ssl->sess);
+ mg_free(ssl);
+}
+
+
+CIVETWEB_API int
+gtls_ssl_read(SSL *ssl, unsigned char *buf, size_t len)
+{
+ ssize_t rc;
+
+ if (ssl == NULL) {
+ return GNUTLS_E_INVALID_SESSION;
+ }
+
+ while ((rc = gnutls_record_recv(ssl->sess, buf, len)) < 0) {
+ switch (rc) {
+ case GNUTLS_E_AGAIN: /* fall through */
+ case GNUTLS_E_INTERRUPTED:
+ continue;
+ default:
+ break;
+ }
+ }
+ /* DEBUG_TRACE("gnutls_record_recv: %d", rc); */
+ return (int)rc;
+}
+
+
+CIVETWEB_API int
+gtls_ssl_write(SSL *ssl, const unsigned char *buf, size_t len)
+{
+ ssize_t rc;
+
+ if (ssl == NULL) {
+ return GNUTLS_E_INVALID_SESSION;
+ }
+
+ while ((rc = gnutls_record_send(ssl->sess, buf, len)) < 0) {
+ switch (rc) {
+ case GNUTLS_E_AGAIN: /* fall through */
+ case GNUTLS_E_INTERRUPTED:
+ continue;
+ default:
+ break;
+ }
+ }
+ /* DEBUG_TRACE("gnutls_record_send: %d", rc); */
+ return (int)rc;
+}
+
+#endif /* USE_GNUTLS */