summaryrefslogtreecommitdiff
path: root/src/rexmpp_tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rexmpp_tls.c')
-rw-r--r--src/rexmpp_tls.c387
1 files changed, 387 insertions, 0 deletions
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c
new file mode 100644
index 0000000..bd464ce
--- /dev/null
+++ b/src/rexmpp_tls.c
@@ -0,0 +1,387 @@
+/**
+ @file rexmpp_tls.c
+ @brief TLS abstraction
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+
+#include "config.h"
+
+#if defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/x509.h>
+#include <gnutls/dane.h>
+#elif defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_tls.h"
+
+#if defined(USE_OPENSSL)
+rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, int ret) {
+ int err = SSL_get_error(s->tls.openssl_conn, ret);
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+ if (ret == 1) {
+ return REXMPP_TLS_SUCCESS;
+ } else if (err == SSL_ERROR_WANT_READ) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_WRITE;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "OpenSSL error %d", err);
+ return REXMPP_TLS_E_OTHER;
+ }
+}
+#endif
+
+int rexmpp_tls_init (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ int err;
+ s->tls.tls_session_data = NULL;
+ s->tls.tls_session_data_size = 0;
+
+ err = gnutls_certificate_allocate_credentials(&(s->tls.gnutls_cred));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
+ gnutls_strerror(err));
+ return 1;
+ }
+ err = gnutls_certificate_set_x509_system_trust(s->tls.gnutls_cred);
+ if (err < 0) {
+ rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s",
+ gnutls_strerror(err));
+ return 1;
+ }
+ return 0;
+#elif defined(USE_OPENSSL)
+ SSL_library_init();
+ SSL_load_error_strings();
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+ s->tls.openssl_conn = NULL;
+ s->tls.openssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (s->tls.openssl_ctx == NULL) {
+ rexmpp_log(s, LOG_CRIT, "OpenSSL context creation error");
+ return 1;
+ }
+ SSL_CTX_set_verify(s->tls.openssl_ctx, SSL_VERIFY_PEER, NULL);
+ if (SSL_CTX_set_default_verify_paths(s->tls.openssl_ctx) == 0) {
+ rexmpp_log(s, LOG_CRIT, "Failed to set default verify paths for OpenSSL context");
+ SSL_CTX_free(s->tls.openssl_ctx);
+ s->tls.openssl_ctx = NULL;
+ return 1;
+ }
+ return 0;
+#else
+ (void)s;
+ return 0;
+#endif
+}
+
+
+void rexmpp_tls_cleanup (rexmpp_t *s) {
+ if (s->tls_state != REXMPP_TLS_INACTIVE &&
+ s->tls_state != REXMPP_TLS_AWAITING_DIRECT) {
+#if defined(USE_GNUTLS)
+ gnutls_deinit(s->tls.gnutls_session);
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_conn != NULL) {
+ SSL_free(s->tls.openssl_conn);
+ s->tls.openssl_conn = NULL;
+ }
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+#else
+ (void)s;
+#endif
+ }
+}
+
+void rexmpp_tls_deinit (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ gnutls_certificate_free_credentials(s->tls.gnutls_cred);
+ if (s->tls.tls_session_data != NULL) {
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ }
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_ctx != NULL) {
+ SSL_CTX_free(s->tls.openssl_ctx);
+ }
+ s->tls.openssl_ctx = NULL;
+#else
+ (void)s;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_connect (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")};
+ rexmpp_log(s, LOG_DEBUG, "starting TLS");
+ gnutls_init(&s->tls.gnutls_session, GNUTLS_CLIENT);
+ gnutls_session_set_ptr(s->tls.gnutls_session, s);
+ gnutls_alpn_set_protocols(s->tls.gnutls_session, &xmpp_client_protocol, 1, 0);
+ gnutls_server_name_set(s->tls.gnutls_session, GNUTLS_NAME_DNS,
+ s->initial_jid.domain,
+ strlen(s->initial_jid.domain));
+ gnutls_set_default_priority(s->tls.gnutls_session);
+ gnutls_credentials_set(s->tls.gnutls_session, GNUTLS_CRD_CERTIFICATE,
+ s->tls.gnutls_cred);
+ gnutls_transport_set_int(s->tls.gnutls_session, s->server_socket);
+ gnutls_handshake_set_timeout(s->tls.gnutls_session,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ if (s->tls.tls_session_data != NULL) {
+ int ret = gnutls_session_set_data(s->tls.gnutls_session,
+ s->tls.tls_session_data,
+ s->tls.tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s",
+ gnutls_strerror(ret));
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ s->tls.tls_session_data_size = 0;
+ }
+ }
+ }
+
+ int ret = gnutls_handshake(s->tls.gnutls_session);
+ if (ret == GNUTLS_E_AGAIN) {
+ rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
+ return REXMPP_TLS_E_AGAIN;
+ } else if (ret == 0) {
+ int status;
+
+ int srv_is_secure = 0;
+ if (s->stream_state == REXMPP_STREAM_NONE &&
+ s->server_srv_tls != NULL) { /* Direct TLS */
+ srv_is_secure = s->server_srv_tls->secure;
+ } else if (s->stream_state != REXMPP_STREAM_NONE &&
+ s->server_srv != NULL) { /* STARTTLS connection */
+ srv_is_secure = s->server_srv->secure;
+ }
+
+ /* Check DANE TLSA records; experimental and purely informative
+ now, but may be nice to (optionally) rely on it in the
+ future. */
+ if ((srv_is_secure || s->manual_host != NULL) &&
+ s->server_socket_dns_secure) {
+ /* Apparently GnuTLS only checks against the target
+ server/derived host, while another possibility is a
+ service/source host
+ (<https://tools.ietf.org/html/rfc7712#section-5.1>,
+ <https://tools.ietf.org/html/rfc7673#section-6>). */
+ ret = dane_verify_session_crt(NULL, s->tls.gnutls_session, s->server_host,
+ "tcp", s->server_port, 0, 0, &status);
+ if (ret) {
+ rexmpp_log(s, LOG_WARNING, "DANE verification error: %s",
+ dane_strerror(ret));
+ } else if (status) {
+ if (status & DANE_VERIFY_CA_CONSTRAINTS_VIOLATED) {
+ rexmpp_log(s, LOG_WARNING, "The CA constraints were violated");
+ }
+ if (status & DANE_VERIFY_CERT_DIFFERS) {
+ rexmpp_log(s, LOG_WARNING, "The certificate obtained via DNS differs");
+ }
+ if (status & DANE_VERIFY_UNKNOWN_DANE_INFO) {
+ rexmpp_log(s, LOG_WARNING,
+ "No known DANE data was found in the DNS record");
+ }
+ } else {
+ rexmpp_log(s, LOG_INFO,
+ "DANE verification did not reject the certificate");
+ }
+ }
+
+ ret = gnutls_certificate_verify_peers3(s->tls.gnutls_session,
+ s->initial_jid.domain,
+ &status);
+ if (ret || status) {
+ if (ret) {
+ rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s",
+ gnutls_strerror(ret));
+ } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) {
+ rexmpp_log(s, LOG_ERR, "Unexpected certificate owner");
+ } else {
+ rexmpp_log(s, LOG_ERR, "Untrusted certificate");
+ }
+ gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR);
+ return REXMPP_TLS_E_OTHER;
+ }
+
+ if (gnutls_session_is_resumed(s->tls.gnutls_session)) {
+ rexmpp_log(s, LOG_INFO, "TLS session is resumed");
+ } else {
+ if (s->tls.tls_session_data != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ }
+ gnutls_session_get_data(s->tls.gnutls_session, NULL,
+ &s->tls.tls_session_data_size);
+ s->tls.tls_session_data = malloc(s->tls.tls_session_data_size);
+ ret = gnutls_session_get_data(s->tls.gnutls_session, s->tls.tls_session_data,
+ &s->tls.tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ s->tls.openssl_conn = SSL_new(s->tls.openssl_ctx);
+ if (s->tls.openssl_conn == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to create an OpenSSL connection object");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set_fd(s->tls.openssl_conn, s->server_socket) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a file descriptor for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set1_host(s->tls.openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ /* For SNI */
+ if (SSL_set_tlsext_host_name(s->tls.openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a tlsext hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+ return rexmpp_process_openssl_ret(s, SSL_connect(s->tls.openssl_conn));
+#else
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_disconnect (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ int ret = gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR);
+ if (ret == GNUTLS_E_SUCCESS) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to close TLS connection: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ int ret = SSL_shutdown(s->tls.openssl_conn);
+ if (ret == 0) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written)
+{
+#if defined(USE_GNUTLS)
+ *written = -1;
+ ssize_t ret = gnutls_record_send(s->tls.gnutls_session,
+ data,
+ data_size);
+ if (ret >= 0) {
+ *written = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ *written = -1;
+ int ret = SSL_write_ex(s->tls.openssl_conn, data, data_size, written);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)written;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) {
+#if defined(USE_GNUTLS)
+ *received = -1;
+ ssize_t ret = gnutls_record_recv(s->tls.gnutls_session, data, data_size);
+ if (ret >= 0) {
+ *received = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS recv error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ *received = -1;
+ int ret = SSL_read_ex(s->tls.openssl_conn, data, data_size, received);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)received;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+#if defined(USE_GNUTLS)
+ if (gnutls_record_get_direction(s->tls.gnutls_session) == 0) {
+ FD_SET(s->server_socket, read_fds);
+ } else {
+ FD_SET(s->server_socket, write_fds);
+ }
+ return s->server_socket + 1;
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_direction == REXMPP_OPENSSL_READ) {
+ FD_SET(s->server_socket, read_fds);
+ return s->server_socket + 1;
+ }
+ if (s->tls.openssl_direction == REXMPP_OPENSSL_WRITE) {
+ FD_SET(s->server_socket, write_fds);
+ return s->server_socket + 1;
+ }
+ return 0;
+#else
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+ return 0;
+#endif
+}