diff options
Diffstat (limited to 'src/rexmpp_tls.c')
-rw-r--r-- | src/rexmpp_tls.c | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c new file mode 100644 index 0000000..2a7c903 --- /dev/null +++ b/src/rexmpp_tls.c @@ -0,0 +1,987 @@ +/** + @file rexmpp_tls.c + @brief TLS abstraction + @author defanor <defanor@uberspace.net> + @date 2021 + @copyright MIT license. +*/ + +#include <syslog.h> +#include <string.h> +#include <stdlib.h> + +#include "config.h" + +#if defined(USE_GNUTLS) +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> +#include <gnutls/dane.h> +#include <gnutls/dtls.h> +#elif defined(USE_OPENSSL) +#include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/err.h> +#endif + +#include "rexmpp.h" +#include "rexmpp_digest.h" +#include "rexmpp_tls.h" + +rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p); +ssize_t +rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size); +int rexmpp_jingle_dtls_pull_timeout_func (void *p, + unsigned int ms); + +#if defined(USE_OPENSSL) +rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *func, + int ret) +{ + int err = SSL_get_error(tls_ctx->openssl_conn, ret); + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; + if (ret == 1) { + return REXMPP_TLS_SUCCESS; + } else if (err == SSL_ERROR_WANT_READ) { + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; + return REXMPP_TLS_E_AGAIN; + } else if (err == SSL_ERROR_WANT_WRITE) { + tls_ctx->openssl_direction = REXMPP_OPENSSL_WRITE; + return REXMPP_TLS_E_AGAIN; + } else { + rexmpp_log(s, LOG_ERR, "OpenSSL error %d (ret %d) in %s", + err, ret, func); + ERR_print_errors_fp(stderr); + return REXMPP_TLS_E_OTHER; + } +} +#endif + +rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls) { + rexmpp_tls_t *tls_ctx = malloc(sizeof(rexmpp_tls_t)); + if (tls_ctx == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to allocate memory for a TLS context"); + return NULL; + } +#if defined(USE_GNUTLS) + (void)dtls; + int err; + tls_ctx->tls_session_data = NULL; + tls_ctx->tls_session_data_size = 0; + + err = gnutls_certificate_allocate_credentials(&(tls_ctx->gnutls_cred)); + if (err) { + rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", + gnutls_strerror(err)); + free(tls_ctx); + return NULL; + } + if (! dtls) { + err = gnutls_certificate_set_x509_system_trust(tls_ctx->gnutls_cred); + } + if (err < 0) { + rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s", + gnutls_strerror(err)); + free(tls_ctx); + return NULL; + } + + tls_ctx->dtls_buf_len = 0; +#elif defined(USE_OPENSSL) + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; + tls_ctx->openssl_conn = NULL; + tls_ctx->openssl_ctx = SSL_CTX_new(dtls + ? DTLS_method() + : TLS_method()); + if (tls_ctx->openssl_ctx == NULL) { + rexmpp_log(s, LOG_CRIT, "OpenSSL context creation error"); + free(tls_ctx); + return NULL; + } + SSL_CTX_set_verify(tls_ctx->openssl_ctx, SSL_VERIFY_PEER, NULL); + if (SSL_CTX_set_default_verify_paths(tls_ctx->openssl_ctx) == 0) { + rexmpp_log(s, LOG_CRIT, + "Failed to set default verify paths for OpenSSL context"); + SSL_CTX_free(tls_ctx->openssl_ctx); + tls_ctx->openssl_ctx = NULL; + free(tls_ctx); + return NULL; + } +#else + (void)s; + (void)dtls; +#endif + return tls_ctx; +} + +void rexmpp_tls_ctx_free (rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + gnutls_certificate_free_credentials(tls_ctx->gnutls_cred); + if (tls_ctx->tls_session_data != NULL) { + free(tls_ctx->tls_session_data); + tls_ctx->tls_session_data = NULL; + } +#elif defined(USE_OPENSSL) + if (tls_ctx->openssl_ctx != NULL) { + SSL_CTX_free(tls_ctx->openssl_ctx); + } + tls_ctx->openssl_ctx = NULL; +#endif + free(tls_ctx); +} + +int rexmpp_tls_init (rexmpp_t *s) { +#if defined(USE_OPENSSL) + SSL_library_init(); + SSL_load_error_strings(); +#endif + s->tls = rexmpp_tls_ctx_new(s, 0); + return (s->tls == NULL); +} + +void rexmpp_tls_session_free (rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + gnutls_deinit(tls_ctx->gnutls_session); +#elif defined(USE_OPENSSL) + if (tls_ctx->openssl_conn != NULL) { + SSL_free(tls_ctx->openssl_conn); + tls_ctx->openssl_conn = NULL; + /* bio_conn is freed implicitly by SSL_free. */ + tls_ctx->bio_conn = NULL; + } + if (tls_ctx->bio_io != NULL) { + BIO_free(tls_ctx->bio_io); + tls_ctx->bio_io = NULL; + } + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; +#else + (void)tls_ctx; +#endif +} + +void rexmpp_tls_cleanup (rexmpp_t *s) { + if (s->tls_state != REXMPP_TLS_INACTIVE && + s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { + rexmpp_tls_session_free(s->tls); + } +} + +void rexmpp_tls_deinit (rexmpp_t *s) { + if (s->tls != NULL) { + rexmpp_tls_ctx_free(s->tls); + s->tls = NULL; + } +} + +#if defined(USE_GNUTLS) +ssize_t +rexmpp_dtls_jingle_pull_func_gnutls (gnutls_transport_ptr_t p, + void *data, + size_t size) +{ + rexmpp_tls_t *tls_ctx = rexmpp_jingle_component_dtls(p); + ssize_t received; + + char *tls_buf = tls_ctx->dtls_buf; + size_t *tls_buf_len = &(tls_ctx->dtls_buf_len); + + rexmpp_tls_err_t ret = REXMPP_TLS_SUCCESS; + if (*tls_buf_len > 0) { + if (size >= *tls_buf_len) { + memcpy(data, tls_buf, *tls_buf_len); + received = *tls_buf_len; + *tls_buf_len = 0; + } else { + if (size > DTLS_SRTP_BUF_SIZE) { + size = DTLS_SRTP_BUF_SIZE; + } + memcpy(data, tls_buf, size); + memmove(tls_buf, tls_buf + size, DTLS_SRTP_BUF_SIZE - size); + received = size; + *tls_buf_len = *tls_buf_len - size; + } + } else { + ret = REXMPP_TLS_E_AGAIN; + } + + if (ret == REXMPP_TLS_SUCCESS) { + return received; + } else if (ret == REXMPP_TLS_E_AGAIN) { + gnutls_transport_set_errno(tls_ctx->gnutls_session, EAGAIN); + } + return -1; +} +#endif + +#if defined(USE_OPENSSL) +long rexmpp_dtls_openssl_bio_cb(BIO *b, int oper, const char *argp, + size_t len, int argi, + long argl, int ret, size_t *processed) { + (void)argi; + (void)argl; + (void)processed; + if (oper == BIO_CB_WRITE) { + rexmpp_jingle_dtls_push_func(BIO_get_callback_arg(b), argp, len); + } + return ret; +} +#endif + +#if defined(USE_OPENSSL) +int rexmpp_openssl_verify_accept_all (int preverify_ok, + X509_STORE_CTX *x509_ctx) +{ + (void)preverify_ok; + (void)x509_ctx; + return 1; +} +#endif + +rexmpp_tls_err_t +rexmpp_dtls_connect (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *user_data, + int client) { +#if defined(USE_GNUTLS) + gnutls_session_t *tls_session = &(tls_ctx->gnutls_session); + gnutls_init(tls_session, + (client ? GNUTLS_CLIENT : GNUTLS_SERVER) | + GNUTLS_DATAGRAM | + GNUTLS_NONBLOCK); + if (! client) { + gnutls_certificate_server_set_request(*tls_session, GNUTLS_CERT_REQUIRE); + } + gnutls_set_default_priority(*tls_session); + rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL); + gnutls_credentials_set(*tls_session, GNUTLS_CRD_CERTIFICATE, + tls_ctx->gnutls_cred); + + gnutls_transport_set_ptr(*tls_session, user_data); + gnutls_transport_set_push_function + (*tls_session, rexmpp_jingle_dtls_push_func); + gnutls_transport_set_pull_function + (*tls_session, rexmpp_dtls_jingle_pull_func_gnutls); + gnutls_transport_set_pull_timeout_function + (*tls_session, rexmpp_jingle_dtls_pull_timeout_func); + /* todo: use the profile/crypto-suite from <crypto/> element */ + gnutls_srtp_set_profile(*tls_session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80); + return REXMPP_TLS_SUCCESS; +#elif defined(USE_OPENSSL) + (void)client; + int err; + /* Setup credentials */ + rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL); + /* Create a connection. */ + tls_ctx->openssl_conn = SSL_new(tls_ctx->openssl_ctx); + SSL_set_verify(tls_ctx->openssl_conn, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + rexmpp_openssl_verify_accept_all); + /* Set a BIO */ + BIO_new_bio_pair(&(tls_ctx->bio_conn), 4096, &(tls_ctx->bio_io), 4096); + BIO_up_ref(tls_ctx->bio_conn); + SSL_set0_rbio(tls_ctx->openssl_conn, tls_ctx->bio_conn); + SSL_set0_wbio(tls_ctx->openssl_conn, tls_ctx->bio_conn); + /* Set a callback to track writes */ + BIO_set_callback_ex(tls_ctx->bio_conn, rexmpp_dtls_openssl_bio_cb); + BIO_set_callback_arg(tls_ctx->bio_conn, user_data); + BIO_set_ssl(tls_ctx->bio_conn, tls_ctx->openssl_conn, BIO_NOCLOSE); + /* Enable SRTP (TODO: support different profiles) */ + err = SSL_set_tlsext_use_srtp(tls_ctx->openssl_conn, + "SRTP_AES128_CM_SHA1_80"); + if (err) { + rexmpp_log(s, LOG_ERR, "Failed to setup SRTP for the DTLS connection"); + return REXMPP_TLS_E_OTHER; + } + if (client) { + err = SSL_connect(tls_ctx->openssl_conn); + } else { + err = SSL_accept(tls_ctx->openssl_conn); + } + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_dtls_connect", err); +#else + (void)s; + (void)tls_ctx; + (void)user_data; + (void)client; + return REXMPP_TLS_E_OTHER; +#endif +} + +void rexmpp_dtls_feed(rexmpp_t *s, rexmpp_tls_t *tls_ctx, uint8_t *buf, size_t len) { +#if defined(USE_GNUTLS) + if (tls_ctx->dtls_buf_len + len < DTLS_SRTP_BUF_SIZE) { + memcpy(tls_ctx->dtls_buf + tls_ctx->dtls_buf_len, buf, len); + tls_ctx->dtls_buf_len += len; + } else { + rexmpp_log(s, LOG_WARNING, "Dropping a DTLS packet"); + } +#elif defined(USE_OPENSSL) + (void)s; + BIO_write(tls_ctx->bio_io, buf, len); +#else + (void)s; + (void)tls_ctx; + (void)buf; + (void)len; +#endif +} + +rexmpp_tls_err_t rexmpp_tls_handshake (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + int ret = gnutls_handshake(tls_ctx->gnutls_session); + if (ret == 0) { + return REXMPP_TLS_SUCCESS; + } else if (ret == GNUTLS_E_AGAIN) { + return REXMPP_TLS_E_AGAIN; + } else { + rexmpp_log(s, LOG_ERR, "Error during a TLS handshake: %s", + gnutls_strerror(ret)); + return REXMPP_TLS_E_OTHER; + } +#elif defined(USE_OPENSSL) + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_handshake", + SSL_do_handshake(tls_ctx->openssl_conn)); +#else + (void)s; + (void)tls_ctx; + return REXMPP_TLS_E_OTHER; +#endif +} + +rexmpp_tls_err_t +rexmpp_tls_connect (rexmpp_t *s) { + if (s->x509_key_file != NULL && s->x509_cert_file != NULL) { + rexmpp_tls_set_x509_key_file(s, s->tls, NULL, NULL); + } + +#if defined(USE_GNUTLS) + if (s->tls_state != REXMPP_TLS_HANDSHAKE) { + gnutls_datum_t xmpp_client_protocol = + {(unsigned char*)"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) { + unsigned 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, s->tls, "rexmpp_tls_connect", + 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, rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + int ret = gnutls_bye(tls_ctx->gnutls_session, GNUTLS_SHUT_RDWR); + if (ret == GNUTLS_E_SUCCESS) { + return REXMPP_TLS_SUCCESS; + } else if (ret == GNUTLS_E_AGAIN) { + return REXMPP_TLS_E_AGAIN; + } 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(tls_ctx->openssl_conn); + if (ret == 0) { + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; + return REXMPP_TLS_E_AGAIN; + } else { + return rexmpp_process_openssl_ret(s, tls_ctx, + "rexmpp_tls_disconnect", ret); + } +#else + (void)tls_ctx; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return REXMPP_TLS_E_OTHER; +#endif +} + +int +rexmpp_tls_srtp_get_keys (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + size_t key_len, + size_t salt_len, + unsigned char *key_mat) +{ +#if defined(USE_GNUTLS) + int key_mat_size; + key_mat_size = + gnutls_srtp_get_keys(tls_ctx->gnutls_session, + key_mat, (key_len + salt_len) * 2, + NULL, NULL, NULL, NULL); + if (key_mat_size == GNUTLS_E_SHORT_MEMORY_BUFFER || + key_mat_size < 0) { + rexmpp_log(s, LOG_ERR, + "Failed to retrieve DTLS key material for SRTP: %s", + gnutls_strerror(key_mat_size)); + } + return 0; +#elif defined(USE_OPENSSL) + /* https://www.rfc-editor.org/rfc/rfc5764.html */ + const char *extractor = "EXTRACTOR-dtls_srtp"; + int err = SSL_export_keying_material(tls_ctx->openssl_conn, + key_mat, 2 * (key_len + salt_len), + extractor, strlen(extractor), + NULL, 0, 0); + return rexmpp_process_openssl_ret(s, tls_ctx, + "rexmpp_tls_srtp_get_keys", err); +#else + (void)s; + (void)tls_ctx; + (void)key_len; + (void)salt_len; + (void)key_mat; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return -1; +#endif +} + +rexmpp_tls_err_t +rexmpp_tls_send (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *written) +{ + *written = -1; +#if defined(USE_GNUTLS) + ssize_t ret = gnutls_record_send(tls_ctx->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) + int ret = SSL_write_ex(tls_ctx->openssl_conn, data, data_size, + (size_t*)written); + if (ret > 0) { + return REXMPP_TLS_SUCCESS; + } else { + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_send", ret); + } +#else + (void)data; + (void)data_size; + (void)tls_ctx; + 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, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *received) +{ +#if defined(USE_GNUTLS) + *received = -1; + ssize_t ret = gnutls_record_recv(tls_ctx->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(tls_ctx->openssl_conn, data, data_size, + (size_t*)received); + if (ret > 0) { + return REXMPP_TLS_SUCCESS; + } else { + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_recv", ret); + } +#else + (void)data; + (void)data_size; + (void)received; + (void)tls_ctx; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return REXMPP_TLS_E_OTHER; +#endif +} + +unsigned int rexmpp_dtls_timeout (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { + (void)s; +#if defined(USE_GNUTLS) + return gnutls_dtls_get_timeout(tls_ctx->gnutls_session); +#else + (void)tls_ctx; + return -1; +#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 +} + +rexmpp_tls_err_t +rexmpp_tls_set_x509_key_file (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *cert_file, + const char *key_file) +{ + if (cert_file == NULL) { + cert_file = s->x509_cert_file; + } + if (key_file == NULL) { + key_file = s->x509_key_file; + } + if (cert_file == NULL || key_file == NULL) { + rexmpp_log(s, LOG_ERR, "No certificate or key file defined"); + return REXMPP_TLS_E_OTHER; + } +#if defined(USE_GNUTLS) + int ret = gnutls_certificate_set_x509_key_file(tls_ctx->gnutls_cred, + cert_file, + key_file, + GNUTLS_X509_FMT_DER); + if (ret == 0) { + return REXMPP_TLS_SUCCESS; + } else { + rexmpp_log(s, LOG_ERR, + "Failed to set a key file: %s", gnutls_strerror(ret)); + return REXMPP_TLS_E_OTHER; + } +#elif defined(USE_OPENSSL) + if (SSL_CTX_use_certificate_file(tls_ctx->openssl_ctx, + cert_file, + SSL_FILETYPE_ASN1) != 1) { + rexmpp_log(s, LOG_ERR, "Failed to set a certificate file"); + return REXMPP_TLS_E_OTHER; + } + if (SSL_CTX_use_PrivateKey_file(tls_ctx->openssl_ctx, + key_file, + SSL_FILETYPE_ASN1) != 1) { + rexmpp_log(s, LOG_ERR, "Failed to set a key file"); + return REXMPP_TLS_E_OTHER; + } + return REXMPP_TLS_SUCCESS; +#else + (void)cert_file; + (void)key_file; + (void)tls_ctx; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return REXMPP_TLS_E_OTHER; +#endif +} + +rexmpp_tls_err_t +rexmpp_tls_set_x509_trust_file (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *trust_file) +{ + if (trust_file == NULL) { + trust_file = s->x509_trust_file; + } + if (trust_file == NULL) { + rexmpp_log(s, LOG_ERR, "No trust file is defined"); + return REXMPP_TLS_E_OTHER; + } +#if defined(USE_GNUTLS) + gnutls_certificate_set_x509_trust_file(tls_ctx->gnutls_cred, + trust_file, + GNUTLS_X509_FMT_DER); + return REXMPP_TLS_SUCCESS; +#elif defined(USE_OPENSSL) + if (SSL_CTX_load_verify_locations(tls_ctx->openssl_ctx, trust_file, NULL) != 1) { + rexmpp_log(s, LOG_ERR, "Failed to set a trusted certificate file"); + return REXMPP_TLS_E_OTHER; + } + return REXMPP_TLS_SUCCESS; +#else + (void)trust_file; + (void)tls_ctx; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return REXMPP_TLS_E_OTHER; +#endif +} + + +int rexmpp_tls_peer_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + unsigned int cert_list_size = 0; + const gnutls_datum_t *cert_list; + cert_list = + gnutls_certificate_get_peers(tls_ctx->gnutls_session, &cert_list_size); + if (cert_list_size != 1) { + rexmpp_log(s, LOG_ERR, + "Unexpected peer certificate list size: %d", + cert_list_size); + return -1; + } + return rexmpp_x509_raw_cert_fp(s, algo_str, cert_list, + raw_fp, fp_str, fp_size); +#elif defined(USE_OPENSSL) + if (strcmp(algo_str, "sha-256") != 0) { + rexmpp_log(s, LOG_ERR, + "Unsupported hash function algorithm: %s", algo_str); + return -1; + } + X509 *peer_cert = SSL_get0_peer_certificate(tls_ctx->openssl_conn); + if (peer_cert == NULL) { + rexmpp_log(s, LOG_ERR, "No peer certificate found"); + return -1; + } + unsigned int len; + X509_digest(peer_cert, EVP_sha256(), (unsigned char*)raw_fp, &len); + *fp_size = len; + size_t i; + for (i = 0; i < *fp_size; i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[*fp_size * 3 - 1] = 0; + return 0; +#else + (void)tls_ctx; + (void)algo_str; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return -1; +#endif +} + +/* TODO: handle different algorithms, and maybe apply this to + arbitrary files. */ +int rexmpp_tls_my_fp (rexmpp_t *s, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ + rexmpp_digest_t digest_ctx; + if (rexmpp_digest_init(&digest_ctx, REXMPP_DIGEST_SHA256)) { + rexmpp_log(s, LOG_ERR, "Failed to initialize a digest object"); + return -1; + } + + if (s->x509_cert_file == NULL) { + rexmpp_log(s, LOG_WARNING, "No X.509 certificate file defined"); + return -1; + } + FILE *fh = fopen(s->x509_cert_file, "r"); + if (fh == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to open the X.509 certificate file"); + return -1; + } + unsigned char *buf[4096]; + size_t len = fread(buf, 1, 4096, fh); + while (len > 0) { + rexmpp_digest_update(&digest_ctx, buf, len); + len = fread(buf, 1, 4096, fh); + } + fclose(fh); + + *fp_size = rexmpp_digest_len(REXMPP_DIGEST_SHA256); + rexmpp_digest_finish(&digest_ctx, raw_fp, *fp_size); + + size_t i; + for (i = 0; i < (*fp_size); i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[(*fp_size) * 3 - 1] = 0; + return 0; +} + +int rexmpp_tls_session_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + gnutls_x509_crt_t *cert_list; + unsigned int cert_list_size = 0; + int err = + gnutls_certificate_get_x509_crt(tls_ctx->gnutls_cred, + 0, &cert_list, &cert_list_size); + if (err) { + rexmpp_log(s, LOG_ERR, + "Failed to read own certificate list: %s", + gnutls_strerror(err)); + return -1; + } + + err = rexmpp_x509_cert_fp(s, algo_str, cert_list[0], raw_fp, fp_str, fp_size); + + size_t i; + for (i = 0; i < cert_list_size; i++) { + gnutls_x509_crt_deinit(cert_list[i]); + } + gnutls_free(cert_list); + return err; +#else + (void)s; + (void)tls_ctx; + (void)algo_str; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} + +int rexmpp_x509_cert_fp (rexmpp_t *s, + const char *algo_str, + void *cert, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + gnutls_datum_t raw_cert; + int err = gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &raw_cert); + if (err != GNUTLS_E_SUCCESS) { + rexmpp_log(s, LOG_ERR, "Failed to export a certificate: %s", + gnutls_strerror(err)); + return err; + } + err = rexmpp_x509_raw_cert_fp(s, algo_str, &raw_cert, raw_fp, fp_str, fp_size); + gnutls_free(raw_cert.data); + return err; +#else + (void)s; + (void)algo_str; + (void)cert; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} + +int rexmpp_x509_raw_cert_fp (rexmpp_t *s, + const char *algo_str, + const void *raw_cert, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + const gnutls_datum_t *cert = (const gnutls_datum_t*)raw_cert; + gnutls_digest_algorithm_t algo = GNUTLS_DIG_UNKNOWN; + /* gnutls_digest_get_id uses different names, so + checking manually here. These are SDP options, + <https://datatracker.ietf.org/doc/html/rfc4572#page-8>. */ + if (strcmp(algo_str, "sha-1") == 0) { + algo = GNUTLS_DIG_SHA1; + } else if (strcmp(algo_str, "sha-224") == 0) { + algo = GNUTLS_DIG_SHA224; + } else if (strcmp(algo_str, "sha-256") == 0) { + algo = GNUTLS_DIG_SHA256; + } else if (strcmp(algo_str, "sha-384") == 0) { + algo = GNUTLS_DIG_SHA384; + } else if (strcmp(algo_str, "sha-512") == 0) { + algo = GNUTLS_DIG_SHA512; + } else if (strcmp(algo_str, "md5") == 0) { + algo = GNUTLS_DIG_MD5; + } + if (algo == GNUTLS_DIG_UNKNOWN) { + rexmpp_log(s, LOG_ERR, "Unknown hash algorithm: %s", algo_str); + return -1; + } + + int err = gnutls_fingerprint(algo, cert, raw_fp, fp_size); + if (err != GNUTLS_E_SUCCESS) { + rexmpp_log(s, LOG_ERR, "Failed to calculate a fingerprint: %s", + gnutls_strerror(err)); + return -1; + } + if (fp_str != NULL) { + size_t i; + for (i = 0; i < (*fp_size); i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[(*fp_size) * 3 - 1] = 0; + } + return 0; +#else + (void)s; + (void)algo_str; + (void)raw_cert; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} |