diff --git a/README.FIRST b/README.FIRST index eb81531e1..8c23c45f4 100644 --- a/README.FIRST +++ b/README.FIRST @@ -27,7 +27,8 @@ Necessary Requirements: Feature Specific Requirements: - For SSL Clients, SSL Challenge controlled OPER feature, and encrypted server links, - a working OpenSSL library. + a working OpenSSL library or GnuTLS library. CHALLENGE is not supported on GnuTLS + yet. - For encrypted oper and (optional) server passwords, a working DES, MD5, or SHA library implementing crypt(). diff --git a/libratbox/configure.ac b/libratbox/configure.ac index 2c7c313be..8d5a77e3b 100644 --- a/libratbox/configure.ac +++ b/libratbox/configure.ac @@ -340,9 +340,34 @@ fi +dnl GnuTLS support +AC_MSG_CHECKING(for GnuTLS) +AC_ARG_ENABLE(gnutls, +[AC_HELP_STRING([--enable-gnutls],[Enable GnuTLS support.]) +AC_HELP_STRING([--disable-gnutls],[Disable GnuTLS support.])], +[cf_enable_gnutls=$enableval], +[cf_enable_gnutls="auto"]) + +if test "$cf_enable_gnutls" != no; then + PKG_CHECK_MODULES(GNUTLS, [gnutls], [ + cf_enable_gnutls="yes" + ], [cf_enable_gnutls="no"]) +fi + +if test "$cf_enable_gnutls" = "auto" -a "$cf_enable_openssl" = "yes"; then + cf_enable_gnutls="no" +fi + if test x"$cf_enable_openssl" != xno; then AC_DEFINE(HAVE_OPENSSL,1,[Has OpenSSL]) + GNUTLS_CFLAGS="" + GNUTLS_LIBS="" SSL_TYPE="openssl" +elif test x"$cf_enable_gnutls" != xno; then + AC_DEFINE(HAVE_GNUTLS, 1, [Has GnuTLS]) + SSL_LIBS="" + SSL_CFLAGS="" + SSL_TYPE="gnutls" fi @@ -407,6 +432,8 @@ AC_SUBST(CFLAGS) AC_SUBST(SEDOBJ) AC_SUBST(SSL_CFLAGS) AC_SUBST(SSL_LIBS) +AC_SUBST(GNUTLS_CFLAGS) +AC_SUBST(GNUTLS_LIBS) if test "$prefix" = "NONE"; then AC_DEFINE_UNQUOTED(RB_PREFIX, "$ac_default_prefix", [Prefix where libratbox is installed.]) diff --git a/libratbox/include/arc4random.h b/libratbox/include/arc4random.h index 0734ebeec..6bdd701fd 100644 --- a/libratbox/include/arc4random.h +++ b/libratbox/include/arc4random.h @@ -1,6 +1,6 @@ -#if !defined(HAVE_OPENSSL) && !defined(HAVE_ARC4RANDOM) +#if !defined(HAVE_OPENSSL) && !defined(HAVE_GNUTLS) && !defined(HAVE_ARC4RANDOM) void arc4random_stir(void); uint32_t arc4random(void); void arc4random_addrandom(uint8_t *dat, int datlen); diff --git a/libratbox/src/Makefile.am b/libratbox/src/Makefile.am index dd8816a82..a32a6efe3 100644 --- a/libratbox/src/Makefile.am +++ b/libratbox/src/Makefile.am @@ -2,7 +2,7 @@ AUTOMAKE_OPTIONS = foreign -INCLUDES = -I. -I../include @SSL_CFLAGS@ +INCLUDES = -I. -I../include @SSL_CFLAGS@ @GNUTLS_CFLAGS@ BUILT_SOURCES = version.c @@ -20,6 +20,7 @@ libratbox_la_SOURCES = \ balloc.c \ commio.c \ openssl.c \ + gnutls.c \ nossl.c \ event.c \ ratbox_lib.c \ @@ -41,7 +42,7 @@ libratbox_la_SOURCES = \ version.c -libratbox_la_LDFLAGS = @SSL_LIBS@ -avoid-version -no-undefined -export-symbols export-syms.txt -libratbox_la_LIBADD = @SSL_LIBS@ +libratbox_la_LDFLAGS = @SSL_LIBS@ @GNUTLS_LIBS@ -avoid-version -no-undefined -export-symbols export-syms.txt +libratbox_la_LIBADD = @SSL_LIBS@ @GNUTLS_LIBS@ lib_LTLIBRARIES = libratbox.la diff --git a/libratbox/src/arc4random.c b/libratbox/src/arc4random.c index 587779e18..dde356e37 100644 --- a/libratbox/src/arc4random.c +++ b/libratbox/src/arc4random.c @@ -31,7 +31,7 @@ #include #include -#if !defined(HAVE_OPENSSL) && !defined(HAVE_ARC4RANDOM) +#if !defined(HAVE_OPENSSL) && !defined(HAVE_GNUTLS) && !defined(HAVE_ARC4RANDOM) #include "arc4random.h" diff --git a/libratbox/src/gnutls.c b/libratbox/src/gnutls.c new file mode 100644 index 000000000..032318158 --- /dev/null +++ b/libratbox/src/gnutls.c @@ -0,0 +1,609 @@ +/* + * libratbox: a library used by ircd-ratbox and other things + * gnutls.c: gnutls related code + * + * Copyright (C) 2007-2008 ircd-ratbox development team + * Copyright (C) 2007-2008 Aaron Sethman + * + * 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 2 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * $Id: gnutls.c 26296 2008-12-13 03:36:00Z androsyn $ + */ + +#include +#include +#include +#include +#ifdef HAVE_GNUTLS + +#include +#include +#include + +static gnutls_certificate_credentials x509; +static gnutls_dh_params dh_params; + +/* These are all used for getting GnuTLS to supply a client cert. */ +#define MAX_CERTS 6 +static unsigned int x509_cert_count; +static gnutls_x509_crt_t x509_cert[MAX_CERTS]; +static gnutls_x509_privkey_t x509_key; +static int cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs, + const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr_st *st); + + +#define SSL_P(x) *((gnutls_session_t *)F->ssl) + +void +rb_ssl_shutdown(rb_fde_t *F) +{ + int i; + if(F == NULL || F->ssl == NULL) + return; + for(i = 0; i < 4; i++) + { + if(gnutls_bye(SSL_P(F), GNUTLS_SHUT_RDWR) == GNUTLS_E_SUCCESS) + break; + } + gnutls_deinit(SSL_P(F)); + rb_free(F->ssl); +} + +unsigned int +rb_ssl_handshake_count(rb_fde_t *F) +{ + return F->handshake_count; +} + +void +rb_ssl_clear_handshake_count(rb_fde_t *F) +{ + F->handshake_count = 0; +} + +static void +rb_ssl_timeout(rb_fde_t *F, void *notused) +{ + lrb_assert(F->accept != NULL); + F->accept->callback(F, RB_ERR_TIMEOUT, NULL, 0, F->accept->data); +} + + +static int +do_ssl_handshake(rb_fde_t *F, PF * callback, void *data) +{ + int ret; + int flags; + + ret = gnutls_handshake(SSL_P(F)); + if(ret < 0) + { + if((ret == GNUTLS_E_INTERRUPTED && rb_ignore_errno(errno)) || ret == GNUTLS_E_AGAIN) + { + if(gnutls_record_get_direction(SSL_P(F)) == 0) + flags = RB_SELECT_READ; + else + flags = RB_SELECT_WRITE; + rb_setselect(F, flags, callback, data); + return 0; + } + F->ssl_errno = ret; + return -1; + } + return 1; /* handshake is finished..go about life */ +} + +static void +rb_ssl_tryaccept(rb_fde_t *F, void *data) +{ + int ret; + struct acceptdata *ad; + + lrb_assert(F->accept != NULL); + + ret = do_ssl_handshake(F, rb_ssl_tryaccept, NULL); + + /* do_ssl_handshake does the rb_setselect */ + if(ret == 0) + return; + + ad = F->accept; + F->accept = NULL; + rb_settimeout(F, 0, NULL, NULL); + rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL); + + if(ret > 0) + ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data); + else + ad->callback(F, RB_ERROR_SSL, NULL, 0, ad->data); + + rb_free(ad); +} + +void +rb_ssl_start_accepted(rb_fde_t *new_F, ACCB * cb, void *data, int timeout) +{ + gnutls_session_t *ssl; + new_F->type |= RB_FD_SSL; + ssl = new_F->ssl = rb_malloc(sizeof(gnutls_session_t)); + new_F->accept = rb_malloc(sizeof(struct acceptdata)); + + new_F->accept->callback = cb; + new_F->accept->data = data; + rb_settimeout(new_F, timeout, rb_ssl_timeout, NULL); + + new_F->accept->addrlen = 0; + + gnutls_init(ssl, GNUTLS_SERVER); + gnutls_set_default_priority(*ssl); + gnutls_credentials_set(*ssl, GNUTLS_CRD_CERTIFICATE, x509); + gnutls_dh_set_prime_bits(*ssl, 1024); + gnutls_transport_set_ptr(*ssl, (gnutls_transport_ptr_t) (long int)new_F->fd); + gnutls_certificate_server_set_request(*ssl, GNUTLS_CERT_REQUEST); + if(do_ssl_handshake(new_F, rb_ssl_tryaccept, NULL)) + { + struct acceptdata *ad = new_F->accept; + new_F->accept = NULL; + ad->callback(new_F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data); + rb_free(ad); + } + +} + + + + +void +rb_ssl_accept_setup(rb_fde_t *F, rb_fde_t *new_F, struct sockaddr *st, int addrlen) +{ + new_F->type |= RB_FD_SSL; + new_F->ssl = rb_malloc(sizeof(gnutls_session_t)); + new_F->accept = rb_malloc(sizeof(struct acceptdata)); + + new_F->accept->callback = F->accept->callback; + new_F->accept->data = F->accept->data; + rb_settimeout(new_F, 10, rb_ssl_timeout, NULL); + memcpy(&new_F->accept->S, st, addrlen); + new_F->accept->addrlen = addrlen; + + gnutls_init((gnutls_session_t *) new_F->ssl, GNUTLS_SERVER); + gnutls_set_default_priority(SSL_P(new_F)); + gnutls_credentials_set(SSL_P(new_F), GNUTLS_CRD_CERTIFICATE, x509); + gnutls_dh_set_prime_bits(SSL_P(new_F), 1024); + gnutls_transport_set_ptr(SSL_P(new_F), (gnutls_transport_ptr_t) (long int)rb_get_fd(new_F)); + gnutls_certificate_server_set_request(SSL_P(new_F), GNUTLS_CERT_REQUEST); + if(do_ssl_handshake(F, rb_ssl_tryaccept, NULL)) + { + struct acceptdata *ad = F->accept; + F->accept = NULL; + ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data); + rb_free(ad); + } +} + + + + +static ssize_t +rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size_t count) +{ + ssize_t ret; + gnutls_session_t *ssl = F->ssl; + + if(r_or_w == 0) + ret = gnutls_record_recv(*ssl, rbuf, count); + else + ret = gnutls_record_send(*ssl, wbuf, count); + + if(ret < 0) + { + switch (ret) + { + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + if(rb_ignore_errno(errno)) + { + if(gnutls_record_get_direction(*ssl) == 0) + return RB_RW_SSL_NEED_READ; + else + return RB_RW_SSL_NEED_WRITE; + break; + } + default: + F->ssl_errno = ret; + errno = EIO; + return RB_RW_IO_ERROR; + } + } + return ret; +} + +ssize_t +rb_ssl_read(rb_fde_t *F, void *buf, size_t count) +{ + return rb_ssl_read_or_write(0, F, buf, NULL, count); +} + +ssize_t +rb_ssl_write(rb_fde_t *F, const void *buf, size_t count) +{ + return rb_ssl_read_or_write(1, F, NULL, buf, count); +} + +static void +rb_gcry_random_seed(void *unused) +{ + gcry_fast_random_poll(); +} + +int +rb_init_ssl(void) +{ + gnutls_global_init(); + + if(gnutls_certificate_allocate_credentials(&x509) != GNUTLS_E_SUCCESS) + { + rb_lib_log("rb_init_ssl: Unable to allocate SSL/TLS certificate credentials"); + return 0; + } + + /* This should be changed to gnutls_certificate_set_retrieve_function2 once + * everyone in the world has upgraded to GnuTLS 3. + */ + gnutls_certificate_client_set_retrieve_function(x509, cert_callback); + + rb_event_addish("rb_gcry_random_seed", rb_gcry_random_seed, NULL, 300); + return 1; +} + +/* We only have one certificate to authenticate with, as both client and server. Unfortunately, + * GnuTLS tries to be clever, and as client, will attempt to use a certificate that the server + * will trust. We usually use self-signed certs, though, so the result of this search is always + * nothing. Therefore, it uses no certificate to authenticate as a client. This is undesirable + * as it breaks fingerprint auth. Thus, we use this callback to force GnuTLS to always + * authenticate with our certificate at all times. + */ +static int +cert_callback(gnutls_session_t session, const gnutls_datum_t *req_ca_rdn, int nreqs, + const gnutls_pk_algorithm_t *sign_algos, int sign_algos_len, gnutls_retr_st *st) +{ + /* XXX - ugly hack. Tell GnuTLS to use the first (only) certificate we have for auth. */ + st->type = GNUTLS_CRT_X509; + st->ncerts = x509_cert_count; + st->cert.x509 = x509_cert; + st->key.x509 = x509_key; + + return 0; +} + +static void +rb_free_datum_t(gnutls_datum_t * d) +{ + rb_free(d->data); + rb_free(d); +} + +static gnutls_datum_t * +rb_load_file_into_datum_t(const char *file) +{ + FILE *f; + gnutls_datum_t *datum; + struct stat fileinfo; + if((f = fopen(file, "r")) == NULL) + return NULL; + if(fstat(fileno(f), &fileinfo)) + return NULL; + + datum = rb_malloc(sizeof(gnutls_datum_t)); + + if(fileinfo.st_size > 131072) /* deal with retards */ + datum->size = 131072; + else + datum->size = fileinfo.st_size; + + datum->data = rb_malloc(datum->size + 1); + fread(datum->data, datum->size, 1, f); + fclose(f); + return datum; +} + +int +rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile) +{ + int ret; + gnutls_datum_t *d_cert, *d_key; + if(cert == NULL) + { + rb_lib_log("rb_setup_ssl_server: No certificate file"); + return 0; + } + + if((d_cert = rb_load_file_into_datum_t(cert)) == NULL) + { + rb_lib_log("rb_setup_ssl_server: Error loading certificate: %s", strerror(errno)); + return 0; + } + + if((d_key = rb_load_file_into_datum_t(keyfile)) == NULL) + { + rb_lib_log("rb_setup_ssl_server: Error loading key: %s", strerror(errno)); + return 0; + } + + /* In addition to creating the certificate set, we also need to store our cert elsewhere + * so we can force GnuTLS to identify with it when acting as a client. + */ + gnutls_x509_privkey_init(&x509_key); + if ((ret = gnutls_x509_privkey_import(x509_key, d_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) + { + rb_lib_log("rb_setup_ssl_server: Error loading key file: %s", gnutls_strerror(ret)); + return 0; + } + + x509_cert_count = MAX_CERTS; + if ((ret = gnutls_x509_crt_list_import(x509_cert, &x509_cert_count, d_cert, GNUTLS_X509_FMT_PEM, + GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED)) < 0) + { + rb_lib_log("rb_setup_ssl_server: Error loading certificate: %s", gnutls_strerror(ret)); + return 0; + } + x509_cert_count = ret; + + if((ret = + gnutls_certificate_set_x509_key_mem(x509, d_cert, d_key, + GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) + { + rb_lib_log("rb_setup_ssl_server: Error loading certificate or key file: %s", + gnutls_strerror(ret)); + return 0; + } + + rb_free_datum_t(d_cert); + rb_free_datum_t(d_key); + + if(dhfile != NULL) + { + if(gnutls_dh_params_init(&dh_params) == GNUTLS_E_SUCCESS) + { + gnutls_datum_t *data; + int xret; + data = rb_load_file_into_datum_t(dhfile); + if(data != NULL) + { + xret = gnutls_dh_params_import_pkcs3(dh_params, data, + GNUTLS_X509_FMT_PEM); + if(xret < 0) + rb_lib_log + ("rb_setup_ssl_server: Error parsing DH file: %s\n", + gnutls_strerror(xret)); + rb_free_datum_t(data); + } + gnutls_certificate_set_dh_params(x509, dh_params); + } + else + rb_lib_log("rb_setup_ssl_server: Unable to setup DH parameters"); + } + return 1; +} + +int +rb_ssl_listen(rb_fde_t *F, int backlog, int defer_accept) +{ + int result; + + result = rb_listen(F->fd, backlog, defer_accept); + F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL; + + return result; +} + +struct ssl_connect +{ + CNCB *callback; + void *data; + int timeout; +}; + +static void +rb_ssl_connect_realcb(rb_fde_t *F, int status, struct ssl_connect *sconn) +{ + F->connect->callback = sconn->callback; + F->connect->data = sconn->data; + rb_free(sconn); + rb_connect_callback(F, status); +} + +static void +rb_ssl_tryconn_timeout_cb(rb_fde_t *F, void *data) +{ + rb_ssl_connect_realcb(F, RB_ERR_TIMEOUT, data); +} + +static void +rb_ssl_tryconn_cb(rb_fde_t *F, void *data) +{ + struct ssl_connect *sconn = data; + int ret; + + ret = do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); + + switch (ret) + { + case -1: + rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn); + break; + case 0: + /* do_ssl_handshake does the rb_setselect stuff */ + return; + default: + break; + + + } + rb_ssl_connect_realcb(F, RB_OK, sconn); +} + +static void +rb_ssl_tryconn(rb_fde_t *F, int status, void *data) +{ + struct ssl_connect *sconn = data; + if(status != RB_OK) + { + rb_ssl_connect_realcb(F, status, sconn); + return; + } + + F->type |= RB_FD_SSL; + + + rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn); + F->ssl = rb_malloc(sizeof(gnutls_session_t)); + gnutls_init(F->ssl, GNUTLS_CLIENT); + gnutls_set_default_priority(SSL_P(F)); + gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509); + gnutls_dh_set_prime_bits(SSL_P(F), 1024); + gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd); + + do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); +} + +void +rb_connect_tcp_ssl(rb_fde_t *F, struct sockaddr *dest, + struct sockaddr *clocal, int socklen, CNCB * callback, void *data, int timeout) +{ + struct ssl_connect *sconn; + if(F == NULL) + return; + + sconn = rb_malloc(sizeof(struct ssl_connect)); + sconn->data = data; + sconn->callback = callback; + sconn->timeout = timeout; + rb_connect_tcp(F, dest, clocal, socklen, rb_ssl_tryconn, sconn, timeout); + +} + +void +rb_ssl_start_connected(rb_fde_t *F, CNCB * callback, void *data, int timeout) +{ + struct ssl_connect *sconn; + if(F == NULL) + return; + + sconn = rb_malloc(sizeof(struct ssl_connect)); + sconn->data = data; + sconn->callback = callback; + sconn->timeout = timeout; + F->connect = rb_malloc(sizeof(struct conndata)); + F->connect->callback = callback; + F->connect->data = data; + F->type |= RB_FD_SSL; + F->ssl = rb_malloc(sizeof(gnutls_session_t)); + + gnutls_init(F->ssl, GNUTLS_CLIENT); + gnutls_set_default_priority(SSL_P(F)); + gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509); + gnutls_dh_set_prime_bits(SSL_P(F), 1024); + gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd); + + rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn); + + do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); +} + +int +rb_init_prng(const char *path, prng_seed_t seed_type) +{ + gcry_fast_random_poll(); + return 1; +} + +int +rb_get_random(void *buf, size_t length) +{ + gcry_randomize(buf, length, GCRY_STRONG_RANDOM); + return 1; +} + +int +rb_get_pseudo_random(void *buf, size_t length) +{ + gcry_randomize(buf, length, GCRY_WEAK_RANDOM); + return 1; +} + +const char * +rb_get_ssl_strerror(rb_fde_t *F) +{ + return gnutls_strerror(F->ssl_errno); +} + +int +rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN]) +{ + gnutls_x509_crt_t cert; + unsigned int cert_list_size; + const gnutls_datum_t *cert_list; + uint8_t digest[RB_SSL_CERTFP_LEN * 2]; + size_t digest_size; + + if (gnutls_certificate_type_get(SSL_P(F)) != GNUTLS_CRT_X509) + return 0; + + if (gnutls_x509_crt_init(&cert) < 0) + return 0; + + cert_list_size = 0; + cert_list = gnutls_certificate_get_peers(SSL_P(F), &cert_list_size); + if (cert_list == NULL) + { + gnutls_x509_crt_deinit(cert); + return 0; + } + + if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0) + { + gnutls_x509_crt_deinit(cert); + return 0; + } + + if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, digest, &digest_size) < 0) + { + gnutls_x509_crt_deinit(cert); + return 0; + } + + memcpy(certfp, digest, RB_SSL_CERTFP_LEN); + + gnutls_x509_crt_deinit(cert); + return 1; +} + +int +rb_supports_ssl(void) +{ + return 1; +} + +void +rb_get_ssl_info(char *buf, size_t len) +{ + rb_snprintf(buf, len, "GNUTLS: compiled (%s), library(%s)", + LIBGNUTLS_VERSION, gnutls_check_version(NULL)); +} + + +#endif /* HAVE_GNUTLS */ diff --git a/libratbox/src/nossl.c b/libratbox/src/nossl.c index cca9a13e2..b6ea24408 100644 --- a/libratbox/src/nossl.c +++ b/libratbox/src/nossl.c @@ -26,7 +26,7 @@ #include #include -#if !defined(HAVE_OPENSSL) +#if !defined(HAVE_OPENSSL) && !defined(HAVE_GNUTLS) #include "arc4random.h"