diff options
Diffstat (limited to 'src/rexmpp_tls.c')
-rw-r--r-- | src/rexmpp_tls.c | 781 |
1 files changed, 651 insertions, 130 deletions
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c index 7919556..2a7c903 100644 --- a/src/rexmpp_tls.c +++ b/src/rexmpp_tls.c @@ -8,6 +8,7 @@ #include <syslog.h> #include <string.h> +#include <stdlib.h> #include "config.h" @@ -16,160 +17,382 @@ #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, int ret) { - int err = SSL_get_error(s->tls.openssl_conn, ret); - s->tls.openssl_direction = REXMPP_OPENSSL_NONE; +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) { - s->tls.openssl_direction = REXMPP_OPENSSL_READ; + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; return REXMPP_TLS_E_AGAIN; } else if (err == SSL_ERROR_WANT_WRITE) { - s->tls.openssl_direction = REXMPP_OPENSSL_WRITE; + tls_ctx->openssl_direction = REXMPP_OPENSSL_WRITE; return REXMPP_TLS_E_AGAIN; } else { - rexmpp_log(s, LOG_ERR, "OpenSSL error %d", err); + 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 -int rexmpp_tls_init (rexmpp_t *s) { +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; - s->tls.tls_session_data = NULL; - s->tls.tls_session_data_size = 0; + tls_ctx->tls_session_data = NULL; + tls_ctx->tls_session_data_size = 0; - err = gnutls_certificate_allocate_credentials(&(s->tls.gnutls_cred)); + err = gnutls_certificate_allocate_credentials(&(tls_ctx->gnutls_cred)); if (err) { rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", gnutls_strerror(err)); - return 1; + free(tls_ctx); + return NULL; + } + if (! dtls) { + err = gnutls_certificate_set_x509_system_trust(tls_ctx->gnutls_cred); } - 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; + free(tls_ctx); + return NULL; } -#ifdef ENABLE_CALLS - err = gnutls_certificate_allocate_credentials(&(s->jingle.dtls_cred)); - if (err) { - gnutls_certificate_free_credentials(s->tls.gnutls_cred); - rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", - gnutls_strerror(err)); - return 1; - } -#endif - return 0; + + tls_ctx->dtls_buf_len = 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) { + 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"); - return 1; + free(tls_ctx); + return NULL; } - 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; + 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; } - return 0; #else (void)s; - return 0; + (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); +} -void rexmpp_tls_cleanup (rexmpp_t *s) { - if (s->tls_state != REXMPP_TLS_INACTIVE && - s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { +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(s->tls.gnutls_session); + gnutls_deinit(tls_ctx->gnutls_session); #elif defined(USE_OPENSSL) - if (s->tls.openssl_conn != NULL) { - SSL_free(s->tls.openssl_conn); - s->tls.openssl_conn = NULL; + 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; } - s->tls.openssl_direction = REXMPP_OPENSSL_NONE; + 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)s; + (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_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; + 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); } -#ifdef ENABLE_CALLS - gnutls_certificate_free_credentials(s->jingle.dtls_cred); + 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) - if (s->tls.openssl_ctx != NULL) { - SSL_CTX_free(s->tls.openssl_ctx); + (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; } - s->tls.openssl_ctx = NULL; +#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 = {"xmpp-client", strlen("xmpp-client")}; + 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, + 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_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 (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; + 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); + 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; + unsigned int status; int srv_is_secure = 0; if (s->stream_state == REXMPP_STREAM_NONE && @@ -190,7 +413,7 @@ rexmpp_tls_connect (rexmpp_t *s) { 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, + 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", @@ -212,7 +435,7 @@ rexmpp_tls_connect (rexmpp_t *s) { } } - ret = gnutls_certificate_verify_peers3(s->tls.gnutls_session, + ret = gnutls_certificate_verify_peers3(s->tls->gnutls_session, s->initial_jid.domain, &status); if (ret || status) { @@ -224,23 +447,23 @@ rexmpp_tls_connect (rexmpp_t *s) { } else { rexmpp_log(s, LOG_ERR, "Untrusted certificate"); } - gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR); + gnutls_bye(s->tls->gnutls_session, GNUTLS_SHUT_RDWR); return REXMPP_TLS_E_OTHER; } - if (gnutls_session_is_resumed(s->tls.gnutls_session)) { + 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) { + 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; + 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); + 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)); @@ -256,26 +479,27 @@ rexmpp_tls_connect (rexmpp_t *s) { } #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) { + 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) { + 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) { + 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) { + 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)); + 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; @@ -283,36 +507,84 @@ rexmpp_tls_connect (rexmpp_t *s) { } rexmpp_tls_err_t -rexmpp_tls_disconnect (rexmpp_t *s) { +rexmpp_tls_disconnect (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { #if defined(USE_GNUTLS) - int ret = gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR); + 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(s->tls.openssl_conn); + int ret = SSL_shutdown(tls_ctx->openssl_conn); if (ret == 0) { - s->tls.openssl_direction = REXMPP_OPENSSL_READ; + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; return REXMPP_TLS_E_AGAIN; } else { - return rexmpp_process_openssl_ret(s, ret); + 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 } -rexmpp_tls_err_t -rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) +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; - ssize_t ret = gnutls_record_send(s->tls.gnutls_session, +#if defined(USE_GNUTLS) + ssize_t ret = gnutls_record_send(tls_ctx->gnutls_session, data, data_size); if (ret >= 0) { @@ -325,27 +597,32 @@ rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) return REXMPP_TLS_E_OTHER; } #elif defined(USE_OPENSSL) - *written = -1; - int ret = SSL_write_ex(s->tls.openssl_conn, data, data_size, written); + 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, ret); + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_send", ret); } #else (void)data; (void)data_size; - (void)written; + (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, void *data, size_t data_size, ssize_t *received) { +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(s->tls.gnutls_session, data, data_size); + ssize_t ret = gnutls_record_recv(tls_ctx->gnutls_session, data, data_size); if (ret >= 0) { *received = ret; return REXMPP_TLS_SUCCESS; @@ -357,35 +634,47 @@ rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) { } #elif defined(USE_OPENSSL) *received = -1; - int ret = SSL_read_ex(s->tls.openssl_conn, data, data_size, received); + 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, ret); + 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) { + 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) { + 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) { + if (s->tls->openssl_direction == REXMPP_OPENSSL_WRITE) { FD_SET(s->server_socket, write_fds); return s->server_socket + 1; } @@ -400,20 +689,25 @@ int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { 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(s->tls.gnutls_cred, + int ret = gnutls_certificate_set_x509_key_file(tls_ctx->gnutls_cred, cert_file, key_file, - GNUTLS_X509_FMT_PEM); -#ifdef ENABLE_CALLS - gnutls_certificate_set_x509_key_file(s->jingle.dtls_cred, - cert_file, - key_file, - GNUTLS_X509_FMT_PEM); -#endif + GNUTLS_X509_FMT_DER); if (ret == 0) { return REXMPP_TLS_SUCCESS; } else { @@ -422,15 +716,15 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, return REXMPP_TLS_E_OTHER; } #elif defined(USE_OPENSSL) - if (SSL_CTX_use_certificate_file(s->tls.openssl_ctx, + if (SSL_CTX_use_certificate_file(tls_ctx->openssl_ctx, cert_file, - SSL_FILETYPE_PEM) != 1) { + 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(s->tls.openssl_ctx, + if (SSL_CTX_use_PrivateKey_file(tls_ctx->openssl_ctx, key_file, - SSL_FILETYPE_PEM) != 1) { + SSL_FILETYPE_ASN1) != 1) { rexmpp_log(s, LOG_ERR, "Failed to set a key file"); return REXMPP_TLS_E_OTHER; } @@ -438,6 +732,7 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, #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 @@ -445,22 +740,248 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, rexmpp_tls_err_t rexmpp_tls_set_x509_trust_file (rexmpp_t *s, - const char *cert_file) + 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(s->tls.gnutls_cred, - cert_file, - GNUTLS_X509_FMT_PEM); + 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(s->tls.openssl_ctx, cert_file, NULL) != 1) { + 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)cert_file; + (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 +} |