From f26079b861fde8de42f9e2db5a0f1a9330ce1ed5 Mon Sep 17 00:00:00 2001 From: pk910 Date: Wed, 25 Nov 2015 19:05:26 +0100 Subject: [PATCH] added gnutls backend and moved backend code into new files gnutls backend is not working yet... --- configure.in | 67 ++++++-- include/ssl.h | 4 +- ircd/ssl.c | 373 +---------------------------------------- ircd/ssl.gnutls.c | 385 +++++++++++++++++++++++++++++++++++++++++++ ircd/ssl.openssl.c | 402 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 843 insertions(+), 388 deletions(-) create mode 100644 ircd/ssl.gnutls.c create mode 100644 ircd/ssl.openssl.c diff --git a/configure.in b/configure.in index d4205cf..fd1a410 100644 --- a/configure.in +++ b/configure.in @@ -226,24 +226,6 @@ else fi AC_SUBST(ENGINE_C) -have_gnutls="no" -AC_CHECK_LIB(gnutls, gnutls_init, [ - AC_CHECK_HEADERS(gnutls/gnutls.h, [ - LIBS="$LIBS -lgnutls" - have_gnutls="yes" - ]) -]) -if test x"$have_gnutls" = xno; then - if test x$is_win32 = xyes ; then - openssl_deps="-lcrypto -lgdi32" - else - openssl_deps="-lcrypto" - fi - AC_CHECK_LIB([ssl],[SSL_library_init], [ - LIBS="$LIBS -lssl $openssl_deps" - ], [], $openssl_deps) -fi - dnl Now look for --enable-debug AC_MSG_CHECKING([whether to enable debug mode]) AC_ARG_ENABLE([debug], @@ -733,6 +715,53 @@ AC_MSG_RESULT([$unet_cv_with_maxcon]) AC_DEFINE_UNQUOTED(MAXCONNECTIONS, $unet_cv_with_maxcon, [Maximum number of network connections]) +unet_cv_enable_gnutls="no" +unet_cv_enable_openssl="yes" + +AC_MSG_CHECKING([for GnuTLS]) +AC_ARG_ENABLE([gnutls], + [ --enable-gnutls Enables GnuTLS ssl backend.], + [unet_cv_enable_gnutls=$enable_gnutls], +) +AC_MSG_RESULT([$unet_cv_enable_gnutls]) + +AC_MSG_CHECKING([for OpenSSL]) +AC_ARG_ENABLE([openssl], + [ --enable-openssl Enables OpenSSL ssl backend.], + [unet_cv_enable_openssl=$enable_openssl], +) +AC_MSG_RESULT([$unet_cv_enable_openssl]) + +if test x"$unet_cv_enable_gnutls" = xyes; then + unet_cv_enable_gnutls="no"; + AC_CHECK_LIB(gnutls, gnutls_init, [ + AC_CHECK_HEADERS(gnutls/gnutls.h, [ + unet_cv_enable_gnutls="yes"; + ]) + ]) +fi + +if test x"$unet_cv_enable_openssl" = xyes; then + unet_cv_enable_openssl="no"; + AC_CHECK_LIB(ssl, SSL_read, [ + AC_CHECK_LIB(crypto, X509_new, [ + AC_CHECK_HEADERS(openssl/ssl.h openssl/err.h, [ + unet_cv_enable_openssl="yes"; + ]) + ]) + ]) +fi + +if test x"$unet_cv_enable_gnutls" = xyes; then + LIBS="$LIBS -lgnutls" + AC_DEFINE([HAVE_GNUTLS], 1, [Define if you are using GnuTLS]) +fi + +if test x"$unet_cv_enable_openssl" = xyes ; then + LIBS="$LIBS -lssl -lcrypto" + AC_DEFINE([HAVE_OPENSSL], 1, [Define if you are using OpenSSL]) +fi + dnl Finally really generate all output files: AC_OUTPUT(Makefile ircd/Makefile ircd/test/Makefile, [echo timestamp > stamp-h]) @@ -748,6 +777,8 @@ ircu is now hopefully configured for your system. Profile: $unet_cv_enable_profile Owner/mode: $unet_cv_with_owner.$unet_cv_with_group ($unet_cv_with_mode) Chroot: $unet_cv_with_chroot + OpenSSL: $unet_cv_enable_openssl + GnuTLS: $unet_cv_enable_gnutls Domain: $unet_cv_with_domain DPath: $unet_cv_with_dpath diff --git a/include/ssl.h b/include/ssl.h index db4cad0..03494cb 100644 --- a/include/ssl.h +++ b/include/ssl.h @@ -24,7 +24,7 @@ enum SSLDataType { SSLData_Client }; -#if defined(HAVE_GNUTLS_GNUTLS_H) +#if defined(HAVE_GNUTLS) #include struct SSLConnection { @@ -39,7 +39,7 @@ struct SSLListener { gnutls_certificate_credentials_t credentials; }; -#elif defined(HAVE_OPENSSL_SSL_H) +#elif defined(HAVE_OPENSSL) #include #include #include diff --git a/ircd/ssl.c b/ircd/ssl.c index 62f6147..c58414c 100644 --- a/ircd/ssl.c +++ b/ircd/ssl.c @@ -17,7 +17,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file - * @brief Implementation of functions for handling local clients. + * @brief Implementation of functions for handling ssl connections * @version $Id$ */ #include "config.h" @@ -44,373 +44,10 @@ #define IOV_MAX 16 /**< minimum required length of an iovec array */ #endif -#if defined(HAVE_OPENSSL_SSL_H) - -static struct SSLPendingConections { - struct SSLConnection *connection; - struct SSLPendingConections *next; - - void *data; - enum SSLDataType datatype; -}; - -struct SSLPendingConections *firstPendingConection = NULL; -int ssl_is_initialized = 0; - -static void ssl_init() { - if(ssl_is_initialized) - return; - ssl_is_initialized = 1; - SSL_library_init(); - OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ - SSL_load_error_strings(); -} - -void ssl_free_connection(struct SSLConnection *connection) { - SSL_CTX *context = NULL; - if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { - struct SSLOutConnection *outconn = (struct SSLOutConnection *)connection; - context = outconn->context; - } - SSL_shutdown(connection->session); - SSL_free(connection->session); - if(context) - SSL_CTX_free(context); - free(connection); -} - -void ssl_free_listener(struct SSLListener *listener) { - SSL_CTX_free(listener->context); - free(listener); -} - -static void ssl_handshake_completed(struct SSLConnection *connection, int success) { - struct SSLPendingConections *pending, *lastPending = NULL; - for(pending = firstPendingConection; pending; pending = pending->next) { - if(pending->connection == connection) { - if(lastPending) - lastPending->next = pending->next; - else - firstPendingConection = pending->next; - switch(pending->datatype) { - case SSLData_Client: { - struct Client *cptr = (struct Client *) pending->data; - if(success) { - if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) - start_auth(cptr); - else if(!completed_connection(cptr)) - exit_client_msg(cptr, cptr, &me, "Registration failed."); - } else - exit_client_msg(cptr, cptr, &me, "SSL Handshake failed."); - } - break; - } - free(pending); - } - lastPending = pending; - } -} - -static int ssl_handshake_outgoing(struct SSLConnection *connection) { - int ret = SSL_do_handshake(connection->session); - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); - - switch(SSL_get_error(connection->session, ret)) { - case SSL_ERROR_NONE: - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); - FlagSet(&connection->flags, SSLFLAG_READY); - - ssl_handshake_completed(connection, 1); - break; - case SSL_ERROR_WANT_READ: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); - break; - case SSL_ERROR_WANT_WRITE: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); - break; - default: - - break; - } -} - -struct SSLConnection *ssl_create_connect(int fd, void *data, enum SSLDataType datatype) { - struct SSLOutConnection *connection = malloc(sizeof(*connection)); - struct SSLConnection *sslconn = (struct SSLConnection *)connection; - struct SSLPendingConections *pending = NULL; - - if(!connection) - return NULL; - - if(!ssl_is_initialized) - ssl_init(); - - connection->context = SSL_CTX_new(SSLv23_client_method()); - if(!connection->context) { - goto ssl_create_connect_failed; - } - connection->session = SSL_new(connection->context); - if(!connection->session) { - goto ssl_create_connect_failed; - } - if(!SSL_set_fd(connection->session, fd)) { - goto ssl_create_connect_failed; - } - SSL_set_connect_state(connection->session); - FlagSet(&connection->flags, SSLFLAG_OUTGOING); - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); - - pending = malloc(sizeof(*pending)); - if(!pending) { - goto ssl_create_connect_failed; - } - pending->connection = connection; - pending->next = firstPendingConection; - firstPendingConection = pending; - - pending->data = data; - pending->datatype = datatype; - - return sslconn; -ssl_create_connect_failed: - free(connection); - return NULL; -} - -void ssl_start_handshake_connect(struct SSLConnection *connection) { - ssl_handshake_outgoing(connection); -} - -struct SSLListener *ssl_create_listener() { - if(!ssl_is_initialized) - ssl_init(); - - struct SSLListener *listener = calloc(1, sizeof(*listener)); - listener->context = SSL_CTX_new(SSLv23_server_method()); - if(!listener->context) { - goto ssl_create_listener_failed; - } - - char *certfile = conf_get_local()->sslcertfile; - char *keyfile = conf_get_local()->sslkeyfile; - char *cafile = conf_get_local()->sslcafile; - - if(!certfile) { - goto ssl_create_listener_failed; - } - if(!keyfile) { - keyfile = certfile; - } - - /* load certificate */ - if(SSL_CTX_use_certificate_file(listener->context, certfile, SSL_FILETYPE_PEM) <= 0) { - goto ssl_create_listener_failed; - } - /* load keyfile */ - if(SSL_CTX_use_PrivateKey_file(listener->context, keyfile, SSL_FILETYPE_PEM) <= 0) { - goto ssl_create_listener_failed; - } - /* check certificate and keyfile */ - if(!SSL_CTX_check_private_key(listener->context)) { - goto ssl_create_listener_failed; - } - /* load cafile */ - if(cafile && cafile[0] && SSL_CTX_load_verify_locations(listener->context, cafile, NULL) <= 0) { - goto ssl_create_listener_failed; - } - FlagSet(&listener->flags, SSLFLAG_READY); - return listener; -ssl_create_listener_failed: - free(listener); - return NULL; -} - -static int ssl_handshake_incoming(struct SSLConnection *connection) { - int result = SSL_accept(connection->session); - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); - switch(SSL_get_error(connection->session, result)) { - case SSL_ERROR_NONE: - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); - FlagSet(&connection->flags, SSLFLAG_READY); - - ssl_handshake_completed(connection, 1); - return 0; - case SSL_ERROR_WANT_READ: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); - return 1; - case SSL_ERROR_WANT_WRITE: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); - return 1; - default: - //unset connection! - //Handshake error! - ssl_handshake_completed(connection, 0); - return 0; - } - return 0; -} - -struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype) { - if(!listener) - return NULL; - struct SSLPendingConections *pending = NULL; - struct SSLConnection *connection = malloc(sizeof(*connection)); - connection->session = SSL_new(listener->context); - if(!connection->session) { - goto ssl_start_handshake_listener_failed; - } - if(!SSL_set_fd(connection->session, fd)) { - goto ssl_start_handshake_listener_failed; - } - FlagSet(&connection->flags, SSLFLAG_INCOMING); - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); - - pending = malloc(sizeof(*pending)); - if(!pending) { - goto ssl_start_handshake_listener_failed; - } - pending->connection = connection; - pending->next = firstPendingConection; - firstPendingConection = pending; - - pending->data = data; - pending->datatype = datatype; - - ssl_handshake_incoming(connection); - return connection; -ssl_start_handshake_listener_failed: - free(connection); - return NULL; -} - -IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, unsigned int buflen, unsigned int *len) { - if(FlagHas(&connection->flags, SSLFLAG_HANDSHAKE)) { - if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { - ssl_handshake_incoming(connection); - return IO_BLOCKED; - } - if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { - ssl_handshake_outgoing(connection); - return IO_BLOCKED; - } - } - - *len = SSL_read(connection->session, buf, buflen); - FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); - int err = SSL_get_error(connection->session, *len); - switch(err) { - case SSL_ERROR_NONE: - return IO_SUCCESS; - case SSL_ERROR_ZERO_RETURN: - return IO_FAILURE; - case SSL_ERROR_WANT_READ: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); - return IO_BLOCKED; - case SSL_ERROR_WANT_WRITE: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); - return IO_BLOCKED; - case SSL_ERROR_SYSCALL: - return IO_FAILURE; - default: - return IO_FAILURE; - } -} - -static ssize_t ssl_writev(SSL *ssl, const struct iovec *vector, int count) { - char *buffer; - register char *bp; - size_t bytes, to_copy; - int i; - - /* Find the total number of bytes to be written. */ - bytes = 0; - for (i = 0; i < count; ++i) - bytes += vector[i].iov_len; - - /* Allocate a temporary buffer to hold the data. */ - buffer = (char *) alloca (bytes); - - /* Copy the data into BUFFER. */ - to_copy = bytes; - bp = buffer; - for (i = 0; i < count; ++i) { - size_t copy = ((vector[i].iov_len) > (to_copy) ? (to_copy) : (vector[i].iov_len)); - memcpy ((void *) bp, (void *) vector[i].iov_base, copy); - bp += copy; - to_copy -= copy; - if (to_copy == 0) - break; - } - return SSL_write(ssl, buffer, bytes); -} - -IOResult ssl_send_encrypt_plain(struct SSLConnection *connection, char* buf, int len) { - return SSL_write(connection->session, buf, len); -} - -IOResult ssl_send_encrypt(struct SSLConnection *connection, struct MsgQ* buf, unsigned int *count_in, unsigned int *count_out) { - int res; - int count; - struct iovec iov[IOV_MAX]; - - assert(0 != buf); - assert(0 != count_in); - assert(0 != count_out); - - *count_in = 0; - count = msgq_mapiov(buf, iov, IOV_MAX, count_in); - res = ssl_writev(connection->session, iov, count); - - switch(SSL_get_error(connection->session, res)) { - case SSL_ERROR_NONE: - case SSL_ERROR_ZERO_RETURN: - *count_out = (unsigned) res; - return IO_SUCCESS; - case SSL_ERROR_WANT_READ: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); - return IO_BLOCKED; - case SSL_ERROR_WANT_WRITE: - FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); - return IO_BLOCKED; - default: - *count_out = 0; - return IO_FAILURE; - } -} - -int ssl_connection_flush(struct SSLConnection *connection) { - if(connection) { - if(ssl_handshake(connection)) { - if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { - return ssl_handshake_incoming(connection); - } - if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { - return ssl_handshake_outgoing(connection); - } - } - } else { - struct SSLPendingConections *curr, *last = NULL, *next; - for(curr = firstPendingConection; curr; curr = next) { - next = curr->next; - if(!ssl_connection_flush(curr->connection)) { - // connection is already in auth process here, curr is freed! - continue; - } - last = curr; - } - } - return 0; -} - -const char* ssl_get_cipher(struct SSLConnection *connection) { - if(!connection) - return NULL; - return SSL_get_cipher(connection->session); -} - +#if defined(HAVE_GNUTLS) +#include "ssl.gnutls.c" +#elif defined(HAVE_OPENSSL) +#include "ssl.openssl.c" #else void ssl_free_connection(struct SSLConnection *connection) {} void ssl_free_listener(struct SSLConnection *listener) {} diff --git a/ircd/ssl.gnutls.c b/ircd/ssl.gnutls.c new file mode 100644 index 0000000..1b08540 --- /dev/null +++ b/ircd/ssl.gnutls.c @@ -0,0 +1,385 @@ +/* + * IRC - Internet Relay Chat, ircd/ssl.gnutls.c + * Copyright (C) 2015 pk910 (Philipp Kreil) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief Implementation of functions for handling ssl connections + * @version $Id$ + */ +#include + +#ifndef GNUTLS_SEC_PARAM_LEGACY +#define GNUTLS_SEC_PARAM_LEGACY 2 +#endif + +struct SSLPendingConections { + struct SSLConnection *connection; + struct SSLPendingConections *next; + + void *data; + enum SSLDataType datatype; +}; + +struct SSLPendingConections *firstPendingConection = NULL; +int ssl_is_initialized = 0; +static gnutls_dh_params_t ssl_dh_params; +static unsigned int ssl_dh_params_bits; + +static int ssl_generate_dh_params() { + ssl_dh_params_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY); + gnutls_dh_params_init(&ssl_dh_params); + gnutls_dh_params_generate2(ssl_dh_params, ssl_dh_params_bits); + return 0; +} + +static void ssl_init() { + if(ssl_is_initialized) + return; + ssl_is_initialized = 1; + int res; + res = gnutls_global_init(); + + if(res != GNUTLS_E_SUCCESS) { + //TODO: Log Errors + } +} + +void ssl_free_connection(struct SSLConnection *connection) { + gnutls_bye(connection->session, GNUTLS_SHUT_RDWR); + if(connection->credentials) + gnutls_certificate_free_credentials(connection->credentials); + gnutls_deinit(connection->session); + free(connection); +} + +void ssl_free_listener(struct SSLListener *listener) { + gnutls_certificate_free_credentials(listener->credentials); + gnutls_priority_deinit(listener->priority); + free(listener); +} + +static void ssl_handshake_completed(struct SSLConnection *connection, int success) { + struct SSLPendingConections *pending, *lastPending = NULL; + for(pending = firstPendingConection; pending; pending = pending->next) { + if(pending->connection == connection) { + if(lastPending) + lastPending->next = pending->next; + else + firstPendingConection = pending->next; + switch(pending->datatype) { + case SSLData_Client: { + struct Client *cptr = (struct Client *) pending->data; + if(success) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) + start_auth(cptr); + else if(!completed_connection(cptr)) + exit_client_msg(cptr, cptr, &me, "Registration failed."); + } else + exit_client_msg(cptr, cptr, &me, "SSL Handshake failed."); + } + break; + } + free(pending); + } + lastPending = pending; + } +} + +static int ssl_handshake_outgoing(struct SSLConnection *connection) { + int ret = gnutls_handshake(connection->session); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); + + if(ret < 0) { + if(gnutls_error_is_fatal(ret) == 0) { + if(gnutls_record_get_direction(connection->session)) + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + else + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return 1; + } else { + + return 0; + } + } else { + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_READY); + ssl_handshake_completed(connection, 1); + return 0; + } +} + +struct SSLConnection *ssl_create_connect(int fd, void *data, enum SSLDataType datatype) { + struct SSLConnection *connection = malloc(sizeof(*connection)); + struct SSLPendingConections *pending = NULL; + + if(!connection) + return NULL; + + if(!ssl_is_initialized) + ssl_init(); + + gnutls_certificate_allocate_credentials(&connection->credentials); + gnutls_init(&connection->session, GNUTLS_CLIENT); + + gnutls_priority_set_direct(connection->session, "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2", NULL); + gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->credentials); + + gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr_t)fd); + //gnutls_handshake_set_timeout(connection->session, 30); + + FlagSet(&connection->flags, SSLFLAG_OUTGOING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_create_connect_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + ssl_handshake_outgoing(connection); + + return connection; +ssl_create_connect_failed: + free(connection); + return NULL; +} + +void ssl_start_handshake_connect(struct SSLConnection *connection) { + ssl_handshake_outgoing(connection); +} + +struct SSLListener *ssl_create_listener() { + if(!ssl_is_initialized) + ssl_init(); + + struct SSLListener *listener = calloc(1, sizeof(*listener)); + + gnutls_priority_init(&listener->priority, "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2", NULL); + gnutls_certificate_allocate_credentials(&listener->credentials); + + char *certfile = conf_get_local()->sslcertfile; + char *keyfile = conf_get_local()->sslkeyfile; + char *cafile = conf_get_local()->sslcafile; + + if(!certfile) { + goto ssl_create_listener_failed; + } + if(!keyfile) { + keyfile = certfile; + } + + /* load certificate */ + if(gnutls_certificate_set_x509_key_file(listener->credentials, certfile, keyfile, GNUTLS_X509_FMT_PEM) < 0) { + goto ssl_create_listener_failed; + } + /* load cafile */ + //TODO: ca file check! + /* + if(cafile && cafile[0] && SSL_CTX_load_verify_locations(listener->context, cafile, NULL) <= 0) { + goto ssl_create_listener_failed; + } + */ + + gnutls_certificate_set_dh_params(listener->credentials, ssl_dh_params); + + FlagSet(&listener->flags, SSLFLAG_READY); + return listener; +ssl_create_listener_failed: + free(listener); + return NULL; +} + +struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype) { + if(!listener) + return NULL; + struct SSLPendingConections *pending = NULL; + struct SSLConnection *connection = malloc(sizeof(*connection)); + + gnutls_init(&connection->session, GNUTLS_SERVER); + gnutls_priority_set(connection->session, listener->priority); + gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, listener->credentials); + connection->credentials = NULL; + gnutls_dh_set_prime_bits(connection->session, ssl_dh_params_bits); + gnutls_certificate_server_set_request(connection->session, GNUTLS_CERT_IGNORE); + + gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr_t)fd); + + FlagSet(&connection->flags, SSLFLAG_INCOMING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_start_handshake_listener_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + ssl_handshake_outgoing(connection); + return connection; +ssl_start_handshake_listener_failed: + free(connection); + return NULL; +} + +IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, unsigned int buflen, unsigned int *len) { + if(FlagHas(&connection->flags, SSLFLAG_HANDSHAKE)) { + ssl_handshake_outgoing(connection); + return IO_BLOCKED; + } + + int ret = gnutls_record_recv(connection->session, buf, buflen); + + if(ret == 0) { + return IO_FAILURE; + } else if(ret < 0 && gnutls_error_is_fatal(ret) == 0) { + if(ret == GNUTLS_E_REHANDSHAKE) { + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + ssl_handshake_outgoing(connection); + return IO_BLOCKED; + } + } else if(ret < 0) { + return IO_FAILURE; + } + *len = ret; + return IO_SUCCESS; +} + +static ssize_t ssl_writev(gnutls_session_t ssl, const struct iovec *vector, int count) { + char *buffer; + register char *bp; + size_t bytes, to_copy; + int i; + + /* Find the total number of bytes to be written. */ + bytes = 0; + for (i = 0; i < count; ++i) + bytes += vector[i].iov_len; + + /* Allocate a temporary buffer to hold the data. */ + buffer = (char *) alloca (bytes); + + /* Copy the data into BUFFER. */ + to_copy = bytes; + bp = buffer; + for (i = 0; i < count; ++i) { + size_t copy = ((vector[i].iov_len) > (to_copy) ? (to_copy) : (vector[i].iov_len)); + memcpy ((void *) bp, (void *) vector[i].iov_base, copy); + bp += copy; + to_copy -= copy; + if (to_copy == 0) + break; + } + return gnutls_record_send(ssl, buffer, bytes); +} + +IOResult ssl_send_encrypt_plain(struct SSLConnection *connection, char* buf, int len) { + return gnutls_record_send(connection->session, buf, len); +} + +IOResult ssl_send_encrypt(struct SSLConnection *connection, struct MsgQ* buf, unsigned int *count_in, unsigned int *count_out) { + int res; + int count; + struct iovec iov[IOV_MAX]; + + assert(0 != buf); + assert(0 != count_in); + assert(0 != count_out); + + *count_in = 0; + count = msgq_mapiov(buf, iov, IOV_MAX, count_in); + res = ssl_writev(connection->session, iov, count); + + if(res == 0) { + *count_out = 0; + return IO_FAILURE; + } else if(res < 0 && gnutls_error_is_fatal(res) == 0) { + if(res == GNUTLS_E_REHANDSHAKE) { + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + ssl_handshake_outgoing(connection); + return IO_BLOCKED; + } + } else if(res < 0) { + *count_out = 0; + return IO_FAILURE; + } + *count_out = (unsigned) res; + return IO_SUCCESS; +} + +int ssl_connection_flush(struct SSLConnection *connection) { + if(connection) { + if(ssl_handshake(connection)) { + return ssl_handshake_outgoing(connection); + } + } else { + struct SSLPendingConections *curr, *last = NULL, *next; + for(curr = firstPendingConection; curr; curr = next) { + next = curr->next; + if(!ssl_connection_flush(curr->connection)) { + // connection is already in auth process here, curr is freed! + continue; + } + last = curr; + } + } + return 0; +} + +const char* ssl_get_cipher(struct SSLConnection *connection) { + if(!connection) + return NULL; + static char buf[401]; + const char *kx_name, *cipher_name, *mac_name; + unsigned int len, i; + char *dest; + + kx_name = gnutls_kx_get_name(gnutls_kx_get(connection->session)); + cipher_name = gnutls_cipher_get_name(gnutls_cipher_get(connection->session)); + mac_name = gnutls_mac_get_name(gnutls_mac_get(connection->session)); + + if(!kx_name || !cipher_name || !mac_name) { + return ""; + } + + len = strlen(kx_name) + strlen(cipher_name) + strlen(mac_name); + if(len > 395) { + return ""; + } + else { + dest = buf; + i = 0; + while((*dest++ = kx_name[i++])) /* empty */ ; + *(dest - 1) = '-'; + i = 0; + while((*dest++ = cipher_name[i++])) /* empty */ ; + *(dest - 1) = '-'; + i = 0; + while((*dest++ = mac_name[i++])) /* empty */ ; + return buf; + } +} diff --git a/ircd/ssl.openssl.c b/ircd/ssl.openssl.c new file mode 100644 index 0000000..f26a442 --- /dev/null +++ b/ircd/ssl.openssl.c @@ -0,0 +1,402 @@ +/* + * IRC - Internet Relay Chat, ircd/ssl.openssl.c + * Copyright (C) 2015 pk910 (Philipp Kreil) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/** @file + * @brief Implementation of functions for handling ssl connections + * @version $Id$ + */ + +static struct SSLPendingConections { + struct SSLConnection *connection; + struct SSLPendingConections *next; + + void *data; + enum SSLDataType datatype; +}; + +struct SSLPendingConections *firstPendingConection = NULL; +int ssl_is_initialized = 0; + +static void ssl_init() { + if(ssl_is_initialized) + return; + ssl_is_initialized = 1; + SSL_library_init(); + OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */ + SSL_load_error_strings(); +} + +void ssl_free_connection(struct SSLConnection *connection) { + SSL_CTX *context = NULL; + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + struct SSLOutConnection *outconn = (struct SSLOutConnection *)connection; + context = outconn->context; + } + SSL_shutdown(connection->session); + SSL_free(connection->session); + if(context) + SSL_CTX_free(context); + free(connection); +} + +void ssl_free_listener(struct SSLListener *listener) { + SSL_CTX_free(listener->context); + free(listener); +} + +static void ssl_handshake_completed(struct SSLConnection *connection, int success) { + struct SSLPendingConections *pending, *lastPending = NULL; + for(pending = firstPendingConection; pending; pending = pending->next) { + if(pending->connection == connection) { + if(lastPending) + lastPending->next = pending->next; + else + firstPendingConection = pending->next; + switch(pending->datatype) { + case SSLData_Client: { + struct Client *cptr = (struct Client *) pending->data; + if(success) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) + start_auth(cptr); + else if(!completed_connection(cptr)) + exit_client_msg(cptr, cptr, &me, "Registration failed."); + } else + exit_client_msg(cptr, cptr, &me, "SSL Handshake failed."); + } + break; + } + free(pending); + } + lastPending = pending; + } +} + +static int ssl_handshake_outgoing(struct SSLConnection *connection) { + int ret = SSL_do_handshake(connection->session); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); + + switch(SSL_get_error(connection->session, ret)) { + case SSL_ERROR_NONE: + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_READY); + + ssl_handshake_completed(connection, 1); + break; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + break; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + break; + default: + + break; + } +} + +struct SSLConnection *ssl_create_connect(int fd, void *data, enum SSLDataType datatype) { + struct SSLOutConnection *connection = malloc(sizeof(*connection)); + struct SSLConnection *sslconn = (struct SSLConnection *)connection; + struct SSLPendingConections *pending = NULL; + + if(!connection) + return NULL; + + if(!ssl_is_initialized) + ssl_init(); + + connection->context = SSL_CTX_new(SSLv23_client_method()); + if(!connection->context) { + goto ssl_create_connect_failed; + } + connection->session = SSL_new(connection->context); + if(!connection->session) { + goto ssl_create_connect_failed; + } + if(!SSL_set_fd(connection->session, fd)) { + goto ssl_create_connect_failed; + } + SSL_set_connect_state(connection->session); + FlagSet(&connection->flags, SSLFLAG_OUTGOING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_create_connect_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + return sslconn; +ssl_create_connect_failed: + free(connection); + return NULL; +} + +void ssl_start_handshake_connect(struct SSLConnection *connection) { + ssl_handshake_outgoing(connection); +} + +struct SSLListener *ssl_create_listener() { + if(!ssl_is_initialized) + ssl_init(); + + struct SSLListener *listener = calloc(1, sizeof(*listener)); + listener->context = SSL_CTX_new(SSLv23_server_method()); + if(!listener->context) { + goto ssl_create_listener_failed; + } + + char *certfile = conf_get_local()->sslcertfile; + char *keyfile = conf_get_local()->sslkeyfile; + char *cafile = conf_get_local()->sslcafile; + + if(!certfile) { + goto ssl_create_listener_failed; + } + if(!keyfile) { + keyfile = certfile; + } + + /* load certificate */ + if(SSL_CTX_use_certificate_file(listener->context, certfile, SSL_FILETYPE_PEM) <= 0) { + goto ssl_create_listener_failed; + } + /* load keyfile */ + if(SSL_CTX_use_PrivateKey_file(listener->context, keyfile, SSL_FILETYPE_PEM) <= 0) { + goto ssl_create_listener_failed; + } + /* check certificate and keyfile */ + if(!SSL_CTX_check_private_key(listener->context)) { + goto ssl_create_listener_failed; + } + /* load cafile */ + if(cafile && cafile[0] && SSL_CTX_load_verify_locations(listener->context, cafile, NULL) <= 0) { + goto ssl_create_listener_failed; + } + FlagSet(&listener->flags, SSLFLAG_READY); + return listener; +ssl_create_listener_failed: + free(listener); + return NULL; +} + +static int ssl_handshake_incoming(struct SSLConnection *connection) { + int result = SSL_accept(connection->session); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_W); + switch(SSL_get_error(connection->session, result)) { + case SSL_ERROR_NONE: + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_READY); + + ssl_handshake_completed(connection, 1); + return 0; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return 1; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return 1; + default: + //unset connection! + //Handshake error! + ssl_handshake_completed(connection, 0); + return 0; + } + return 0; +} + +struct SSLConnection *ssl_start_handshake_listener(struct SSLListener *listener, int fd, void *data, enum SSLDataType datatype) { + if(!listener) + return NULL; + struct SSLPendingConections *pending = NULL; + struct SSLConnection *connection = malloc(sizeof(*connection)); + connection->session = SSL_new(listener->context); + if(!connection->session) { + goto ssl_start_handshake_listener_failed; + } + if(!SSL_set_fd(connection->session, fd)) { + goto ssl_start_handshake_listener_failed; + } + FlagSet(&connection->flags, SSLFLAG_INCOMING); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE); + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + + pending = malloc(sizeof(*pending)); + if(!pending) { + goto ssl_start_handshake_listener_failed; + } + pending->connection = connection; + pending->next = firstPendingConection; + firstPendingConection = pending; + + pending->data = data; + pending->datatype = datatype; + + ssl_handshake_incoming(connection); + return connection; +ssl_start_handshake_listener_failed: + free(connection); + return NULL; +} + +IOResult ssl_recv_decrypt(struct SSLConnection *connection, char *buf, unsigned int buflen, unsigned int *len) { + if(FlagHas(&connection->flags, SSLFLAG_HANDSHAKE)) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { + ssl_handshake_incoming(connection); + return IO_BLOCKED; + } + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + ssl_handshake_outgoing(connection); + return IO_BLOCKED; + } + } + + *len = SSL_read(connection->session, buf, buflen); + FlagClr(&connection->flags, SSLFLAG_HANDSHAKE_R); + int err = SSL_get_error(connection->session, *len); + switch(err) { + case SSL_ERROR_NONE: + return IO_SUCCESS; + case SSL_ERROR_ZERO_RETURN: + return IO_FAILURE; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return IO_BLOCKED; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return IO_BLOCKED; + case SSL_ERROR_SYSCALL: + return IO_FAILURE; + default: + return IO_FAILURE; + } +} + +static ssize_t ssl_writev(SSL *ssl, const struct iovec *vector, int count) { + char *buffer; + register char *bp; + size_t bytes, to_copy; + int i; + + /* Find the total number of bytes to be written. */ + bytes = 0; + for (i = 0; i < count; ++i) + bytes += vector[i].iov_len; + + /* Allocate a temporary buffer to hold the data. */ + buffer = (char *) alloca (bytes); + + /* Copy the data into BUFFER. */ + to_copy = bytes; + bp = buffer; + for (i = 0; i < count; ++i) { + size_t copy = ((vector[i].iov_len) > (to_copy) ? (to_copy) : (vector[i].iov_len)); + memcpy ((void *) bp, (void *) vector[i].iov_base, copy); + bp += copy; + to_copy -= copy; + if (to_copy == 0) + break; + } + return SSL_write(ssl, buffer, bytes); +} + +IOResult ssl_send_encrypt_plain(struct SSLConnection *connection, char* buf, int len) { + return SSL_write(connection->session, buf, len); +} + +IOResult ssl_send_encrypt(struct SSLConnection *connection, struct MsgQ* buf, unsigned int *count_in, unsigned int *count_out) { + int res; + int count; + struct iovec iov[IOV_MAX]; + + assert(0 != buf); + assert(0 != count_in); + assert(0 != count_out); + + *count_in = 0; + count = msgq_mapiov(buf, iov, IOV_MAX, count_in); + res = ssl_writev(connection->session, iov, count); + + switch(SSL_get_error(connection->session, res)) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + *count_out = (unsigned) res; + return IO_SUCCESS; + case SSL_ERROR_WANT_READ: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_R); + return IO_BLOCKED; + case SSL_ERROR_WANT_WRITE: + FlagSet(&connection->flags, SSLFLAG_HANDSHAKE_W); + return IO_BLOCKED; + default: + *count_out = 0; + return IO_FAILURE; + } +} + +int ssl_connection_flush(struct SSLConnection *connection) { + if(connection) { + if(ssl_handshake(connection)) { + if(FlagHas(&connection->flags, SSLFLAG_INCOMING)) { + return ssl_handshake_incoming(connection); + } + if(FlagHas(&connection->flags, SSLFLAG_OUTGOING)) { + return ssl_handshake_outgoing(connection); + } + } + } else { + struct SSLPendingConections *curr, *last = NULL, *next; + for(curr = firstPendingConection; curr; curr = next) { + next = curr->next; + if(!ssl_connection_flush(curr->connection)) { + // connection is already in auth process here, curr is freed! + continue; + } + last = curr; + } + } + return 0; +} + +const char* ssl_get_cipher(struct SSLConnection *connection) { + if(!connection) + return NULL; + static char buf[400]; + char buf2[128]; + int bits; + SSL_CIPHER *c; + + buf[0] = '\0'; + strcpy(buf, SSL_get_version(connection->session)); + strcat(buf, "-"); + strcat(buf, SSL_get_cipher(connection->session)); + c = SSL_get_current_cipher(connection->session); + SSL_CIPHER_get_bits(c, &bits); + strcat(buf, "-"); + ircd_snprintf(0, buf2, sizeof(buf2), "%d", bits); + strcat(buf, buf2); + strcat(buf, "bits"); + return buf; +} -- 2.20.1