From 4dd6796cc55ec427a8b94a062609a70a40ec13f9 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Thu, 6 Apr 2017 01:04:43 +0200 Subject: Add fallback SSL implementation using OpenSSL --- configure.ac | 16 ++ m4/pkg.m4 | 275 +++++++++++++++++++++ src/Makefile.am | 3 + src/ssl_openssl.cc | 682 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 976 insertions(+) create mode 100644 m4/pkg.m4 create mode 100644 src/ssl_openssl.cc diff --git a/configure.ac b/configure.ac index e346f6d..c0ca729 100644 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,7 @@ AC_SUBST([THREAD_LIBS]) have_ssl=0 ssl_name="no SSL library found" ssl_mbedtls=0 +ssl_openssl=0 SSL_CFLAGS= SSL_LIBS= @@ -93,10 +94,25 @@ AS_IF([test x$mbedtls_check = xyes], ssl_name=mbedtls SSL_LIBS="-lmbedtls -lmbedx509 -lmbedcrypto"])])])])]) +# Check for openssl +PKG_PROG_PKG_CONFIG +AS_IF([test x$have_ssl != x1], + [AC_ARG_ENABLE([openssl], + [AC_HELP_STRING([--disable-openssl], [do not use openssl even if found])], + [openssl_check=$enableval], [openssl_check=yes]) + AS_IF([test x$openssl_check = xyes], + [openssl_need="openssl >= 1.0.2" + PKG_CHECK_EXISTS([$openssl_need], + [PKG_CHECK_MODULES([SSL], [$openssl_need]) + have_ssl=1 + ssl_openssl=1 + ssl_name=openssl])])]) + AC_SUBST([SSL_CFLAGS]) AC_SUBST([SSL_LIBS]) AM_CONDITIONAL([HAVE_SSL],[test "x$have_ssl" = "x1"]) AM_CONDITIONAL([HAVE_MBEDTLS],[test "x$ssl_mbedtls" = "x1"]) +AM_CONDITIONAL([HAVE_OPENSSL],[test "x$ssl_openssl" = "x1"]) # Finish up diff --git a/m4/pkg.m4 b/m4/pkg.m4 new file mode 100644 index 0000000..82bea96 --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,275 @@ +dnl pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +dnl serial 11 (pkg-config-0.29.1) +dnl +dnl Copyright © 2004 Scott James Remnant . +dnl Copyright © 2012-2015 Dan Nicholson +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +dnl 02111-1307, USA. +dnl +dnl As a special exception to the GNU General Public License, if you +dnl distribute this file as part of a program that contains a +dnl configuration script generated by Autoconf, you may include it under +dnl the same distribution terms that you use for the rest of that +dnl program. + +dnl PKG_PREREQ(MIN-VERSION) +dnl ----------------------- +dnl Since: 0.29 +dnl +dnl Verify that the version of the pkg-config macros are at least +dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's +dnl installed version of pkg-config, this checks the developer's version +dnl of pkg.m4 when generating configure. +dnl +dnl To ensure that this macro is defined, also add: +dnl m4_ifndef([PKG_PREREQ], +dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) +dnl +dnl See the "Since" comment for each macro you use to see what version +dnl of the macros you require. +m4_defun([PKG_PREREQ], +[m4_define([PKG_MACROS_VERSION], [0.29.1]) +m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, + [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) +])dnl PKG_PREREQ + +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) +dnl ---------------------------------- +dnl Since: 0.16 +dnl +dnl Search for the pkg-config tool and set the PKG_CONFIG variable to +dnl first found in the path. Checks that the version of pkg-config found +dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is +dnl used since that's the first version where most current features of +dnl pkg-config existed. +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])dnl PKG_PROG_PKG_CONFIG + +dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------------------------------- +dnl Since: 0.18 +dnl +dnl Check to see whether a particular set of modules exists. Similar to +dnl PKG_CHECK_MODULES(), but does not set variables or print errors. +dnl +dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +dnl only at the first occurence in configure.ac, so if the first place +dnl it's called might be skipped (such as if it is within an "if", you +dnl have to call PKG_CHECK_EXISTS manually +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +dnl --------------------------------------------- +dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting +dnl pkg_failed based on the result. +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])dnl _PKG_CONFIG + +dnl _PKG_SHORT_ERRORS_SUPPORTED +dnl --------------------------- +dnl Internal check to see if pkg-config supports short errors. +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])dnl _PKG_SHORT_ERRORS_SUPPORTED + + +dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl -------------------------------------------------------------- +dnl Since: 0.4.0 +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES might not happen, you should be sure to include an +dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])dnl PKG_CHECK_MODULES + + +dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------------------- +dnl Since: 0.29 +dnl +dnl Checks for existence of MODULES and gathers its build flags with +dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags +dnl and VARIABLE-PREFIX_LIBS from --libs. +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to +dnl include an explicit call to PKG_PROG_PKG_CONFIG in your +dnl configure.ac. +AC_DEFUN([PKG_CHECK_MODULES_STATIC], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +_save_PKG_CONFIG=$PKG_CONFIG +PKG_CONFIG="$PKG_CONFIG --static" +PKG_CHECK_MODULES($@) +PKG_CONFIG=$_save_PKG_CONFIG[]dnl +])dnl PKG_CHECK_MODULES_STATIC + + +dnl PKG_INSTALLDIR([DIRECTORY]) +dnl ------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable pkgconfigdir as the location where a module +dnl should install pkg-config .pc files. By default the directory is +dnl $libdir/pkgconfig, but the default can be changed by passing +dnl DIRECTORY. The user can override through the --with-pkgconfigdir +dnl parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_INSTALLDIR + + +dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) +dnl -------------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable noarch_pkgconfigdir as the location where a +dnl module should install arch-independent pkg-config .pc files. By +dnl default the directory is $datadir/pkgconfig, but the default can be +dnl changed by passing DIRECTORY. The user can override through the +dnl --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_NOARCH_INSTALLDIR + + +dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------- +dnl Since: 0.28 +dnl +dnl Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])dnl PKG_CHECK_VAR diff --git a/src/Makefile.am b/src/Makefile.am index 7b1034c..c050770 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,6 +31,9 @@ libmitm_a_SOURCES = mitm.cc if HAVE_MBEDTLS libmitm_a_SOURCES += ssl_mbedtls.cc endif +if HAVE_OPENSSL +libmitm_a_SOURCES += ssl_openssl.cc +endif libmitm_a_CXXFLAGS = $(AM_CXXFLAGS) @SSL_CFLAGS@ tp_genca_SOURCES = genca.cc logger.cc diff --git a/src/ssl_openssl.cc b/src/ssl_openssl.cc new file mode 100644 index 0000000..0c3eed0 --- /dev/null +++ b/src/ssl_openssl.cc @@ -0,0 +1,682 @@ +// -*- mode: c++; c-basic-offset: 2; -*- + +#include "common.hh" + +#define SSL oSSL + +#include +#include +#include +#include +#include +#include + +#undef SSL + +#include + +#include "buffer.hh" +#include "logger.hh" +#include "ssl.hh" + +namespace { + +void check_init() { +#if OPENSSL_VERSION_NUMBER < 0x10100000 + static bool initialized; + if (initialized) return; + initialized = true; + SSL_load_error_strings(); + SSL_library_init(); +#endif +} + +class SSLEntropyImpl : public SSLEntropy { +public: + SSLEntropyImpl() { + } +}; + +class SSLCertStoreImpl : public SSLCertStore { +public: + SSLCertStoreImpl(std::string const& filename) + : file_(filename) { + } + ~SSLCertStoreImpl() override { + } + + std::string const& file() const { + return file_; + } +private: + std::string file_; +}; + +class SSLKeyImpl : public SSLKey { +public: + SSLKeyImpl(EVP_PKEY* key) + : key_(key) { + } + ~SSLKeyImpl() override { + EVP_PKEY_free(key_); + } + + EVP_PKEY* key() const { + return key_; + } + +private: + EVP_PKEY* const key_; +}; + +class SSLCertImpl : public SSLCert { +public: + SSLCertImpl(X509* x509) + : x509_(x509) { + } + ~SSLCertImpl() override { + X509_free(x509_); + } + + X509* x509() const { + return x509_; + } + +private: + X509* const x509_; +}; + +void logerr(Logger* logger, const char* message) { + bool any = false; + while (true) { + auto err = ERR_get_error(); + if (!err) { + if (!any) logger->out(Logger::ERR, "%s: Unknown error", message); + break; + } + any = true; + char buffer[512]; + ERR_error_string_n(err, buffer, sizeof(buffer)); + logger->out(Logger::ERR, "%s: %s", message, buffer); + } +} + +class SSLImpl : public SSL { +public: + SSLImpl(Logger* logger, uint16_t flags) + : logger_(logger), flags_(flags), + bio_method({ + (99 | BIO_TYPE_SOURCE_SINK), + "SSLImpl", + bio_write, + bio_read, + bio_puts, + bio_gets, + bio_ctrl, + bio_create, + bio_destroy, + nullptr + }), + ctx_(nullptr), ssl_(nullptr), + bio_(nullptr), rbuf_(nullptr), wbuf_(nullptr) { + } + + ~SSLImpl() override { + if (ssl_) SSL_free(ssl_); + if (ctx_) SSL_CTX_free(ctx_); + if (bio_) BIO_free(bio_); + } + + bool unsecure() const { + return flags_ & UNSECURE; + } + + TransferResult transfer(Buffer* ssl_in, Buffer* ssl_out, + Buffer* data_in, Buffer* data_out) override { + bool want_read = false, want_write = false; + rbuf_ = ssl_in; + wbuf_ = ssl_out; + while (!want_read || !want_write) { + switch (state_) { + case HANDSHAKE: { + auto ret = handshake(); + if (ret == 1) { + state_ = TRANSFER; + continue; + } + ret = SSL_get_error(ssl_, ret); + if (ret == SSL_ERROR_WANT_READ || ret == SSL_ERROR_WANT_WRITE) { + return SSL::NO_ERR; + } + logsslerr("Handshake", ret); + return SSL::ERR; + } + case CLOSED: { + auto ret = SSL_shutdown(ssl_); + if (ret == 1) { + return SSL::CLOSED; + } else if (ret == 0) { + continue; + } + ret = SSL_get_error(ssl_, ret); + if (ret == SSL_ERROR_WANT_READ || ret == SSL_ERROR_WANT_WRITE) { + return SSL::NO_ERR; + } + logsslerr("Close", ret); + return SSL::ERR; + } + case ERROR: + return SSL::ERR; + case TRANSFER: { + size_t avail; + auto wptr = data_out->write_ptr(&avail); + if (avail > 0) { + auto ret = SSL_read(ssl_, wptr, avail); + if (ret > 0) { + data_out->commit(ret); + } else { + ret = SSL_get_error(ssl_, ret); + if (ret == SSL_ERROR_WANT_READ || ret == SSL_ERROR_WANT_WRITE) { + want_read = true; + } else if (ret == SSL_ERROR_ZERO_RETURN) { + return SSL::CLOSED; + } else { + logsslerr("SSL_read", ret); + return SSL::ERR; + } + } + } else { + assert(false); + want_read = true; + } + auto rptr = data_in->read_ptr(&avail); + if (avail > 0) { + auto ret = SSL_write(ssl_, rptr, avail); + if (ret > 0) { + data_in->consume(ret); + } else { + ret = SSL_get_error(ssl_, ret); + if (ret == SSL_ERROR_WANT_READ || ret == SSL_ERROR_WANT_WRITE) { + want_write = true; + } else if (ret == SSL_ERROR_ZERO_RETURN) { + return SSL::CLOSED; + } else { + logsslerr("SSL_write", ret); + return SSL::ERR; + } + } + } else { + want_write = true; + } + break; + } + } + } + rbuf_ = nullptr; + wbuf_ = nullptr; + return NO_ERR; + } + + void close() override { + switch (state_) { + case CLOSED: + case ERROR: + return; + default: + state_ = CLOSED; + } + } + +protected: + Logger* const logger_; + uint16_t const flags_; + + void logsslerr(std::string const& message, int err) { + state_ = ERROR; + switch (err) { + case SSL_ERROR_NONE: + logger_->out(Logger::ERR, "%s: No error (?)", message.c_str()); + return; + case SSL_ERROR_ZERO_RETURN: + logger_->out(Logger::ERR, "%s: Connection closed", message.c_str()); + return; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + assert(false); + logger_->out(Logger::ERR, "%s: Non-blocking error", message.c_str()); + return; + case SSL_ERROR_WANT_X509_LOOKUP: + assert(false); + logger_->out(Logger::ERR, "%s: Unknown error", message.c_str()); + return; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + default: + logerr(logger_, message.c_str()); + return; + } + } + + bool setup(SSL_CTX* ctx) { + if (!ctx) return false; + ctx_ = ctx; + SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE); + ssl_ = SSL_new(ctx_); + if (!ssl_) { + logerr(logger_, "Unable to create SSL"); + return false; + } + bio_ = BIO_new(&bio_method); + bio_->ptr = this; + SSL_set_bio(ssl_, bio_, bio_); + return true; + } + + oSSL* ssl() { + return ssl_; + } + + virtual int handshake() = 0; + +private: + enum State { + HANDSHAKE, + CLOSED, + ERROR, + TRANSFER + }; + BIO_METHOD bio_method; + + static int bio_write(BIO* bio, const char* buf, int len) { + BIO_clear_retry_flags(bio); + if (len <= 0) return 0; + auto impl = reinterpret_cast(bio->ptr); + if (impl->wbuf_) { + impl->wbuf_->write(buf, len); + return len; + } + BIO_set_retry_write(bio); + return -1; + } + static int bio_read(BIO* bio, char* buf, int len) { + BIO_clear_retry_flags(bio); + if (len <= 0) return 0; + auto impl = reinterpret_cast(bio->ptr); + if (impl->rbuf_) { + auto ret = impl->rbuf_->read(buf, len); + if (ret > 0) return ret; + } + BIO_set_retry_read(bio); + return -1; + } + static int bio_puts(BIO* bio, const char* str) { + return bio_write(bio, str, strlen(str)); + } + static int bio_gets(BIO* UNUSED(bio), char* UNUSED(str), int UNUSED(size)) { + return -2; + } + static long bio_ctrl(BIO* UNUSED(bio), int cmd, + long UNUSED(num), void* UNUSED(ptr)) { + if (cmd == BIO_CTRL_FLUSH) { + return 1; + } + return 0; + } + static int bio_create(BIO* bio) { + bio->shutdown = 0; + bio->init = 1; + bio->ptr = nullptr; + return 1; + } + static int bio_destroy(BIO* bio) { + if (!bio) return 0; + bio->init = 0; + bio->ptr = nullptr; + return 1; + } + SSL_CTX* ctx_; + oSSL* ssl_; + State state_; + BIO* bio_; + Buffer* rbuf_; + Buffer* wbuf_; +}; + +class SSLServerImpl : public SSLImpl { +public: + SSLServerImpl(Logger* logger, uint16_t flags) + : SSLImpl(logger, flags) { + } + + bool setup(SSLCert* cert, SSLKey* key) { + auto ctx = SSL_CTX_new(SSLv23_server_method()); + if (!ctx) { + logerr(logger_, "Unable to create server context"); + return false; + } + if (!unsecure()) { + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + } + if (cert) { + if (SSL_CTX_use_certificate( + ctx, static_cast(cert)->x509()) != 1) { + logerr(logger_, "Unable to set certificate"); + return false; + } + } + if (key) { + if (SSL_CTX_use_PrivateKey( + ctx, static_cast(key)->key()) != 1) { + logerr(logger_, "Unable to set private key"); + return false; + } + } + return SSLImpl::setup(ctx); + } + + int handshake() { + return SSL_accept(ssl()); + } +}; + +class SSLClientImpl : public SSLImpl { +public: + SSLClientImpl(Logger* logger, uint16_t flags) + : SSLImpl(logger, flags) { + } + + bool setup(SSLCertStore* store, std::string const& host) { + auto ctx = SSL_CTX_new(SSLv23_client_method()); + if (!ctx) { + logerr(logger_, "Unable to create client context"); + return false; + } + if (!unsecure()) { + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + } + if (SSL_CTX_load_verify_locations( + ctx, static_cast(store)->file().c_str(), + nullptr) != 1) { + logerr(logger_, "Unable to load certificate store"); + return false; + } + if (!SSLImpl::setup(ctx)) return false; + + // Setup peer verification + auto param = SSL_get0_param(ssl()); + X509_VERIFY_PARAM_set_hostflags( + param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + X509_VERIFY_PARAM_set1_host(param, host.data(), host.size()); + SSL_set_verify(ssl(), unsecure() ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, + nullptr); + return true; + } + + int handshake() { + return SSL_connect(ssl()); + } + +protected: + std::string host_; +}; + +} // namespace + +// static +SSLEntropy* SSLEntropy::create(Logger* UNUSED(logger)) { + return new SSLEntropyImpl(); +} + +// static +SSLCertStore* SSLCertStore::create(Logger* UNUSED(logger), + std::string const& bundle) { + // TODO(the_jk): Read certificates here and not later when store is used + return new SSLCertStoreImpl(bundle); +} + +// static +bool SSLKey::generate(Logger* logger, SSLEntropy* UNUSED(entropy), + std::string* key) { + check_init(); + + RSA* rsa = RSA_new(); + EVP_PKEY* pk = EVP_PKEY_new(); + BIGNUM* exp = BN_new(); + BIO* bio = BIO_new(BIO_s_mem()); + bool ok = false; + int len; + + if (BN_set_word(exp, 65537) != 1) { + logerr(logger, "Unable to set exponent"); + goto error; + } + if (RSA_generate_key_ex(rsa, 4096, exp, nullptr) != 1) { + logerr(logger, "Unable to generate key"); + goto error; + } + if (!EVP_PKEY_assign_RSA(pk, rsa)) { + logerr(logger, "Unable to copy key"); + goto error; + } + rsa = nullptr; + + if (PEM_write_bio_PKCS8PrivateKey(bio, pk, nullptr, nullptr, 0, + nullptr, nullptr) != 1) { + logerr(logger, "Error writing key"); + goto error; + } + + len = BIO_pending(bio); + key->resize(len); + BIO_read(bio, &(*key)[0], len); + + ok = true; + + error: + BIO_free(bio); + EVP_PKEY_free(pk); + if (rsa) RSA_free(rsa); + BN_free(exp); + return ok; +} + +// static +SSLKey* SSLKey::load(Logger* logger, std::string const& data) { + check_init(); + + EVP_PKEY* key = EVP_PKEY_new(); + BIO* bio = BIO_new_mem_buf(data.data(), data.size()); + SSLKey* ret = nullptr; + + if (!PEM_read_bio_PrivateKey(bio, &key, nullptr, nullptr)) { + logerr(logger, "Error reading key"); + goto error; + } + + ret = new SSLKeyImpl(key); + key = nullptr; + + error: + BIO_free(bio); + if (key) EVP_PKEY_free(key); + return ret; +} + +// static +bool SSLCert::generate(Logger* logger, SSLEntropy* UNUSED(entropy), + SSLCert* issuer_cert, SSLKey* issuer_key, + std::string const& host, SSLKey* key, + std::string* cert) { + check_init(); + + X509* x509 = X509_new(); + BIO* bio = BIO_new(BIO_s_mem()); + BIGNUM* bn = BN_new(); + ASN1_INTEGER* serial = ASN1_INTEGER_new(); + X509_NAME* name; + X509_EXTENSION* ext; + X509V3_CTX ctx; + EVP_PKEY* sign_key = nullptr; + bool ok = false; + int len; + char const* constraints; + std::string tmp; + int ret; + + if (X509_set_version(x509, 2) != 1) { + logerr(logger, "Unable to set cert version"); + goto error; + } + if (BN_pseudo_rand(bn, 32, 0, 0) != 1) { + logerr(logger, "Unable to generate random serial"); + goto error; + } + if (!BN_to_ASN1_INTEGER(bn, serial)) { + logerr(logger, "Unable to convert serial"); + goto error; + } + if (X509_set_serialNumber(x509, serial) != 1) { + logerr(logger, "Unable to set serial"); + goto error; + } + if (!X509_gmtime_adj(X509_get_notBefore(x509), - (24 * 60 * 60 - 1))) { + logerr(logger, "Unable to not before time"); + goto error; + } + if (!X509_gmtime_adj(X509_get_notAfter(x509), 30 * 24 * 60 * 60)) { + logerr(logger, "Unable to not after time"); + goto error; + } + + if (key) { + if (X509_set_pubkey(x509, static_cast(key)->key()) != 1) { + logerr(logger, "Unable to set public key"); + goto error; + } + } + + name = X509_get_subject_name(x509); + if (X509_NAME_add_entry_by_txt( + name, "CN", MBSTRING_ASC, + reinterpret_cast(host.data()), host.size(), + -1, 0) != 1) { + logerr(logger, "Unable to set common name"); + goto error; + } + X509V3_set_ctx_nodb(&ctx); + if (issuer_cert) { + auto issuer = static_cast(issuer_cert)->x509(); + X509_set_issuer_name(x509, X509_get_subject_name(issuer)); + X509V3_set_ctx(&ctx, issuer, x509, nullptr, nullptr, 0); + } else { + X509_set_issuer_name(x509, name); + X509V3_set_ctx(&ctx, x509, x509, nullptr, nullptr, 0); + } + + if (issuer_cert) { + constraints = "CA:FALSE"; + } else { + constraints = "CA:TRUE,pathlen:1"; + } + ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_basic_constraints, + const_cast(constraints)); + if (!ext) { + logerr(logger, "Unable to create basic constraints extension"); + goto error; + } + ret = X509_add_ext(x509, ext, -1); + X509_EXTENSION_free(ext); + if (ret != 1) { + logerr(logger, "Unable to add basic constraints extension"); + goto error; + } + + if (issuer_cert) { + tmp = "DNS:" + host; + ext = X509V3_EXT_conf_nid(nullptr, &ctx, NID_subject_alt_name, + const_cast(tmp.c_str())); + if (!ext) { + logerr(logger, "Unable to create subject alt name extension"); + goto error; + } + ret = X509_add_ext(x509, ext, -1); + X509_EXTENSION_free(ext); + if (ret != 1) { + logerr(logger, "Unable to add subject alt name extension"); + goto error; + } + } + + if (issuer_key) { + sign_key = static_cast(issuer_key)->key(); + } else if (key) { + sign_key = static_cast(key)->key(); + } + + if (!X509_sign(x509, sign_key, EVP_sha256())) { + logerr(logger, "Error signing cert"); + goto error; + } + + if (PEM_write_bio_X509(bio, x509) != 1) { + logerr(logger, "Error writing cert"); + goto error; + } + + len = BIO_pending(bio); + cert->resize(len); + BIO_read(bio, &(*cert)[0], len); + + ok = true; + + error: + BN_free(bn); + ASN1_INTEGER_free(serial); + BIO_free(bio); + X509_free(x509); + return ok; +} + +// static +SSLCert* SSLCert::load(Logger* logger, std::string const& data) { + check_init(); + + X509* x509 = X509_new(); + BIO* bio = BIO_new_mem_buf(data.data(), data.size()); + SSLCert* ret = nullptr; + + if (!PEM_read_bio_X509(bio, &x509, nullptr, nullptr)) { + logerr(logger, "Error reading cert"); + goto error; + } + + ret = new SSLCertImpl(x509); + x509 = nullptr; + + error: + BIO_free(bio); + if (x509) X509_free(x509); + return ret; +} + +// static +const uint16_t SSL::UNSECURE = 0x01; + +// static +SSL* SSL::server(Logger* logger, SSLEntropy* entropy, + SSLCert* cert, SSLKey* key, + uint16_t flags) { + std::unique_ptr ret(new SSLServerImpl(logger, flags)); + if (!ret->setup(cert, key)) return nullptr; + return ret.release(); +} + +// static +SSL* SSL::client(Logger* logger, SSLEntropy* entropy, SSLCertStore* store, + std::string const& host, uint16_t flags) { + std::unique_ptr ret(new SSLClientImpl(logger, flags)); + if (!ret->setup(store, host)) return nullptr; + return ret.release(); +} -- cgit v1.2.3-70-g09d2