X-Git-Url: https://jfr.im/git/solanum.git/blobdiff_plain/0fe9dd4119b3d58ee75d76a417fe48883bda7b99..35cd299395a742622722f9e38fcd4e743bcbedae:/librb/src/openssl.c diff --git a/librb/src/openssl.c b/librb/src/openssl.c index 6418b608..5fc960c5 100644 --- a/librb/src/openssl.c +++ b/librb/src/openssl.c @@ -1,9 +1,10 @@ /* * 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 + * Copyright (C) 2015-2016 Aaron Jones * * 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 @@ -29,239 +30,188 @@ #include #include -#include -#include -#include -#include -#include -#include -/* - * 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 = NULL; -static SSL_CTX *ssl_client_ctx = NULL; -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(); + + F->ssl = SSL_new(ssl_ctx); - for(i = 0; i < 4; i++) + 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) { + 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, X509_STORE_CTX *const x509_ctx) { - 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; @@ -272,7 +222,7 @@ rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size 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; @@ -280,9 +230,10 @@ rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size } break; default: - err = get_last_err(); + err = rb_ssl_last_err(); break; } + F->ssl_errno = err; if(err > 0) { @@ -294,539 +245,530 @@ rb_ssl_read_or_write(int r_or_w, rb_fde_t *F, void *rbuf, const void *wbuf, size return ret; } -ssize_t -rb_ssl_read(rb_fde_t *F, void *buf, size_t count) +static int +make_certfp(X509 *const cert, uint8_t certfp[const RB_SSL_CERTFP_LEN], const int method) { - return rb_ssl_read_or_write(0, F, buf, NULL, count); -} + unsigned int hashlen = 0; + const EVP_MD *md_type = NULL; + const ASN1_ITEM *item = NULL; + void *data = NULL; -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); -} + 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; + } -static int -verify_accept_all_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) -{ - return 1; + if(ASN1_item_digest(item, md_type, data, certfp, &hashlen) != 1) + { + rb_lib_log("%s: ASN1_item_digest: %s", __func__, rb_ssl_strerror(rb_ssl_last_err())); + return 0; + } + + return (int) hashlen; } -static const char * -get_ssl_error(unsigned long err) + + +/* + * External OpenSSL-specific code + */ + +void +rb_ssl_shutdown(rb_fde_t *const F) { - static char buf[512]; + if(F == NULL || F->ssl == NULL) + return; - ERR_error_string_n(err, buf, sizeof buf); - return buf; + (void) rb_ssl_last_err(); + + for(int i = 0; i < 4; i++) + { + int ret = SSL_shutdown(SSL_P(F)); + int err = SSL_get_error(SSL_P(F), ret); + + if(ret >= 0 || (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)) + break; + } + + SSL_free(SSL_P(F)); + F->ssl = NULL; } int rb_init_ssl(void) { - char librb_data[] = "librb data"; - -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - /* - * OpenSSL 1.1.0 and above automatically initialises itself with sane defaults - */ - SSL_library_init(); +#ifndef LRB_SSL_NO_EXPLICIT_INIT + (void) SSL_library_init(); SSL_load_error_strings(); #endif - librb_index = SSL_get_ex_new_index(0, librb_data, NULL, NULL, NULL); - + rb_lib_log("%s: OpenSSL backend initialised", __func__); return 1; } int -rb_setup_ssl_server(const char *certfile, const char *keyfile, const char *dhfile, const char *cipher_list) +rb_setup_ssl_server(const char *const certfile, const char *keyfile, + const char *const dhfile, const char *cipherlist) { - const char librb_ciphers[] = "kEECDH+HIGH:kEDH+HIGH:HIGH:!aNULL"; - - #ifdef LRB_HAVE_TLS_SET_CURVES - const char librb_curves[] = "P-521:P-384:P-256"; - #endif - if(certfile == NULL) { - rb_lib_log("rb_setup_ssl_server: No certificate file"); + rb_lib_log("%s: no certificate file specified", __func__); return 0; } if(keyfile == NULL) keyfile = certfile; - if(cipher_list == NULL) - cipher_list = librb_ciphers; + if(cipherlist == NULL) + cipherlist = rb_default_ciphers; - if (ssl_server_ctx) - SSL_CTX_free(ssl_server_ctx); - if (ssl_client_ctx) - SSL_CTX_free(ssl_client_ctx); + (void) rb_ssl_last_err(); #ifdef LRB_HAVE_TLS_METHOD_API - ssl_server_ctx = SSL_CTX_new(TLS_server_method()); - ssl_client_ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX *const ssl_ctx_new = SSL_CTX_new(TLS_method()); #else - ssl_server_ctx = SSL_CTX_new(SSLv23_server_method()); - ssl_client_ctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX *const ssl_ctx_new = SSL_CTX_new(SSLv23_method()); #endif - if(ssl_server_ctx == NULL) + if(ssl_ctx_new == NULL) { - rb_lib_log("rb_init_openssl: Unable to initialize OpenSSL server context: %s", - get_ssl_error(ERR_get_error())); + rb_lib_log("%s: SSL_CTX_new: %s", __func__, rb_ssl_strerror(rb_ssl_last_err())); return 0; } - if(ssl_client_ctx == NULL) + if(SSL_CTX_use_certificate_chain_file(ssl_ctx_new, certfile) != 1) { - rb_lib_log("rb_init_openssl: Unable to initialize OpenSSL client context: %s", - get_ssl_error(ERR_get_error())); + 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; } - #ifndef LRB_HAVE_TLS_METHOD_API - SSL_CTX_set_options(ssl_server_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - SSL_CTX_set_options(ssl_client_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + if(SSL_CTX_use_PrivateKey_file(ssl_ctx_new, keyfile, SSL_FILETYPE_PEM) != 1) + { + 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(dhfile == NULL) + { + rb_lib_log("%s: no DH parameters file specified", __func__); + } + else + { + FILE *const dhf = fopen(dhfile, "r"); + DH *dhp = NULL; + + if(dhf == NULL) + { + 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 + { + SSL_CTX_set_tmp_dh(ssl_ctx_new, dhp); + DH_free(dhp); + fclose(dhf); + } + } + + if(SSL_CTX_set_cipher_list(ssl_ctx_new, cipherlist) != 1) + { + 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 - #ifdef SSL_OP_SINGLE_DH_USE - SSL_CTX_set_options(ssl_server_ctx, SSL_OP_SINGLE_DH_USE); + #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_SINGLE_ECDH_USE - SSL_CTX_set_options(ssl_server_ctx, SSL_OP_SINGLE_ECDH_USE); + #ifdef SSL_OP_NO_TLSv1 + (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_NO_TLSv1); #endif #ifdef SSL_OP_NO_TICKET - SSL_CTX_set_options(ssl_server_ctx, SSL_OP_NO_TICKET); - SSL_CTX_set_options(ssl_client_ctx, SSL_OP_NO_TICKET); + (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_NO_TICKET); #endif #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE - SSL_CTX_set_options(ssl_server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_CIPHER_SERVER_PREFERENCE); #endif - 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); + #ifdef SSL_OP_SINGLE_DH_USE + (void) SSL_CTX_set_options(ssl_ctx_new, SSL_OP_SINGLE_DH_USE); + #endif - #ifdef LRB_HAVE_TLS_SET_CURVES - SSL_CTX_set1_curves_list(ssl_server_ctx, librb_curves); + #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 - SSL_CTX_set_ecdh_auto(ssl_server_ctx, 1); + (void) SSL_CTX_set_ecdh_auto(ssl_ctx_new, 1); #endif - /* - * Set manual ECDHE curve on OpenSSL 1.0.0 & 1.0.1, but make sure it's actually available - */ - #if (OPENSSL_VERSION_NUMBER >= 0x10000000L) && (OPENSSL_VERSION_NUMBER < 0x10002000L) && !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); + #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 - SSL_CTX_set_cipher_list(ssl_server_ctx, cipher_list); - SSL_CTX_set_cipher_list(ssl_client_ctx, cipher_list); - if(!SSL_CTX_use_certificate_chain_file(ssl_server_ctx, certfile) || !SSL_CTX_use_certificate_chain_file(ssl_client_ctx, certfile)) - { - rb_lib_log("rb_setup_ssl_server: Error loading certificate file [%s]: %s", certfile, - get_ssl_error(ERR_get_error())); - return 0; - } + if(ssl_ctx) + SSL_CTX_free(ssl_ctx); - 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)) - { - rb_lib_log("rb_setup_ssl_server: Error loading keyfile [%s]: %s", keyfile, - get_ssl_error(ERR_get_error())); - return 0; - } + ssl_ctx = ssl_ctx_new; - if(dhfile != NULL) - { - /* 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) - { - DH *dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - if(dh == NULL) - { - rb_lib_log - ("rb_setup_ssl_server: Error loading DH params file [%s]: %s", - dhfile, get_ssl_error(ERR_get_error())); - BIO_free(bio); - return 0; - } - BIO_free(bio); - SSL_CTX_set_tmp_dh(ssl_server_ctx, dh); - DH_free(dh); - } - else - { - rb_lib_log("rb_setup_ssl_server: Error loading DH params file [%s]: %s", - dhfile, get_ssl_error(ERR_get_error())); - } - } + 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); + 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); +} - 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; - } +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); +} + + + +/* + * Internal library-agnostic code + */ - return RAND_status(); +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) { - 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"); - - if (!f) - return -1; + cli_F->type |= RB_FD_SSL; - cert = PEM_read_X509(f, NULL, NULL, NULL); - fclose(f); + 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); - 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 */