/************************************************************************ * IRC - Internet Relay Chat, ircd/ssl.c * Copyright (C) 2002 Alex Badea * Copyright (C) 2013 Matthew Beeching (Jobe) * * See file AUTHORS in IRC package for additional names of * the programmers. * * 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 Implimentation of common SSL functions. * @version $Id:$ */ #include "config.h" #include "client.h" #include "ircd_alloc.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_snprintf.h" #include "ircd_string.h" #include "listener.h" #include "s_bsd.h" #include "s_conf.h" #include "s_debug.h" #include "send.h" #include "ssl.h" #ifdef USE_SSL #include #include #include #include #include #include #include #include #include #include #include #ifndef IOV_MAX #define IOV_MAX 1024 #endif /* IOV_MAX */ SSL_CTX *ssl_server_ctx; SSL_CTX *ssl_client_ctx; SSL_CTX *ssl_init_server_ctx(void); SSL_CTX *ssl_init_client_ctx(void); int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *cert); void ssl_set_nonblocking(SSL *s); int ssl_smart_shutdown(SSL *ssl); void sslfail(char *txt); void binary_to_hex(unsigned char *bin, char *hex, int length); int ssl_init(void) { SSL_library_init(); SSL_load_error_strings(); ERR_load_crypto_strings(); Debug((DEBUG_NOTICE, "SSL: read %d bytes of randomness", RAND_load_file("/dev/urandom", 4096))); ssl_server_ctx = ssl_init_server_ctx(); if (!ssl_server_ctx) return -1; ssl_client_ctx = ssl_init_client_ctx(); if (!ssl_client_ctx) return -1; return 0; } int ssl_reinit(int sig) { SSL_CTX *temp_ctx; if (1 == sig) sendto_opmask_butone(0, SNO_OLDSNO, "Got signal SIGUSR1, reloading SSL certificates"); /* Attempt to reinitialize server context, return on error */ temp_ctx = ssl_init_server_ctx(); if (!temp_ctx) return -1; /* Now reinitialize server context for real. */ SSL_CTX_free(temp_ctx); SSL_CTX_free(ssl_server_ctx); ssl_server_ctx = ssl_init_server_ctx(); /* Attempt to reinitialize client context, return on error */ temp_ctx = ssl_init_client_ctx(); if (!temp_ctx) return -1; /* Now reinitialize client context for real. */ SSL_CTX_free(temp_ctx); SSL_CTX_free(ssl_client_ctx); ssl_client_ctx = ssl_init_client_ctx(); return 0; } SSL_CTX *ssl_init_server_ctx(void) { SSL_CTX *server_ctx = NULL; int vrfyopts = SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE; server_ctx = SSL_CTX_new(SSLv23_server_method()); if (!server_ctx) { sslfail("Error creating new server context"); return NULL; } if (feature_bool(FEAT_SSL_REQUIRECLIENTCERT)) vrfyopts |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; if (feature_bool(FEAT_SSL_NOSSLV2)) SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv2); if (feature_bool(FEAT_SSL_NOSSLV3)) SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3); if (feature_bool(FEAT_SSL_NOTLSV1)) SSL_CTX_set_options(server_ctx, SSL_OP_NO_TLSv1); SSL_CTX_set_verify(server_ctx, vrfyopts, ssl_verify_callback); SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF); if (SSL_CTX_use_certificate_chain_file(server_ctx, feature_str(FEAT_SSL_CERTFILE)) <= 0) { sslfail("Error loading SSL certificate for server context"); SSL_CTX_free(server_ctx); return NULL; } if (SSL_CTX_use_PrivateKey_file(server_ctx, feature_str(FEAT_SSL_KEYFILE), SSL_FILETYPE_PEM) <= 0) { sslfail("Error loading SSL key for server context"); SSL_CTX_free(server_ctx); return NULL; } if (!SSL_CTX_check_private_key(server_ctx)) { sslfail("Error checking SSL private key for server context"); SSL_CTX_free(server_ctx); return NULL; } if (!EmptyString(feature_str(FEAT_SSL_CIPHERS))) { if (SSL_CTX_set_cipher_list(server_ctx, feature_str(FEAT_SSL_CIPHERS)) == 0) { sslfail("Error setting SSL cipher list for clients"); SSL_CTX_free(server_ctx); return NULL; } } if (!EmptyString(feature_str(FEAT_SSL_CACERTFILE))) { if (!SSL_CTX_load_verify_locations(server_ctx, feature_str(FEAT_SSL_CACERTFILE), NULL)) { sslfail("Error loading trusted CA certificates file for server context"); SSL_CTX_free(server_ctx); return NULL; } } #if defined(SSL_CTX_set_ecdh_auto) SSL_CTX_set_ecdh_auto(server_ctx, 1); #elif OPENSSL_VERSION_NUMBER < 0x10100000L SSL_CTX_set_tmp_ecdh(server_ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); #else #endif SSL_CTX_set_options(server_ctx, SSL_OP_SINGLE_ECDH_USE|SSL_OP_SINGLE_DH_USE); return server_ctx; } SSL_CTX *ssl_init_client_ctx(void) { SSL_CTX *client_ctx = NULL; client_ctx = SSL_CTX_new(SSLv23_client_method()); if (!client_ctx) { sslfail("Error creating new client context"); return NULL; } if (feature_bool(FEAT_SSL_NOSSLV2)) SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv2); if (feature_bool(FEAT_SSL_NOSSLV3)) SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3); if (feature_bool(FEAT_SSL_NOTLSV1)) SSL_CTX_set_options(client_ctx, SSL_OP_NO_TLSv1); SSL_CTX_set_session_cache_mode(client_ctx, SSL_SESS_CACHE_OFF); if (SSL_CTX_use_certificate_chain_file(client_ctx, feature_str(FEAT_SSL_CERTFILE)) <= 0) { sslfail("Error loading SSL certificate for client context"); SSL_CTX_free(client_ctx); return NULL; } if (SSL_CTX_use_PrivateKey_file(client_ctx, feature_str(FEAT_SSL_KEYFILE), SSL_FILETYPE_PEM) <= 0) { sslfail("Error loading SSL key for client context"); SSL_CTX_free(client_ctx); return NULL; } if (!SSL_CTX_check_private_key(client_ctx)) { sslfail("Error checking SSL private key for client context"); SSL_CTX_free(client_ctx); return NULL; } return client_ctx; } int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *cert) { int err = 0; err = X509_STORE_CTX_get_error(cert); if (feature_bool(FEAT_SSL_NOSELFSIGNED) && (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) return 0; if (feature_bool(FEAT_SSL_VERIFYCERT)) { if (!feature_bool(FEAT_SSL_NOSELFSIGNED) && (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) return 1; return preverify_ok; } return 1; } void ssl_abort(struct Client *cptr) { Debug((DEBUG_DEBUG, "SSL: aborted")); if (cli_socket(cptr).ssl) SSL_free(cli_socket(cptr).ssl); cli_socket(cptr).ssl = NULL; } int ssl_accept(struct Client *cptr) { int r = 0; if (!IsSSLNeedAccept(cptr)) return -1; if ((r = SSL_accept(cli_socket(cptr).ssl)) <= 0) { unsigned long err = SSL_get_error(cli_socket(cptr).ssl, r); if (err) { switch (err) { case SSL_ERROR_SYSCALL: if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: return 1; default: cli_sslerror(cptr) = ssl_error_str(err, errno); Debug((DEBUG_ERROR, "SSL_accept: %s", cli_sslerror(cptr))); SSL_set_shutdown(cli_socket(cptr).ssl, SSL_RECEIVED_SHUTDOWN); ssl_smart_shutdown(cli_socket(cptr).ssl); SSL_free(cli_socket(cptr).ssl); cli_socket(cptr).ssl = NULL; cli_error(cptr) = errno; return 0; } return 0; } return 0; } ClearSSLNeedAccept(cptr); if (SSL_is_init_finished(cli_socket(cptr).ssl)) { char *sslfp = ssl_get_fingerprint(cli_socket(cptr).ssl); if (sslfp) ircd_strncpy(cli_sslclifp(cptr), sslfp, BUFSIZE+1); } return -1; } int ssl_starttls(struct Client *cptr) { if (!cli_socket(cptr).ssl) { cli_socket(cptr).ssl = SSL_new(ssl_server_ctx); SSL_set_fd(cli_socket(cptr).ssl, cli_socket(cptr).s_fd); ssl_set_nonblocking(cli_socket(cptr).ssl); SetSSLNeedAccept(cptr); } return ssl_accept(cptr); } void ssl_add_connection(struct Listener *listener, int fd) { SSL* ssl; assert(0 != listener); if (!(ssl = SSL_new(ssl_server_ctx))) { Debug((DEBUG_DEBUG, "SSL_new failed")); close(fd); return; } SSL_set_fd(ssl, fd); ssl_set_nonblocking(ssl); add_connection(listener, fd, ssl); } void ssl_doerror(struct Client *cptr) { unsigned long err = 0; char ebuf[120]; memset(&ebuf, 0, 120); err = ERR_get_error(); ERR_error_string(err, (char *)&ebuf); sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for client %s: %s", cli_name(cptr), ebuf); } void ssl_doerror_anon(void) { unsigned long err = 0; char ebuf[120]; memset(&ebuf, 0, 120); err = ERR_get_error(); ERR_error_string(err, (char *)&ebuf); sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for unknown client: %s", ebuf); } /* * ssl_recv - non blocking read of a connection * returns: * 1 if data was read or socket is blocked (recoverable error) * count_out > 0 if data was read * * 0 if socket closed from other end * -1 if an unrecoverable error occurred */ IOResult ssl_recv(struct Socket *socketh, struct Client *cptr, char* buf, unsigned int length, unsigned int* count_out) { int res; unsigned long err = 0; assert(0 != socketh); assert(0 != buf); assert(0 != count_out); *count_out = 0; errno = 0; ERR_clear_error(); res = SSL_read(socketh->ssl, buf, length); switch (SSL_get_error(socketh->ssl, res)) { case SSL_ERROR_NONE: *count_out = (unsigned) res; return IO_SUCCESS; case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_X509_LOOKUP: Debug((DEBUG_DEBUG, "SSL_read returned WANT_ - retrying")); return IO_BLOCKED; case SSL_ERROR_SYSCALL: if (res < 0 && errno == EINTR) return IO_BLOCKED; /* ??? */ break; case SSL_ERROR_ZERO_RETURN: /* close_notify received */ SSL_shutdown(socketh->ssl); /* Send close_notify back */ break; } err = SSL_get_error(cli_socket(cptr).ssl, res); cli_sslerror(cptr) = ssl_error_str(err, errno); cli_error(cptr) = errno; if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) ssl_doerror(cptr); return IO_FAILURE; } /* * ssl_sendv - non blocking writev to a connection * returns: * 1 if data was written * count_out contains amount written * * 0 if write call blocked, recoverable error * -1 if an unrecoverable error occurred */ IOResult ssl_sendv(struct Socket *socketh, struct Client *cptr, struct MsgQ* buf, unsigned int* count_in, unsigned int* count_out) { int res; int count; int k; struct iovec iov[IOV_MAX]; IOResult retval = IO_BLOCKED; int ssl_err = 0; errno = 0; assert(0 != socketh); assert(0 != buf); assert(0 != count_in); assert(0 != count_out); *count_in = 0; *count_out = 0; count = msgq_mapiov(buf, iov, IOV_MAX, count_in); for (k = 0; k < count; k++) { res = SSL_write(socketh->ssl, iov[k].iov_base, iov[k].iov_len); ssl_err = SSL_get_error(socketh->ssl, res); Debug((DEBUG_DEBUG, "SSL_write returned %d, error code %d.", res, ssl_err)); switch (ssl_err) { case SSL_ERROR_NONE: *count_out += (unsigned) res; retval = IO_SUCCESS; break; case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_X509_LOOKUP: Debug((DEBUG_DEBUG, "SSL_write returned want WRITE, READ, or X509; returning retval %d", retval)); return retval; case SSL_ERROR_SSL: { int errorValue; Debug((DEBUG_ERROR, "SSL_write returned SSL_ERROR_SSL, errno %d, retval %d, res %d, ssl error code %d", errno, retval, res, ssl_err)); while((errorValue = ERR_get_error())) { Debug((DEBUG_ERROR, " Error Queue: %d -- %s", errorValue, ERR_error_string(errorValue, NULL))); } cli_sslerror(cptr) = ssl_error_str(ssl_err, errno); cli_error(cptr) = errno; ssl_doerror(cptr); return IO_FAILURE; } case SSL_ERROR_SYSCALL: if(res < 0 && (errno == EWOULDBLOCK || errno == EINTR || errno == EBUSY || errno == EAGAIN)) { Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SYSCALL, errno %d - returning retval %d", errno, retval)); return retval; } else { Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SYSCALL - errno %d - returning IO_FAILURE", errno)); cli_sslerror(cptr) = ssl_error_str(ssl_err, errno); cli_error(cptr) = errno; ssl_doerror(cptr); return IO_FAILURE; } /* if(errno == EAGAIN) * its what unreal ircd does..* { Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SSL - errno %d returning retval %d", errno, retval)); return retval; } */ case SSL_ERROR_ZERO_RETURN: SSL_shutdown(socketh->ssl); return IO_FAILURE; default: Debug((DEBUG_DEBUG, "SSL_write return fell through - errno %d returning retval %d", errno, retval)); return retval; /* unknown error, assume block or success*/ } } Debug((DEBUG_DEBUG, "SSL_write return fell through(2) - errno %d returning retval %d", errno, retval)); return retval; } int ssl_send(struct Client *cptr, const char *buf, unsigned int len) { char fmt[16]; if (!cli_socket(cptr).ssl) return write(cli_fd(cptr), buf, len); /* * XXX HACK * * Incomplete SSL writes must be retried with the same write buffer; * at this point SSL_write usually fails, so the data must be queued. * We're abusing the normal send queue for this. * Also strip \r\n from message, as sendrawto_one adds it later * this hack sucks. it conflicted with prority queues - caused random * ssl disconnections for YEARS. In summery, this hack == bad. I may * have solved that, but this still makes me nervous. */ ircd_snprintf(0, fmt, sizeof(fmt), "%%.%us", len - 2); sendrawto_one(cptr, fmt, buf); send_queued(cptr); return len; } int ssl_murder(void *ssl, int fd, const char *buf) { if (!ssl) { if (buf) (void)!write(fd, buf, strlen(buf)); } else { if (buf) SSL_write((SSL *) ssl, buf, strlen(buf)); SSL_free((SSL *) ssl); } close(fd); return 0; } void ssl_free(struct Socket *socketh) { if (!socketh->ssl) return; SSL_free(socketh->ssl); } char *ssl_get_cipher(SSL *ssl) { static char buf[400]; int bits; const SSL_CIPHER *c; c = SSL_get_current_cipher(ssl); SSL_CIPHER_get_bits(c, &bits); /* Use ircd_snprintf for safe bounded string formatting */ ircd_snprintf(0, buf, sizeof(buf), "%s-%s-%dbits", SSL_get_version(ssl), SSL_get_cipher(ssl), bits); return (buf); } int ssl_connect(struct Socket* sock, struct ConfItem *aconf) { int r = 0; if (!sock->ssl) { sock->ssl = SSL_new(ssl_client_ctx); SSL_set_fd(sock->ssl, sock->s_fd); SSL_set_connect_state(sock->ssl); if (!EmptyString(aconf->sslciphers)) { if (SSL_set_cipher_list(sock->ssl, aconf->sslciphers) == 0) { return -2; } } ssl_set_nonblocking(sock->ssl); } r = SSL_connect(sock->ssl); if (r<=0) { if ((SSL_get_error(sock->ssl, r) == SSL_ERROR_WANT_WRITE) || (SSL_get_error(sock->ssl, r) == SSL_ERROR_WANT_READ)) return 0; /* Needs to call SSL_connect() again */ else if (SSL_get_error(sock->ssl, r) == SSL_ERROR_SSL) { unsigned long e = ERR_get_error(); sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for connection attempt: %s", ERR_error_string(e, NULL)); return -1; /* Fatal error */ } else { sendto_opmask_butone(0, SNO_TCPCOMMON, "Unknown SSL error for connection attempt (%d)", SSL_get_error(sock->ssl, r)); return -1; /* Fatal error */ } } return 1; /* Connection complete */ } char* ssl_get_fingerprint(SSL *ssl) { X509* cert; unsigned int n = 0; unsigned char md[EVP_MAX_MD_SIZE]; const EVP_MD *digest = EVP_sha256(); static char hex[BUFSIZE + 1]; cert = SSL_get_peer_certificate(ssl); if (!(cert)) return NULL; if (!X509_digest(cert, digest, md, &n)) { X509_free(cert); return NULL; } binary_to_hex(md, hex, n); X509_free(cert); return (hex); } void ssl_set_nonblocking(SSL *s) { BIO_set_nbio(SSL_get_rbio(s),1); BIO_set_nbio(SSL_get_wbio(s),1); } int ssl_is_init_finished(SSL *s) { return SSL_is_init_finished(s); } int ssl_smart_shutdown(SSL *ssl) { char i; int rc; rc = 0; for(i = 0; i < 4; i++) { if((rc = SSL_shutdown(ssl))) break; } return rc; } /** * Retrieve a static string for the given SSL error. * * \param err The error to look up. * \param my_errno The value of errno to use in case we want to call strerror(). */ char *ssl_error_str(int err, int my_errno) { static char ssl_errbuf[256]; char *ssl_errstr = NULL; switch(err) { case SSL_ERROR_NONE: ssl_errstr = "SSL: No error"; break; case SSL_ERROR_SSL: ssl_errstr = "Internal OpenSSL error or protocol error"; ssl_doerror_anon(); break; case SSL_ERROR_WANT_READ: ssl_errstr = "OpenSSL functions requested a read()"; break; case SSL_ERROR_WANT_WRITE: ssl_errstr = "OpenSSL functions requested a write()"; break; case SSL_ERROR_WANT_X509_LOOKUP: ssl_errstr = "OpenSSL requested a X509 lookup which didn't arrive"; break; case SSL_ERROR_SYSCALL: snprintf(ssl_errbuf, sizeof(ssl_errbuf), "%s", strerror(my_errno)); ssl_errstr = ssl_errbuf; break; case SSL_ERROR_ZERO_RETURN: ssl_errstr = "Underlying socket operation returned zero"; break; case SSL_ERROR_WANT_CONNECT: ssl_errstr = "OpenSSL functions wanted a connect()"; break; default: ssl_errstr = "Unknown OpenSSL error (huh?)"; } return ssl_errstr; } const char* ssl_get_verify_result(SSL *ssl) { int vrfyresult = SSL_get_verify_result(ssl); return X509_verify_cert_error_string(vrfyresult); } void sslfail(char *txt) { unsigned long err = ERR_get_error(); char string[120]; if (!err) { Debug((DEBUG_DEBUG, "%s: poof", txt)); } else { ERR_error_string(err, string); Debug((DEBUG_FATAL, "%s: %s", txt, string)); } } void binary_to_hex(unsigned char *bin, char *hex, int length) { static const char trans[] = "0123456789ABCDEF"; int i; for(i = 0; i < length; i++) { hex[i << 1] = trans[bin[i] >> 4]; hex[(i << 1) + 1] = trans[bin[i] & 0xf]; } hex[i << 1] = '\0'; } #endif /* USE_SSL */