ircu2/ircd/ssl.c

734 lines
19 KiB
C

/************************************************************************
* IRC - Internet Relay Chat, ircd/ssl.c
* Copyright (C) 2002 Alex Badea <vampire@go.ro>
* 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 <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/uio.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#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 */