diff options
Diffstat (limited to 'src/rexmpp_tls.c')
-rw-r--r-- | src/rexmpp_tls.c | 479 |
1 files changed, 404 insertions, 75 deletions
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c index 9234e92..803cae8 100644 --- a/src/rexmpp_tls.c +++ b/src/rexmpp_tls.c @@ -17,6 +17,7 @@ #include <gnutls/crypto.h> #include <gnutls/x509.h> #include <gnutls/dane.h> +#include <gnutls/dtls.h> #elif defined(USE_OPENSSL) #include <openssl/ssl.h> #endif @@ -24,6 +25,16 @@ #include "rexmpp.h" #include "rexmpp_tls.h" +ssize_t +rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size); +rexmpp_tls_err_t +rexmpp_jingle_dtls_pull_func (void *comp, + void *data, + size_t size, + ssize_t *received); +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); @@ -43,101 +54,191 @@ rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, int ret) { } #endif -int rexmpp_tls_init (rexmpp_t *s) { - s->tls = malloc(sizeof(struct rexmpp_tls)); +rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls) { + rexmpp_tls_t *tls_ctx = malloc(sizeof(rexmpp_tls_t)); #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; + 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; + return NULL; } -#ifdef ENABLE_CALLS - err = gnutls_certificate_allocate_credentials(&(s->tls->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; + 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; + return NULL; } - return 0; #else - s->tls = NULL; - return 0; + (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_cleanup (rexmpp_t *s) { - if (s->tls_state != REXMPP_TLS_INACTIVE && - s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { +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; } - s->tls->openssl_direction = REXMPP_OPENSSL_NONE; + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; #else (void)s; #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) - 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; +ssize_t +rexmpp_dtls_jingle_pull_func_gnutls (gnutls_transport_ptr_t p, + void *data, + size_t size) +{ + rexmpp_jingle_component_t *comp = p; + ssize_t received; + rexmpp_tls_err_t err = + rexmpp_jingle_dtls_pull_func(comp, data, size, &received); + if (err == REXMPP_TLS_SUCCESS) { + return received; + } else if (err == REXMPP_TLS_E_AGAIN) { + gnutls_transport_set_errno(comp->dtls->gnutls_session, EAGAIN); } -#ifdef ENABLE_CALLS - gnutls_certificate_free_credentials(s->tls->dtls_cred); + return -1; +} #endif -#elif defined(USE_OPENSSL) - if (s->tls->openssl_ctx != NULL) { - SSL_CTX_free(s->tls->openssl_ctx); + +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); } - s->tls->openssl_ctx = NULL; + 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; +#else + /* TODO: OpenSSL */ + (void)s; + (void)tls_ctx; + (void)user_data; + (void)client; + return REXMPP_TLS_E_OTHER; #endif - if (s->tls != NULL) { - free(s->tls); - s->tls = NULL; +} + +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; } +#else + (void)s; + (void)tls_ctx; + /* TODO: OpenSSL */ + /* SSL_do_handshake */ + 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 = @@ -312,12 +413,52 @@ rexmpp_tls_disconnect (rexmpp_t *s) { #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 *client_key_wsalt, + unsigned char *server_key_wsalt) +{ +#if defined(USE_GNUTLS) + int key_mat_size; + char key_mat[4096]; + gnutls_datum_t client_key, client_salt, server_key, server_salt; + key_mat_size = + gnutls_srtp_get_keys(tls_ctx->gnutls_session, + key_mat, (key_len + salt_len) * 2, + &client_key, &client_salt, + &server_key, &server_salt); + rexmpp_log(s, LOG_DEBUG, "SRTP key material size: %d", + key_mat_size); + memcpy(client_key_wsalt, client_key.data, key_len); + memcpy(client_key_wsalt + key_len, client_salt.data, salt_len); + memcpy(server_key_wsalt, server_key.data, key_len); + memcpy(server_key_wsalt + key_len, server_salt.data, salt_len); + return 0; +#else + /* TODO: OpenSSL */ + (void)s; + (void)tls_ctx; + (void)key_len; + (void)salt_len; + (void)client_key_wsalt; + (void)server_key_wsalt; + return -1; +#endif +} + rexmpp_tls_err_t -rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) +rexmpp_tls_send (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + 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, + ssize_t ret = gnutls_record_send(tls_ctx->gnutls_session, data, data_size); if (ret >= 0) { @@ -331,7 +472,7 @@ rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) } #elif defined(USE_OPENSSL) *written = -1; - int ret = SSL_write_ex(s->tls->openssl_conn, data, data_size, + int ret = SSL_write_ex(tls_ctx->openssl_conn, data, data_size, (size_t*)written); if (ret > 0) { return REXMPP_TLS_SUCCESS; @@ -348,10 +489,15 @@ rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) } 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; @@ -363,7 +509,7 @@ 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, + int ret = SSL_read_ex(tls_ctx->openssl_conn, data, data_size, (size_t*)received); if (ret > 0) { return REXMPP_TLS_SUCCESS; @@ -379,6 +525,16 @@ rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) { #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) { @@ -407,20 +563,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->tls->dtls_cred, - cert_file, - key_file, - GNUTLS_X509_FMT_PEM); -#endif if (ret == 0) { return REXMPP_TLS_SUCCESS; } else { @@ -429,13 +590,13 @@ 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) { 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) { rexmpp_log(s, LOG_ERR, "Failed to set a key file"); @@ -452,22 +613,190 @@ 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_certificate_set_x509_trust_file(tls_ctx->gnutls_cred, + trust_file, GNUTLS_X509_FMT_PEM); 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; 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); +#else + /* TODO: OpenSSL */ + (void)s; + (void)tls_ctx; + (void)algo_str; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} + +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 + /* TODO: OpenSSL */ + (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 + /* TODO: OpenSSL */ + (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 + /* TODO: OpenSSL */ + (void)s; + (void)algo_str; + (void)raw_cert; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} |