/*
* librb: a library used by ircd-ratbox and other things
- * openssl.c: openssl related code
+ * openssl.c: OpenSSL backend
*
* Copyright (C) 2007-2008 ircd-ratbox development team
* Copyright (C) 2007-2008 Aaron Sethman <androsyn@ratbox.org>
+ * Copyright (C) 2015-2016 Aaron Jones <aaronmdjones@gmail.com>
*
* 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
#include <commio-int.h>
#include <commio-ssl.h>
-#include <openssl/ssl.h>
-#include <openssl/dh.h>
-#include <openssl/err.h>
-#include <openssl/evp.h>
-#include <openssl/rand.h>
-#include <openssl/opensslv.h>
-/*
- * This is a mess but what can you do when the library authors
- * refuse to play ball with established conventions?
- */
-#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x20020002L)
-# define LRB_HAVE_TLS_METHOD_API 1
-#else
-# if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-# define LRB_HAVE_TLS_METHOD_API 1
-# endif
-#endif
+#include "openssl_ratbox.h"
+
+typedef enum
+{
+ RB_FD_TLS_DIRECTION_IN = 0,
+ RB_FD_TLS_DIRECTION_OUT = 1
+} rb_fd_tls_direction;
+
+#define SSL_P(x) ((SSL *)((x)->ssl))
+
+
+
+static SSL_CTX *ssl_ctx = NULL;
+
+struct ssl_connect
+{
+ CNCB *callback;
+ void *data;
+ int timeout;
+};
+
+static const char *rb_ssl_strerror(unsigned long);
+static void rb_ssl_connect_realcb(rb_fde_t *, int, struct ssl_connect *);
+
+
/*
- * Use SSL_CTX_set_ecdh_auto() in OpenSSL 1.0.2 only
- * Use SSL_CTX_set1_curves_list() in OpenSSL 1.0.2 and above
- * TODO: Merge this into the block above if LibreSSL implements them
+ * Internal OpenSSL-specific code
*/
-#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10002000L)
-# define LRB_HAVE_TLS_SET_CURVES 1
-# if (OPENSSL_VERSION_NUMBER < 0x10100000L)
-# define LRB_HAVE_TLS_ECDH_AUTO 1
-# endif
-#endif
-
-static SSL_CTX *ssl_server_ctx;
-static SSL_CTX *ssl_client_ctx;
-static int librb_index = -1;
static unsigned long
-get_last_err(void)
+rb_ssl_last_err(void)
{
- unsigned long t_err, err = 0;
- err = ERR_get_error();
- if(err == 0)
- return 0;
+ unsigned long err_saved, err = 0;
- while((t_err = ERR_get_error()) > 0)
- err = t_err;
+ while((err_saved = ERR_get_error()) != 0)
+ err = err_saved;
return err;
}
-void
-rb_ssl_shutdown(rb_fde_t *F)
+static void
+rb_ssl_init_fd(rb_fde_t *const F, const rb_fd_tls_direction dir)
{
- int i;
- if(F == NULL || F->ssl == NULL)
- return;
- SSL_set_shutdown((SSL *) F->ssl, SSL_RECEIVED_SHUTDOWN);
+ (void) rb_ssl_last_err();
- for(i = 0; i < 4; i++)
+ F->ssl = SSL_new(ssl_ctx);
+
+ if(F->ssl == NULL)
{
- if(SSL_shutdown((SSL *) F->ssl))
- break;
+ rb_lib_log("%s: SSL_new: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
+ rb_close(F);
+ return;
}
- get_last_err();
- SSL_free((SSL *) F->ssl);
-}
-unsigned int
-rb_ssl_handshake_count(rb_fde_t *F)
-{
- return F->handshake_count;
-}
+ switch(dir)
+ {
+ case RB_FD_TLS_DIRECTION_IN:
+ SSL_set_accept_state(SSL_P(F));
+ break;
+ case RB_FD_TLS_DIRECTION_OUT:
+ SSL_set_connect_state(SSL_P(F));
+ break;
+ }
-void
-rb_ssl_clear_handshake_count(rb_fde_t *F)
-{
- F->handshake_count = 0;
+ SSL_set_fd(SSL_P(F), rb_get_fd(F));
}
static void
-rb_ssl_timeout(rb_fde_t *F, void *notused)
+rb_ssl_accept_common(rb_fde_t *const F, void *const data __attribute__((unused)))
{
+ lrb_assert(F != NULL);
lrb_assert(F->accept != NULL);
- F->accept->callback(F, RB_ERR_TIMEOUT, NULL, 0, F->accept->data);
-}
+ lrb_assert(F->accept->callback != NULL);
+ lrb_assert(F->ssl != NULL);
+ (void) rb_ssl_last_err();
-static void
-rb_ssl_info_callback(SSL * ssl, int where, int ret)
-{
- if(where & SSL_CB_HANDSHAKE_START)
+ int ret = SSL_do_handshake(SSL_P(F));
+ int err = SSL_get_error(SSL_P(F), ret);
+
+ if(ret == 1)
{
- rb_fde_t *F = SSL_get_ex_data(ssl, librb_index);
- if(F == NULL)
- return;
F->handshake_count++;
+
+ rb_settimeout(F, 0, NULL, NULL);
+ rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL);
+
+ struct acceptdata *const ad = F->accept;
+ F->accept = NULL;
+ ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data);
+ rb_free(ad);
+
+ return;
+ }
+ if(ret == -1 && err == SSL_ERROR_WANT_READ)
+ {
+ rb_setselect(F, RB_SELECT_READ, rb_ssl_accept_common, NULL);
+ return;
+ }
+ if(ret == -1 && err == SSL_ERROR_WANT_WRITE)
+ {
+ rb_setselect(F, RB_SELECT_WRITE, rb_ssl_accept_common, NULL);
+ return;
}
-}
-static void
-rb_setup_ssl_cb(rb_fde_t *F)
-{
- SSL_set_ex_data(F->ssl, librb_index, (char *)F);
- SSL_set_info_callback((SSL *) F->ssl, (void (*)(const SSL *,int,int))rb_ssl_info_callback);
+ errno = EIO;
+ F->ssl_errno = (unsigned long) err;
+ F->accept->callback(F, RB_ERROR_SSL, NULL, 0, F->accept->data);
}
static void
-rb_ssl_tryaccept(rb_fde_t *F, void *data)
+rb_ssl_connect_common(rb_fde_t *const F, void *const data)
{
- int ssl_err;
- lrb_assert(F->accept != NULL);
- int flags;
- struct acceptdata *ad;
+ lrb_assert(F != NULL);
+ lrb_assert(F->ssl != NULL);
- if(!SSL_is_init_finished((SSL *) F->ssl))
- {
- if((ssl_err = SSL_accept((SSL *) F->ssl)) <= 0)
- {
- switch (ssl_err = SSL_get_error((SSL *) F->ssl, ssl_err))
- {
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- if(ssl_err == SSL_ERROR_WANT_WRITE)
- flags = RB_SELECT_WRITE;
- else
- flags = RB_SELECT_READ;
- F->ssl_errno = get_last_err();
- rb_setselect(F, flags, rb_ssl_tryaccept, NULL);
- break;
- case SSL_ERROR_SYSCALL:
- F->accept->callback(F, RB_ERROR, NULL, 0, F->accept->data);
- break;
- default:
- F->ssl_errno = get_last_err();
- F->accept->callback(F, RB_ERROR_SSL, NULL, 0, F->accept->data);
- break;
- }
- return;
- }
- }
- rb_settimeout(F, 0, NULL, NULL);
- rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL);
+ (void) rb_ssl_last_err();
- ad = F->accept;
- F->accept = NULL;
- ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data);
- rb_free(ad);
+ int ret = SSL_do_handshake(SSL_P(F));
+ int err = SSL_get_error(SSL_P(F), ret);
-}
+ if(ret == 1)
+ {
+ F->handshake_count++;
+ rb_settimeout(F, 0, NULL, NULL);
+ rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL);
-static void
-rb_ssl_accept_common(rb_fde_t *new_F)
-{
- int ssl_err;
- if((ssl_err = SSL_accept((SSL *) new_F->ssl)) <= 0)
+ rb_ssl_connect_realcb(F, RB_OK, data);
+
+ return;
+ }
+ if(ret == -1 && err == SSL_ERROR_WANT_READ)
{
- switch (ssl_err = SSL_get_error((SSL *) new_F->ssl, ssl_err))
- {
- case SSL_ERROR_SYSCALL:
- if(rb_ignore_errno(errno))
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- {
- new_F->ssl_errno = get_last_err();
- rb_setselect(new_F, RB_SELECT_READ | RB_SELECT_WRITE,
- rb_ssl_tryaccept, NULL);
- return;
- }
- default:
- new_F->ssl_errno = get_last_err();
- new_F->accept->callback(new_F, RB_ERROR_SSL, NULL, 0, new_F->accept->data);
- return;
- }
+ rb_setselect(F, RB_SELECT_READ, rb_ssl_connect_common, data);
+ return;
}
- else
+ if(ret == -1 && err == SSL_ERROR_WANT_WRITE)
{
- rb_ssl_tryaccept(new_F, NULL);
+ rb_setselect(F, RB_SELECT_WRITE, rb_ssl_connect_common, data);
+ return;
}
+
+ errno = EIO;
+ F->ssl_errno = (unsigned long) err;
+ rb_ssl_connect_realcb(F, RB_ERROR_SSL, data);
}
-void
-rb_ssl_start_accepted(rb_fde_t *new_F, ACCB * cb, void *data, int timeout)
+static const char *
+rb_ssl_strerror(const unsigned long err)
{
- new_F->type |= RB_FD_SSL;
- new_F->ssl = SSL_new(ssl_server_ctx);
- new_F->accept = rb_malloc(sizeof(struct acceptdata));
+ static char errbuf[512];
- new_F->accept->callback = cb;
- new_F->accept->data = data;
- rb_settimeout(new_F, timeout, rb_ssl_timeout, NULL);
+ ERR_error_string_n(err, errbuf, sizeof errbuf);
- new_F->accept->addrlen = 0;
- SSL_set_fd((SSL *) new_F->ssl, rb_get_fd(new_F));
- rb_setup_ssl_cb(new_F);
- rb_ssl_accept_common(new_F);
+ return errbuf;
}
-
-
-
-void
-rb_ssl_accept_setup(rb_fde_t *F, rb_fde_t *new_F, struct sockaddr *st, int addrlen)
+static int
+verify_accept_all_cb(const int preverify_ok __attribute__((unused)), X509_STORE_CTX *const x509_ctx __attribute__((unused)))
{
- new_F->type |= RB_FD_SSL;
- new_F->ssl = SSL_new(ssl_server_ctx);
- 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;
-
- SSL_set_fd((SSL *) new_F->ssl, rb_get_fd(new_F));
- rb_setup_ssl_cb(new_F);
- rb_ssl_accept_common(new_F);
+ return 1;
}
static ssize_t
-rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size_t count)
+rb_ssl_read_or_write(const int r_or_w, rb_fde_t *const F, void *const rbuf, const void *const wbuf, const size_t count)
{
ssize_t ret;
unsigned long err;
- SSL *ssl = F->ssl;
+
+ (void) rb_ssl_last_err();
if(r_or_w == 0)
- ret = (ssize_t) SSL_read(ssl, rbuf, (int)count);
+ ret = (ssize_t) SSL_read(SSL_P(F), rbuf, (int)count);
else
- ret = (ssize_t) SSL_write(ssl, wbuf, (int)count);
+ ret = (ssize_t) SSL_write(SSL_P(F), wbuf, (int)count);
if(ret < 0)
{
- switch (SSL_get_error(ssl, ret))
+ switch(SSL_get_error(SSL_P(F), ret))
{
case SSL_ERROR_WANT_READ:
errno = EAGAIN;
case SSL_ERROR_ZERO_RETURN:
return 0;
case SSL_ERROR_SYSCALL:
- err = get_last_err();
+ err = rb_ssl_last_err();
if(err == 0)
{
F->ssl_errno = 0;
}
break;
default:
- err = get_last_err();
+ err = rb_ssl_last_err();
break;
}
+
F->ssl_errno = err;
if(err > 0)
{
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 int
-verify_accept_all_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
-{
- return 1;
-}
-
-static const char *
-get_ssl_error(unsigned long err)
+make_certfp(X509 *const cert, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
{
- static char buf[512];
-
- ERR_error_string_n(err, buf, sizeof buf);
- return buf;
-}
+ unsigned int hashlen = 0;
+ const EVP_MD *md_type = NULL;
+ const ASN1_ITEM *item = NULL;
+ void *data = NULL;
-int
-rb_init_ssl(void)
-{
- int ret = 1;
- char librb_data[] = "librb data";
- const char librb_ciphers[] = "kEECDH+HIGH:kEDH+HIGH:HIGH:!RC4:!aNULL";
-#ifdef LRB_HAVE_TLS_SET_CURVES
- const char librb_curves[] = "P-521:P-384:P-256";
-#endif
- SSL_load_error_strings();
- SSL_library_init();
- librb_index = SSL_get_ex_new_index(0, librb_data, NULL, NULL, NULL);
-
-#ifndef LRB_HAVE_TLS_METHOD_API
- ssl_server_ctx = SSL_CTX_new(SSLv23_server_method());
-#else
- ssl_server_ctx = SSL_CTX_new(TLS_server_method());
-#endif
+ switch(method)
+ {
+ case RB_SSL_CERTFP_METH_CERT_SHA1:
+ hashlen = RB_SSL_CERTFP_LEN_SHA1;
+ md_type = EVP_sha1();
+ item = ASN1_ITEM_rptr(X509);
+ data = cert;
+ break;
+ case RB_SSL_CERTFP_METH_CERT_SHA256:
+ hashlen = RB_SSL_CERTFP_LEN_SHA256;
+ md_type = EVP_sha256();
+ item = ASN1_ITEM_rptr(X509);
+ data = cert;
+ break;
+ case RB_SSL_CERTFP_METH_CERT_SHA512:
+ hashlen = RB_SSL_CERTFP_LEN_SHA512;
+ md_type = EVP_sha512();
+ item = ASN1_ITEM_rptr(X509);
+ data = cert;
+ break;
+ case RB_SSL_CERTFP_METH_SPKI_SHA256:
+ hashlen = RB_SSL_CERTFP_LEN_SHA256;
+ md_type = EVP_sha256();
+ item = ASN1_ITEM_rptr(X509_PUBKEY);
+ data = X509_get_X509_PUBKEY(cert);
+ break;
+ case RB_SSL_CERTFP_METH_SPKI_SHA512:
+ hashlen = RB_SSL_CERTFP_LEN_SHA512;
+ md_type = EVP_sha512();
+ item = ASN1_ITEM_rptr(X509_PUBKEY);
+ data = X509_get_X509_PUBKEY(cert);
+ break;
+ default:
+ return 0;
+ }
- if(ssl_server_ctx == NULL)
+ if(ASN1_item_digest(item, md_type, data, certfp, &hashlen) != 1)
{
- rb_lib_log("rb_init_openssl: Unable to initialize OpenSSL server context: %s",
- get_ssl_error(ERR_get_error()));
- ret = 0;
+ rb_lib_log("%s: ASN1_item_digest: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
+ return 0;
}
- long server_options = SSL_CTX_get_options(ssl_server_ctx);
+ return (int) hashlen;
+}
-#ifndef LRB_HAVE_TLS_METHOD_API
- server_options |= SSL_OP_NO_SSLv2;
- server_options |= SSL_OP_NO_SSLv3;
-#endif
-#ifdef SSL_OP_SINGLE_DH_USE
- server_options |= SSL_OP_SINGLE_DH_USE;
-#endif
-#ifdef SSL_OP_SINGLE_ECDH_USE
- server_options |= SSL_OP_SINGLE_ECDH_USE;
-#endif
+/*
+ * External OpenSSL-specific code
+ */
-#ifdef SSL_OP_NO_TICKET
- server_options |= SSL_OP_NO_TICKET;
-#endif
+void
+rb_ssl_shutdown(rb_fde_t *const F)
+{
+ if(F == NULL || F->ssl == NULL)
+ return;
- server_options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+ (void) rb_ssl_last_err();
- SSL_CTX_set_options(ssl_server_ctx, server_options);
- SSL_CTX_set_verify(ssl_server_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_accept_all_cb);
- SSL_CTX_set_session_cache_mode(ssl_server_ctx, SSL_SESS_CACHE_OFF);
- SSL_CTX_set_cipher_list(ssl_server_ctx, librb_ciphers);
+ for(int i = 0; i < 4; i++)
+ {
+ int ret = SSL_shutdown(SSL_P(F));
+ int err = SSL_get_error(SSL_P(F), ret);
-#ifdef LRB_HAVE_TLS_SET_CURVES
- SSL_CTX_set1_curves_list(ssl_server_ctx, librb_curves);
-#endif
+ if(ret >= 0 || (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE))
+ break;
+ }
-#ifdef LRB_HAVE_TLS_ECDH_AUTO
- SSL_CTX_set_ecdh_auto(ssl_server_ctx, 1);
-#endif
+ SSL_free(SSL_P(F));
+ F->ssl = NULL;
+}
-#if !defined(LRB_HAVE_TLS_SET_CURVES) && !defined(LRB_HAVE_TLS_ECDH_AUTO)
- /* Set ECDHE on OpenSSL 1.0.0 & 1.0.1, but make sure it's actually available
- * (it's not by default on Solaris or Red Hat... fuck Red Hat and Oracle)
- */
- #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) && !defined(OPENSSL_NO_ECDH)
- EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp384r1);
- if (key) {
- SSL_CTX_set_tmp_ecdh(ssl_server_ctx, key);
- EC_KEY_free(key);
- }
- #endif
+int
+rb_init_ssl(void)
+{
+#ifndef LRB_SSL_NO_EXPLICIT_INIT
+ (void) SSL_library_init();
+ SSL_load_error_strings();
#endif
-#ifndef LRB_HAVE_TLS_METHOD_API
- ssl_client_ctx = SSL_CTX_new(SSLv23_client_method());
-#else
- ssl_client_ctx = SSL_CTX_new(TLS_client_method());
-#endif
+ rb_lib_log("%s: OpenSSL backend initialised", __func__);
+ return 1;
+}
- if(ssl_client_ctx == NULL)
+int
+rb_setup_ssl_server(const char *const certfile, const char *keyfile,
+ const char *const dhfile, const char *cipherlist)
+{
+ if(certfile == NULL)
{
- rb_lib_log("rb_init_openssl: Unable to initialize OpenSSL client context: %s",
- get_ssl_error(ERR_get_error()));
- ret = 0;
+ rb_lib_log("%s: no certificate file specified", __func__);
+ return 0;
}
-#ifndef LRB_HAVE_TLS_METHOD_API
- SSL_CTX_set_options(ssl_client_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
-#endif
+ if(keyfile == NULL)
+ keyfile = certfile;
-#ifdef SSL_OP_NO_TICKET
- SSL_CTX_set_options(ssl_client_ctx, SSL_OP_NO_TICKET);
-#endif
+ if(cipherlist == NULL)
+ cipherlist = rb_default_ciphers;
- SSL_CTX_set_cipher_list(ssl_client_ctx, librb_ciphers);
- return ret;
-}
+ (void) rb_ssl_last_err();
+ #ifdef LRB_HAVE_TLS_METHOD_API
+ SSL_CTX *const ssl_ctx_new = SSL_CTX_new(TLS_method());
+ #else
+ SSL_CTX *const ssl_ctx_new = SSL_CTX_new(SSLv23_method());
+ #endif
-int
-rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile, const char *cipher_list)
-{
- DH *dh;
- unsigned long err;
- if(cert == NULL)
+ if(ssl_ctx_new == NULL)
{
- rb_lib_log("rb_setup_ssl_server: No certificate file");
+ rb_lib_log("%s: SSL_CTX_new: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
return 0;
}
- if(!SSL_CTX_use_certificate_chain_file(ssl_server_ctx, cert) || !SSL_CTX_use_certificate_chain_file(ssl_client_ctx, cert))
+
+ if(SSL_CTX_use_certificate_chain_file(ssl_ctx_new, certfile) != 1)
{
- err = ERR_get_error();
- rb_lib_log("rb_setup_ssl_server: Error loading certificate file [%s]: %s", cert,
- get_ssl_error(err));
+ rb_lib_log("%s: SSL_CTX_use_certificate_chain_file ('%s'): %s", __func__, certfile,
+ rb_ssl_strerror(rb_ssl_last_err()));
+
+ SSL_CTX_free(ssl_ctx_new);
return 0;
}
- if(keyfile == NULL)
+ if(SSL_CTX_use_PrivateKey_file(ssl_ctx_new, keyfile, SSL_FILETYPE_PEM) != 1)
{
- rb_lib_log("rb_setup_ssl_server: No key file");
+ rb_lib_log("%s: SSL_CTX_use_PrivateKey_file ('%s'): %s", __func__, keyfile,
+ rb_ssl_strerror(rb_ssl_last_err()));
+
+ SSL_CTX_free(ssl_ctx_new);
return 0;
}
-
- if(!SSL_CTX_use_PrivateKey_file(ssl_server_ctx, keyfile, SSL_FILETYPE_PEM) || !SSL_CTX_use_PrivateKey_file(ssl_client_ctx, keyfile, SSL_FILETYPE_PEM))
+ if(dhfile == NULL)
{
- err = ERR_get_error();
- rb_lib_log("rb_setup_ssl_server: Error loading keyfile [%s]: %s", keyfile,
- get_ssl_error(err));
- return 0;
+ rb_lib_log("%s: no DH parameters file specified", __func__);
}
-
- if(dhfile != NULL)
+ else
{
- /* DH parameters aren't necessary, but they are nice..if they didn't pass one..that is their problem */
- BIO *bio = BIO_new_file(dhfile, "r");
- if(bio != NULL)
+ FILE *const dhf = fopen(dhfile, "r");
+ DH *dhp = NULL;
+
+ if(dhf == NULL)
{
- dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
- if(dh == NULL)
- {
- err = ERR_get_error();
- rb_lib_log
- ("rb_setup_ssl_server: Error loading DH params file [%s]: %s",
- dhfile, get_ssl_error(err));
- BIO_free(bio);
- return 0;
- }
- BIO_free(bio);
- SSL_CTX_set_tmp_dh(ssl_server_ctx, dh);
+ rb_lib_log("%s: fopen ('%s'): %s", __func__, dhfile, strerror(errno));
+ }
+ else if(PEM_read_DHparams(dhf, &dhp, NULL, NULL) == NULL)
+ {
+ rb_lib_log("%s: PEM_read_DHparams ('%s'): %s", __func__, dhfile,
+ rb_ssl_strerror(rb_ssl_last_err()));
+ fclose(dhf);
}
else
{
- err = ERR_get_error();
- rb_lib_log("rb_setup_ssl_server: Error loading DH params file [%s]: %s",
- dhfile, get_ssl_error(err));
+ SSL_CTX_set_tmp_dh(ssl_ctx_new, dhp);
+ DH_free(dhp);
+ fclose(dhf);
}
}
- if (cipher_list != NULL)
+ if(SSL_CTX_set_cipher_list(ssl_ctx_new, cipherlist) != 1)
{
- SSL_CTX_set_cipher_list(ssl_server_ctx, cipher_list);
- SSL_CTX_set_cipher_list(ssl_client_ctx, cipher_list);
+ rb_lib_log("%s: SSL_CTX_set_cipher_list: could not configure any ciphers", __func__);
+ SSL_CTX_free(ssl_ctx_new);
+ return 0;
}
+ SSL_CTX_set_session_cache_mode(ssl_ctx_new, SSL_SESS_CACHE_OFF);
+ SSL_CTX_set_verify(ssl_ctx_new, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_accept_all_cb);
+
+ #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+ (void) SSL_CTX_clear_options(ssl_ctx_new, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+ #endif
+
+ #ifndef LRB_HAVE_TLS_METHOD_API
+ (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ #endif
+
+ #ifdef SSL_OP_NO_TICKET
+ (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_NO_TICKET);
+ #endif
+
+ #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ #endif
+
+ #ifdef SSL_OP_SINGLE_DH_USE
+ (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_SINGLE_DH_USE);
+ #endif
+
+ #ifdef SSL_OP_SINGLE_ECDH_USE
+ (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_SINGLE_ECDH_USE);
+ #endif
+
+ #ifdef LRB_HAVE_TLS_ECDH_AUTO
+ (void) SSL_CTX_set_ecdh_auto(ssl_ctx_new, 1);
+ #endif
+
+ #ifdef LRB_HAVE_TLS_SET_CURVES
+ (void) SSL_CTX_set1_curves_list(ssl_ctx_new, rb_default_curves);
+ #else
+ # if (OPENSSL_VERSION_NUMBER >= 0x10000000L) && !defined(OPENSSL_NO_ECDH) && defined(NID_secp384r1)
+ EC_KEY *const ec_key = EC_KEY_new_by_curve_name(NID_secp384r1);
+ if(ec_key != NULL)
+ {
+ SSL_CTX_set_tmp_ecdh(ssl_ctx_new, ec_key);
+ EC_KEY_free(ec_key);
+ }
+ else
+ rb_lib_log("%s: EC_KEY_new_by_curve_name failed; will not enable ECDHE- ciphers", __func__);
+ # else
+ rb_lib_log("%s: OpenSSL built without ECDH support; will not enable ECDHE- ciphers", __func__);
+ # endif
+ #endif
+
+
+ if(ssl_ctx)
+ SSL_CTX_free(ssl_ctx);
+
+ ssl_ctx = ssl_ctx_new;
+
+
+ rb_lib_log("%s: TLS configuration successful", __func__);
return 1;
}
int
-rb_ssl_listen(rb_fde_t *F, int backlog, int defer_accept)
+rb_init_prng(const char *const path, prng_seed_t seed_type)
{
- int result;
+ (void) rb_ssl_last_err();
- result = rb_listen(F, backlog, defer_accept);
- F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL;
+ if(seed_type == RB_PRNG_FILE && RAND_load_file(path, -1) < 0)
+ rb_lib_log("%s: RAND_load_file: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
- return result;
+ if(RAND_status() != 1)
+ {
+ rb_lib_log("%s: RAND_status: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
+ return 0;
+ }
+
+ rb_lib_log("%s: PRNG initialised", __func__);
+ return 1;
}
-struct ssl_connect
+int
+rb_get_random(void *const buf, const size_t length)
{
- CNCB *callback;
- void *data;
- int timeout;
-};
+ (void) rb_ssl_last_err();
-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);
+ if(RAND_bytes(buf, (int) length) != 1)
+ {
+ rb_lib_log("%s: RAND_bytes: %s", __func__, rb_ssl_strerror(rb_ssl_last_err()));
+ return 0;
+ }
+
+ return 1;
}
-static void
-rb_ssl_tryconn_timeout_cb(rb_fde_t *F, void *data)
+const char *
+rb_get_ssl_strerror(rb_fde_t *const F)
{
- rb_ssl_connect_realcb(F, RB_ERR_TIMEOUT, data);
+ return rb_ssl_strerror(F->ssl_errno);
}
-static void
-rb_ssl_tryconn_cb(rb_fde_t *F, void *data)
+int
+rb_get_ssl_certfp(rb_fde_t *const F, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
{
- struct ssl_connect *sconn = data;
- int ssl_err;
- if(!SSL_is_init_finished((SSL *) F->ssl))
+ if(F == NULL || F->ssl == NULL)
+ return 0;
+
+ X509 *const peer_cert = SSL_get_peer_certificate(SSL_P(F));
+ if(peer_cert == NULL)
+ return 0;
+
+ int len = 0;
+
+ switch(SSL_get_verify_result(SSL_P(F)))
{
- if((ssl_err = SSL_connect((SSL *) F->ssl)) <= 0)
- {
- switch (ssl_err = SSL_get_error((SSL *) F->ssl, ssl_err))
- {
- case SSL_ERROR_SYSCALL:
- if(rb_ignore_errno(errno))
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- {
- F->ssl_errno = get_last_err();
- rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE,
- rb_ssl_tryconn_cb, sconn);
- return;
- }
- default:
- F->ssl_errno = get_last_err();
- rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn);
- return;
- }
- }
- else
- {
- rb_ssl_connect_realcb(F, RB_OK, sconn);
- }
+ case X509_V_OK:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ len = make_certfp(peer_cert, certfp, method);
+ // fallthrough
+ default:
+ X509_free(peer_cert);
+ return len;
}
}
-static void
-rb_ssl_tryconn(rb_fde_t *F, int status, void *data)
+int
+rb_get_ssl_certfp_file(const char *const filename, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method)
{
- struct ssl_connect *sconn = data;
- int ssl_err;
- if(status != RB_OK)
- {
- rb_ssl_connect_realcb(F, status, sconn);
- return;
- }
+ FILE *const fp = fopen(filename, "r");
+ if (fp == NULL)
+ return -1;
- F->type |= RB_FD_SSL;
- F->ssl = SSL_new(ssl_client_ctx);
- SSL_set_fd((SSL *) F->ssl, F->fd);
- rb_setup_ssl_cb(F);
- rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
- if((ssl_err = SSL_connect((SSL *) F->ssl)) <= 0)
+ X509 *const cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ if (cert == NULL)
{
- switch (ssl_err = SSL_get_error((SSL *) F->ssl, ssl_err))
- {
- case SSL_ERROR_SYSCALL:
- if(rb_ignore_errno(errno))
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- {
- F->ssl_errno = get_last_err();
- rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE,
- rb_ssl_tryconn_cb, sconn);
- return;
- }
- default:
- F->ssl_errno = get_last_err();
- rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn);
- return;
- }
- }
- else
- {
- rb_ssl_connect_realcb(F, RB_OK, sconn);
+ fclose(fp);
+ return 0;
}
+
+ int len = make_certfp(cert, certfp, method);
+
+ X509_free(cert);
+ fclose(fp);
+
+ return len;
}
void
-rb_connect_tcp_ssl(rb_fde_t *F, struct sockaddr *dest,
- struct sockaddr *clocal, CNCB * callback, void *data, int timeout)
+rb_get_ssl_info(char *const buf, const size_t len)
{
- 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, rb_ssl_tryconn, sconn, timeout);
+#ifdef LRB_SSL_FULL_VERSION_INFO
+ if(LRB_SSL_VNUM_RUNTIME == LRB_SSL_VNUM_COMPILETIME)
+ (void) snprintf(buf, len, "OpenSSL: compiled 0x%lx, library %s",
+ LRB_SSL_VNUM_COMPILETIME, LRB_SSL_VTEXT_COMPILETIME);
+ else
+ (void) snprintf(buf, len, "OpenSSL: compiled (0x%lx, %s), library (0x%lx, %s)",
+ LRB_SSL_VNUM_COMPILETIME, LRB_SSL_VTEXT_COMPILETIME,
+ LRB_SSL_VNUM_RUNTIME, LRB_SSL_VTEXT_RUNTIME);
+#else
+ (void) snprintf(buf, len, "OpenSSL: compiled 0x%lx, library %s",
+ LRB_SSL_VNUM_COMPILETIME, LRB_SSL_VTEXT_RUNTIME);
+#endif
}
-void
-rb_ssl_start_connected(rb_fde_t *F, CNCB * callback, void *data, int timeout)
+const char *
+rb_ssl_get_cipher(rb_fde_t *const F)
{
- struct ssl_connect *sconn;
- int ssl_err;
- if(F == NULL)
- return;
+ if(F == NULL || F->ssl == NULL)
+ return NULL;
- 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 = SSL_new(ssl_client_ctx);
+ static char buf[512];
- SSL_set_fd((SSL *) F->ssl, F->fd);
- rb_setup_ssl_cb(F);
- rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
- if((ssl_err = SSL_connect((SSL *) F->ssl)) <= 0)
- {
- switch (ssl_err = SSL_get_error((SSL *) F->ssl, ssl_err))
- {
- case SSL_ERROR_SYSCALL:
- if(rb_ignore_errno(errno))
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_WRITE:
- {
- F->ssl_errno = get_last_err();
- rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE,
- rb_ssl_tryconn_cb, sconn);
- return;
- }
- default:
- F->ssl_errno = get_last_err();
- rb_ssl_connect_realcb(F, RB_ERROR_SSL, sconn);
- return;
- }
- }
- else
- {
- rb_ssl_connect_realcb(F, RB_OK, sconn);
- }
+ const char *const version = SSL_get_version(SSL_P(F));
+ const char *const cipher = SSL_get_cipher_name(SSL_P(F));
+
+ (void) snprintf(buf, sizeof buf, "%s, %s", version, cipher);
+
+ return buf;
}
-int
-rb_init_prng(const char *path, prng_seed_t seed_type)
+ssize_t
+rb_ssl_read(rb_fde_t *const F, void *const buf, const size_t count)
{
- if(seed_type == RB_PRNG_DEFAULT)
- {
-#ifdef _WIN32
- RAND_screen();
-#endif
- return RAND_status();
- }
- if(path == NULL)
- return RAND_status();
+ return rb_ssl_read_or_write(0, F, buf, NULL, count);
+}
+
+ssize_t
+rb_ssl_write(rb_fde_t *const F, const void *const buf, const size_t count)
+{
+ return rb_ssl_read_or_write(1, F, NULL, buf, count);
+}
- switch (seed_type)
- {
- case RB_PRNG_FILE:
- if(RAND_load_file(path, -1) == -1)
- return -1;
- break;
-#ifdef _WIN32
- case RB_PRNGWIN32:
- RAND_screen();
- break;
-#endif
- default:
- return -1;
- }
- return RAND_status();
+
+/*
+ * Internal library-agnostic code
+ */
+
+static void
+rb_ssl_connect_realcb(rb_fde_t *const F, const int status, struct ssl_connect *const sconn)
+{
+ lrb_assert(F->connect != NULL);
+
+ F->connect->callback = sconn->callback;
+ F->connect->data = sconn->data;
+
+ rb_connect_callback(F, status);
+ rb_free(sconn);
}
-int
-rb_get_random(void *buf, size_t length)
+static void
+rb_ssl_timeout_cb(rb_fde_t *const F, void *const data __attribute__((unused)))
{
- int ret;
+ lrb_assert(F->accept != NULL);
+ lrb_assert(F->accept->callback != NULL);
- if((ret = RAND_bytes(buf, length)) == 0)
- {
- /* remove the error from the queue */
- ERR_get_error();
- }
- return ret;
+ F->accept->callback(F, RB_ERR_TIMEOUT, NULL, 0, F->accept->data);
}
-const char *
-rb_get_ssl_strerror(rb_fde_t *F)
+static void
+rb_ssl_tryconn_timeout_cb(rb_fde_t *const F, void *const data)
{
- return get_ssl_error(F->ssl_errno);
+ rb_ssl_connect_realcb(F, RB_ERR_TIMEOUT, data);
}
-static int
-make_certfp(X509 *cert, uint8_t certfp[RB_SSL_CERTFP_LEN], int method)
+static void
+rb_ssl_tryconn(rb_fde_t *const F, const int status, void *const data)
{
- const ASN1_ITEM *it;
- const EVP_MD *evp;
- void *data;
- unsigned int len;
+ lrb_assert(F != NULL);
- switch(method)
+ struct ssl_connect *const sconn = data;
+
+ if(status != RB_OK)
{
- case RB_SSL_CERTFP_METH_CERT_SHA1:
- it = ASN1_ITEM_rptr(X509);
- evp = EVP_sha1();
- data = cert;
- len = RB_SSL_CERTFP_LEN_SHA1;
- break;
- case RB_SSL_CERTFP_METH_CERT_SHA256:
- it = ASN1_ITEM_rptr(X509);
- evp = EVP_sha256();
- data = cert;
- len = RB_SSL_CERTFP_LEN_SHA256;
- break;
- case RB_SSL_CERTFP_METH_CERT_SHA512:
- it = ASN1_ITEM_rptr(X509);
- evp = EVP_sha512();
- data = cert;
- len = RB_SSL_CERTFP_LEN_SHA512;
- break;
- case RB_SSL_CERTFP_METH_SPKI_SHA256:
- it = ASN1_ITEM_rptr(X509_PUBKEY);
- evp = EVP_sha256();
- data = X509_get_X509_PUBKEY(cert);
- len = RB_SSL_CERTFP_LEN_SHA256;
- break;
- case RB_SSL_CERTFP_METH_SPKI_SHA512:
- it = ASN1_ITEM_rptr(X509_PUBKEY);
- evp = EVP_sha512();
- data = X509_get_X509_PUBKEY(cert);
- len = RB_SSL_CERTFP_LEN_SHA512;
- break;
- default:
- return 0;
+ rb_ssl_connect_realcb(F, status, sconn);
+ return;
}
- if (ASN1_item_digest(it, evp, data, certfp, &len) != 1)
- len = 0;
- return (int) len;
+ F->type |= RB_FD_SSL;
+
+ rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
+ rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_OUT);
+ rb_ssl_connect_common(F, sconn);
}
+
+
+/*
+ * External library-agnostic code
+ */
+
int
-rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method)
+rb_supports_ssl(void)
{
- int len = 0;
- X509 *cert;
- int res;
+ return 1;
+}
- if (F->ssl == NULL)
- return 0;
+unsigned int
+rb_ssl_handshake_count(rb_fde_t *const F)
+{
+ return F->handshake_count;
+}
- cert = SSL_get_peer_certificate((SSL *) F->ssl);
- if(cert == NULL)
- return 0;
+void
+rb_ssl_clear_handshake_count(rb_fde_t *const F)
+{
+ F->handshake_count = 0;
+}
- res = SSL_get_verify_result((SSL *) F->ssl);
- switch(res)
- {
- case X509_V_OK:
- case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
- case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
- case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
- case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
- case X509_V_ERR_CERT_UNTRUSTED:
- len = make_certfp(cert, certfp, method);
+void
+rb_ssl_start_accepted(rb_fde_t *const F, ACCB *const cb, void *const data, const int timeout)
+{
+ F->type |= RB_FD_SSL;
- default: /* to silence code inspectors */
- break;
- }
+ F->accept = rb_malloc(sizeof(struct acceptdata));
+ F->accept->callback = cb;
+ F->accept->data = data;
+ F->accept->addrlen = 0;
+ (void) memset(&F->accept->S, 0x00, sizeof F->accept->S);
- X509_free(cert);
- return len;
+ rb_settimeout(F, timeout, rb_ssl_timeout_cb, NULL);
+ rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_IN);
+ rb_ssl_accept_common(F, NULL);
}
-int
-rb_get_ssl_certfp_file(const char *filename, uint8_t certfp[RB_SSL_CERTFP_LEN], int method)
+void
+rb_ssl_accept_setup(rb_fde_t *const srv_F, rb_fde_t *const cli_F, struct sockaddr *const st, const int addrlen)
{
- X509 *cert;
- FILE *f = fopen(filename, "r");
+ cli_F->type |= RB_FD_SSL;
- if (!f)
- return -1;
+ cli_F->accept = rb_malloc(sizeof(struct acceptdata));
+ cli_F->accept->callback = srv_F->accept->callback;
+ cli_F->accept->data = srv_F->accept->data;
+ cli_F->accept->addrlen = (rb_socklen_t) addrlen;
+ (void) memset(&cli_F->accept->S, 0x00, sizeof cli_F->accept->S);
+ (void) memcpy(&cli_F->accept->S, st, (size_t) addrlen);
- cert = PEM_read_X509(f, NULL, NULL, NULL);
- fclose(f);
-
- if (cert) {
- unsigned int len = make_certfp(cert, certfp, method);
- X509_free(cert);
- return len;
- }
- return 0;
+ rb_settimeout(cli_F, 10, rb_ssl_timeout_cb, NULL);
+ rb_ssl_init_fd(cli_F, RB_FD_TLS_DIRECTION_IN);
+ rb_ssl_accept_common(cli_F, NULL);
}
int
-rb_supports_ssl(void)
+rb_ssl_listen(rb_fde_t *const F, const int backlog, const int defer_accept)
{
- return 1;
+ int result = rb_listen(F, backlog, defer_accept);
+
+ F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL;
+
+ return result;
}
void
-rb_get_ssl_info(char *buf, size_t len)
+rb_connect_tcp_ssl(rb_fde_t *const F, struct sockaddr *const dest, struct sockaddr *const clocal,
+ CNCB *const callback, void *const data, const int timeout)
{
- snprintf(buf, len, "Using SSL: %s compiled: 0x%lx, library 0x%lx",
- SSLeay_version(SSLEAY_VERSION),
- (long)OPENSSL_VERSION_NUMBER, SSLeay());
+ if(F == NULL)
+ return;
+
+ struct ssl_connect *const sconn = rb_malloc(sizeof *sconn);
+ sconn->data = data;
+ sconn->callback = callback;
+ sconn->timeout = timeout;
+
+ rb_connect_tcp(F, dest, clocal, rb_ssl_tryconn, sconn, timeout);
}
-const char *
-rb_ssl_get_cipher(rb_fde_t *F)
+void
+rb_ssl_start_connected(rb_fde_t *const F, CNCB *const callback, void *const data, const int timeout)
{
- const SSL_CIPHER *sslciph;
+ if(F == NULL)
+ return;
- if(F == NULL || F->ssl == NULL)
- return NULL;
+ struct ssl_connect *const sconn = rb_malloc(sizeof *sconn);
+ sconn->data = data;
+ sconn->callback = callback;
+ sconn->timeout = timeout;
- if((sslciph = SSL_get_current_cipher(F->ssl)) == NULL)
- return NULL;
+ F->connect = rb_malloc(sizeof(struct conndata));
+ F->connect->callback = callback;
+ F->connect->data = data;
+ F->type |= RB_FD_SSL;
- return SSL_CIPHER_get_name(sslciph);
+ rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn);
+ rb_ssl_init_fd(F, RB_FD_TLS_DIRECTION_OUT);
+ rb_ssl_connect_common(F, sconn);
}
#endif /* HAVE_OPENSSL */