X-Git-Url: http://git.pk910.de/?p=ircu2.10.12-pk.git;a=blobdiff_plain;f=ircd%2Fssl.gnutls.c;fp=ircd%2Fssl.gnutls.c;h=1b0854098af7f244c37a6a4e104286d9a45b0ecc;hp=0000000000000000000000000000000000000000;hb=f26079b861fde8de42f9e2db5a0f1a9330ce1ed5;hpb=99ba4795414c55c34fb778da3b3b0563170212a5 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; + } +}