Browse Source

Add GnutTLS support.

Signed-off-by: catch-error <mirko.kraft@gmx.ch>
catch-error 6 months ago
parent
commit
cf1e77659d
10 changed files with 433 additions and 20 deletions
  1. 6 1
      CMakeLists.txt
  2. 4 0
      Makefile
  3. 2 0
      README.md
  4. 3 2
      docs/Building.md
  5. 2 1
      docs/UserManual.md
  6. 20 0
      docs/gnutls.md
  7. 1 0
      format.bat
  8. 6 1
      src/CMakeLists.txt
  9. 149 15
      src/civetweb.c
  10. 240 0
      src/mod_gnutls.inl

+ 6 - 1
CMakeLists.txt

@@ -237,8 +237,11 @@ message(STATUS "Compile for OpenSSL 1.1 API - ${CIVETWEB_SSL_OPENSSL_API_1_1}")
 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(
@@ -538,6 +541,8 @@ if (CIVETWEB_ENABLE_MEMORY_DEBUGGING)
 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)

+ 4 - 0
Makefile

@@ -97,6 +97,9 @@ endif
 
 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
@@ -303,6 +306,7 @@ help:
 	@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."

+ 2 - 0
README.md

@@ -134,6 +134,8 @@ simplicity by a carefully selected list of features:
 
 [Mbed TLS](https://github.com/ARMmbed/mbedtls)
 
+[GNU TLS](https://gnutls.org)
+
 
 Support
 -------

+ 3 - 2
docs/Building.md

@@ -179,8 +179,9 @@ make build COPT="-DNDEBUG -DNO_CGI"
 | `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__       |
 |                              |                                                                     |

+ 2 - 1
docs/UserManual.md

@@ -692,7 +692,8 @@ The OpenSSL cipher string uses different cipher names than IANA
 (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`

+ 20 - 0
docs/gnutls.md

@@ -0,0 +1,20 @@
+#### 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/

+ 1 - 0
format.bat

@@ -17,6 +17,7 @@ clang-format -i src/handle_form.inl
 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
 

+ 6 - 1
src/CMakeLists.txt

@@ -47,7 +47,12 @@ endif()
 
 # 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}")

+ 149 - 15
src/civetweb.c

@@ -1594,8 +1594,9 @@ static int mg_init_library_called = 0;
 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"
@@ -1608,7 +1609,10 @@ 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)
+    && (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
@@ -1773,11 +1777,15 @@ typedef int socklen_t;
 #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 */
@@ -2564,7 +2572,6 @@ struct mg_connection {
 	                 * 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
@@ -6131,7 +6138,7 @@ push_inner(struct mg_context *ctx,
 		return -2;
 	}
 
-#if defined(NO_SSL) && !defined(USE_MBEDTLS)
+#if defined(NO_SSL) && !defined(USE_MBEDTLS) && !defined(USE_GNUTLS)
 	if (ssl) {
 		return -2;
 	}
@@ -6157,6 +6164,16 @@ push_inner(struct mg_context *ctx,
 				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();
@@ -6413,6 +6430,62 @@ pull_inner(FILE *fp,
 			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;
@@ -9555,7 +9628,7 @@ connect_socket(
 		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) {
@@ -16660,6 +16733,37 @@ mg_sslctx_init(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx)
 	           : 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,
@@ -17935,7 +18039,7 @@ uninitialize_openssl(void)
 #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)
@@ -18207,6 +18311,11 @@ close_connection(struct mg_connection *conn)
 		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
@@ -18278,7 +18387,7 @@ mg_close_connection(struct mg_connection *conn)
 
 	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)) {
@@ -18380,7 +18489,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		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)
 
@@ -18457,7 +18566,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 			            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);
@@ -18465,7 +18574,7 @@ mg_connect_client_impl(const struct mg_client_options *client_options,
 		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
@@ -20186,6 +20295,24 @@ worker_thread_run(struct mg_connection *conn)
 				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)) {
@@ -20693,6 +20820,13 @@ free_context(struct mg_context *ctx)
 		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) {
@@ -21383,7 +21517,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 	}
 #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. */
@@ -21868,7 +22002,7 @@ mg_start_domain2(struct mg_context *ctx,
 	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) {
@@ -21947,7 +22081,7 @@ mg_check_feature(unsigned feature)
 #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)

+ 240 - 0
src/mod_gnutls.inl

@@ -0,0 +1,240 @@
+#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 */