diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 703fb962..129eb8e9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -53,6 +53,11 @@ jobs: path: dist key: ${{ matrix.os }}-cmake-dist-${{ matrix.EVENT_MATRIX }}-v2 + - name: Install Depends + run: | + sudo apt-get update + sudo apt-get install -y libmbedtls-dev + - name: Build And Test shell: bash run: | @@ -153,6 +158,11 @@ jobs: path: dist key: ${{ matrix.os }}-autotools-dist-${{ matrix.EVENT_MATRIX }}-v2 + - name: Install Depends + run: | + sudo apt-get update + sudo apt-get install -y libmbedtls-dev + - name: Build And Test shell: bash run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 910a627a..84b4c9ec 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -45,6 +45,9 @@ jobs: path: build key: macos-10.15-cmake-${{ matrix.EVENT_MATRIX }}-v2 + - name: Install Depends + run: brew install mbedtls + - name: Build And Test shell: bash run: | @@ -124,7 +127,7 @@ jobs: key: ${{ matrix.os }}-autotools-${{ matrix.EVENT_MATRIX }}-v2 - name: Install Depends - run: brew install autoconf automake libtool pkg-config + run: brew install autoconf automake libtool pkg-config mbedtls - name: Build And Test shell: bash diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index a297e8ca..d0f2af85 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -46,7 +46,7 @@ jobs: uses: actions/cache@v1.1.2 with: path: build - key: mingw-autotools-${{ matrix.EVENT_MATRIX }}-v2 + key: mingw-autotools-${{ matrix.EVENT_MATRIX }}-v3 - uses: numworks/setup-msys2@v1 if: steps.cache-mingw.outputs.cache-hit != 'true' @@ -56,7 +56,7 @@ jobs: - name: Install Dependes if: steps.cache-mingw.outputs.cache-hit != 'true' run: | - msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc autoconf automake libtool mingw-w64-x86_64-openssl + msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc autoconf automake libtool mingw-w64-x86_64-openssl mingw-w64-x86_64-mbedtls - name: Build And Test shell: powershell @@ -115,7 +115,7 @@ jobs: uses: actions/cache@v1.1.2 with: path: build - key: mingw-cmake-${{ matrix.EVENT_MATRIX }}-v2 + key: mingw-cmake-${{ matrix.EVENT_MATRIX }}-v3 - uses: numworks/setup-msys2@v1 if: steps.cache-mingw-cmake.outputs.cache-hit != 'true' @@ -125,7 +125,7 @@ jobs: - name: Install Dependes if: steps.cache-mingw-cmake.outputs.cache-hit != 'true' run: | - msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-openssl + msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-openssl mingw-w64-x86_64-mbedtls - name: Build And Test shell: powershell diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b30038a0..82538156 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -36,7 +36,7 @@ jobs: uses: actions/cache@v1.0.3 with: path: C:\vcpkg\installed - key: ${{ matrix.os }}-vcpkg + key: ${{ matrix.os }}-vcpkg-v2 - name: Cache Build uses: actions/cache@v1.0.3 @@ -50,19 +50,18 @@ jobs: run: | vcpkg install openssl:x64-windows vcpkg install zlib:x64-windows + vcpkg install mbedtls:x64-windows - name: Build And Test shell: powershell run: | - $OPENSSL_ROOT_DIR="C:\vcpkg\installed\x64-windows" $EVENT_BUILD_PARALLEL=10 $EVENT_TESTS_PARALLEL=1 - $env:PATH="$OPENSSL_ROOT_DIR/bin;$env:PATH" mkdir build -ea 0 cd build - $CMAKE_CMD="cmake -G 'Visual Studio 15 2017 Win64' .." + $CMAKE_CMD="cmake -G 'Visual Studio 15 2017 Win64' -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake .." function cmake_configure($retry) { $errcode=0 @@ -142,7 +141,7 @@ jobs: uses: actions/cache@v1.1.0 with: path: C:\vcpkg\installed - key: ${{ matrix.os }}-vcpkg + key: ${{ matrix.os }}-vcpkg-v2 - name: Cache Build uses: actions/cache@v1.1.0 @@ -156,17 +155,16 @@ jobs: run: | vcpkg install openssl:x64-windows vcpkg install zlib:x64-windows + vcpkg install mbedtls:x64-windows - name: Build And Test shell: powershell run: | - $OPENSSL_ROOT_DIR="C:\vcpkg\installed\x64-windows" $EVENT_BUILD_PARALLEL=10 $EVENT_TESTS_PARALLEL=1 - $env:PATH="$OPENSSL_ROOT_DIR/bin;$env:PATH" if ( "${{ matrix.EVENT_MATRIX }}" -eq "LIBRARY_TYPE_STATIC" ) { - $EVENT_CMAKE_OPTIONS="-DEVENT__LIBRARY_TYPE=STATIC" + $EVENT_CMAKE_OPTIONS="-DEVENT__LIBRARY_TYPE=STATIC -DEVENT__MSVC_STATIC_RUNTIME=OFF" } elseif ( "${{ matrix.EVENT_MATRIX }}" -eq "DISABLE_OPENSSL" ) { $EVENT_CMAKE_OPTIONS="-DEVENT__DISABLE_OPENSSL=ON" @@ -187,7 +185,7 @@ jobs: $EVENT_CMAKE_OPTIONS="-DEVENT__DISABLE_TESTS=ON -DEVENT__DISABLE_SAMPLES=ON" } elseif ( "${{ matrix.EVENT_MATRIX }}" -eq "TEST_EXPORT_STATIC" ) { - $EVENT_CMAKE_OPTIONS="-DEVENT__LIBRARY_TYPE=STATIC -DEVENT__DISABLE_TESTS=ON -DEVENT__DISABLE_SAMPLES=ON" + $EVENT_CMAKE_OPTIONS="-DEVENT__LIBRARY_TYPE=STATIC -DEVENT__MSVC_STATIC_RUNTIME=OFF -DEVENT__DISABLE_TESTS=ON -DEVENT__DISABLE_SAMPLES=ON" } else { $EVENT_CMAKE_OPTIONS="" @@ -197,10 +195,10 @@ jobs: cd build if ("${{ matrix.os }}" -eq "windows-2016") { - $CMAKE_CMD="cmake -G 'Visual Studio 15 2017 Win64' .." + $CMAKE_CMD="cmake -G 'Visual Studio 15 2017 Win64' -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake .." } else { # windows-2019 - $CMAKE_CMD="cmake -G 'Visual Studio 16 2019' -A x64 .. $EVENT_CMAKE_OPTIONS" + $CMAKE_CMD="cmake -G 'Visual Studio 16 2019' -A x64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake .. $EVENT_CMAKE_OPTIONS" } function cmake_configure($retry) { diff --git a/CMakeLists.txt b/CMakeLists.txt index 960e2e1e..89f0ca32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,9 @@ option(EVENT__DISABLE_THREAD_SUPPORT option(EVENT__DISABLE_OPENSSL "Define if libevent should build without support for OpenSSL encryption" OFF) +option(EVENT__DISABLE_MBEDTLS + "Define if libevent should build without support for mbed TLS encryption" OFF) + option(EVENT__DISABLE_BENCHMARK "Defines if libevent should build without the benchmark executables" OFF) @@ -861,11 +864,26 @@ if (NOT EVENT__DISABLE_OPENSSL) include_directories(${OPENSSL_INCLUDE_DIR}) - list(APPEND SRC_OPENSSL bufferevent_openssl.c) + list(APPEND SRC_OPENSSL bufferevent_openssl.c bufferevent_ssl.c) list(APPEND HDR_PUBLIC include/event2/bufferevent_ssl.h) list(APPEND LIB_APPS ${OPENSSL_LIBRARIES}) endif() +if (NOT EVENT__DISABLE_MBEDTLS) + find_package(MbedTLS REQUIRED) + + set(EVENT__HAVE_MBEDTLS 1) + + message(STATUS "mbed TLS include: ${MBEDTLS_INCLUDE_DIR}") + message(STATUS "mbed TLS lib: ${MBEDTLS_LIBRARIES}") + + include_directories(${MBEDTLS_INCLUDE_DIR}) + + list(APPEND SRC_MBEDTLS bufferevent_mbedtls.c bufferevent_ssl.c) + list(APPEND HDR_PUBLIC include/event2/bufferevent_ssl.h) + list(APPEND LIB_APPS ${MBEDTLS_LIBRARIES}) +endif() + if (NOT EVENT__DISABLE_THREAD_SUPPORT) if (WIN32) list(APPEND SRC_CORE evthread_win32.c) @@ -971,6 +989,14 @@ if (NOT EVENT__DISABLE_OPENSSL) SOURCES ${SRC_OPENSSL}) endif() +if (NOT EVENT__DISABLE_MBEDTLS) + add_event_library(event_mbedtls + INNER_LIBRARIES event_core + OUTER_INCLUDES ${MBEDTLS_INCLUDE_DIR} + LIBRARIES ${MBEDTLS_LIBRARIES} + SOURCES ${SRC_MBEDTLS}) +endif() + if (EVENT__HAVE_PTHREADS) set(SRC_PTHREADS evthread_pthread.c) add_event_library(event_pthreads @@ -1014,8 +1040,8 @@ macro(add_sample_prog ssl name) ${LIB_APPS} ${LIB_PLATFORM}) - if (${ssl}) - target_link_libraries(${name} event_openssl) + if (TARGET ${ssl}) + target_link_libraries(${name} ${ssl}) if(WIN32) target_link_libraries(${name} crypt32) endif() @@ -1038,13 +1064,18 @@ if (NOT EVENT__DISABLE_SAMPLES) endif() if (NOT EVENT__DISABLE_OPENSSL) - add_sample_prog(ON https-client + add_sample_prog(event_openssl https-client sample/https-client.c sample/openssl_hostname_validation.c sample/hostcheck.c) - add_sample_prog(ON le-proxy + add_sample_prog(event_openssl le-proxy sample/le-proxy.c) - add_sample_prog(ON becat sample/becat.c ${WIN32_GETOPT}) + add_sample_prog(event_openssl becat sample/becat.c ${WIN32_GETOPT}) + endif() + + if (NOT EVENT__DISABLE_MBEDTLS) + add_sample_prog(event_mbedtls ssl-client-mbedtls + sample/ssl-client-mbedtls.c) endif() set(SAMPLES_WOPT @@ -1156,7 +1187,11 @@ if (NOT EVENT__DISABLE_TESTS) endif() if (NOT EVENT__DISABLE_OPENSSL) - list(APPEND SRC_REGRESS test/regress_ssl.c) + list(APPEND SRC_REGRESS test/regress_openssl.c) + endif() + + if (NOT EVENT__DISABLE_MBEDTLS) + list(APPEND SRC_REGRESS test/regress_mbedtls.c) endif() add_executable(regress ${SRC_REGRESS}) @@ -1169,6 +1204,9 @@ if (NOT EVENT__DISABLE_TESTS) if (NOT EVENT__DISABLE_OPENSSL) target_link_libraries(regress event_openssl) endif() + if (NOT EVENT__DISABLE_MBEDTLS) + target_link_libraries(regress event_mbedtls) + endif() if (CMAKE_USE_PTHREADS_INIT) target_link_libraries(regress event_pthreads) endif() diff --git a/LICENSE b/LICENSE index f7afe7cc..e86a5551 100644 --- a/LICENSE +++ b/LICENSE @@ -128,3 +128,24 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============================== + +The ssl-client-mbedtls.c is available under the following license: + +Copyright (C) 2006-2015, ARM Limited, All Rights Reserved +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +This file is part of mbed TLS (https://tls.mbed.org) diff --git a/Makefile.am b/Makefile.am index b66b42c2..73e76b0b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,7 +101,9 @@ LIBEVENT_PKGCONFIG=libevent.pc libevent_core.pc libevent_extra.pc # included from other files. PLATFORM_DEPENDENT_SRC = \ arc4random.c \ - epoll_sub.c + epoll_sub.c \ + bufferevent_ssl.c \ + test/regress_ssl.c CMAKE_FILES = \ cmake/AddCompilerFlags.cmake \ @@ -116,6 +118,7 @@ CMAKE_FILES = \ cmake/CodeCoverage.cmake \ cmake/COPYING-CMAKE-SCRIPTS \ cmake/Copyright.txt \ + cmake/FindMbedTLS.cmake \ cmake/LibeventConfig.cmake.in \ cmake/LibeventConfigVersion.cmake.in \ cmake/Macros.cmake \ @@ -154,6 +157,10 @@ if OPENSSL LIBEVENT_LIBS_LA += libevent_openssl.la LIBEVENT_PKGCONFIG += libevent_openssl.pc endif +if MBEDTLS +LIBEVENT_LIBS_LA += libevent_mbedtls.la +LIBEVENT_PKGCONFIG += libevent_mbedtls.pc +endif if INSTALL_LIBEVENT lib_LTLIBRARIES = $(LIBEVENT_LIBS_LA) @@ -287,12 +294,19 @@ libevent_extra_la_LIBADD = $(MAYBE_CORE) $(SYS_LIBS) libevent_extra_la_LDFLAGS = $(GENERIC_LDFLAGS) if OPENSSL -libevent_openssl_la_SOURCES = bufferevent_openssl.c +libevent_openssl_la_SOURCES = bufferevent_openssl.c bufferevent_ssl.c libevent_openssl_la_LIBADD = $(MAYBE_CORE) $(OPENSSL_LIBS) libevent_openssl_la_LDFLAGS = $(GENERIC_LDFLAGS) libevent_openssl_la_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_INCS) endif +if MBEDTLS +libevent_mbedtls_la_SOURCES = bufferevent_mbedtls.c bufferevent_ssl.c +libevent_mbedtls_la_LIBADD = $(MAYBE_CORE) $(MBEDTLS_LIBS) +libevent_mbedtls_la_LDFLAGS = $(GENERIC_LDFLAGS) +libevent_mbedtls_la_CPPFLAGS = $(AM_CPPFLAGS) $(MBEDTLS_INCS) +endif + noinst_HEADERS += \ WIN32-Code/getopt.h \ WIN32-Code/getopt.c \ @@ -323,6 +337,7 @@ noinst_HEADERS += \ time-internal.h \ util-internal.h \ openssl-compat.h \ + ssl-compat.h \ wepoll.h EVENT1_HDRS = \ diff --git a/bufferevent-internal.h b/bufferevent-internal.h index 87ab9ad9..0404d4ff 100644 --- a/bufferevent-internal.h +++ b/bufferevent-internal.h @@ -306,11 +306,18 @@ extern const struct bufferevent_ops bufferevent_ops_pair; #define BEV_IS_FILTER(bevp) ((bevp)->be_ops == &bufferevent_ops_filter) #define BEV_IS_PAIR(bevp) ((bevp)->be_ops == &bufferevent_ops_pair) -#if defined(EVENT__HAVE_OPENSSL) -extern const struct bufferevent_ops bufferevent_ops_openssl; -#define BEV_IS_OPENSSL(bevp) ((bevp)->be_ops == &bufferevent_ops_openssl) +#if defined(EVENT__HAVE_OPENSSL) | defined(EVENT__HAVE_MBEDTLS) +extern const struct bufferevent_ops bufferevent_ops_ssl; +#define BEV_IS_SSL(bevp) ((bevp)->be_ops == &bufferevent_ops_ssl) #else -#define BEV_IS_OPENSSL(bevp) 0 +#define BEV_IS_SSL(bevp) 0 +#endif + +#if defined(EVENT__HAVE_MBEDTLS) +extern const struct bufferevent_ops bufferevent_ops_mbedtls; +#define BEV_IS_MBEDTLS(bevp) ((bevp)->be_ops == &bufferevent_ops_mbedtls) +#else +#define BEV_IS_MBEDTLS(bevp) 0 #endif #ifdef _WIN32 diff --git a/bufferevent_mbedtls.c b/bufferevent_mbedtls.c new file mode 100644 index 00000000..c60b109c --- /dev/null +++ b/bufferevent_mbedtls.c @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "event2/util.h" +#include "util-internal.h" +#include "event2/buffer.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_struct.h" +#include "event2/bufferevent_ssl.h" + +#include "ssl-compat.h" +#include "mm-internal.h" + +struct mbedtls_context { + mbedtls_ssl_context *ssl; + mbedtls_net_context net; +}; +static void * +mbedtls_context_init(void *ssl) +{ + struct mbedtls_context *ctx = mm_malloc(sizeof(*ctx)); + if (ctx) { + ctx->ssl = ssl; + ctx->net.fd = -1; + } + return ctx; +} +static void +mbedtls_context_free(void *ssl, int flags) +{ + struct mbedtls_context *ctx = ssl; + if (flags & BEV_OPT_CLOSE_ON_FREE) + mbedtls_ssl_free(ctx->ssl); + mm_free(ctx); +} +static int +mbedtls_context_renegotiate(void *ssl) +{ + struct mbedtls_context *ctx = ssl; + return mbedtls_ssl_renegotiate(ctx->ssl); +} +static int +mbedtls_context_write(void *ssl, const unsigned char *buf, size_t len) +{ + struct mbedtls_context *ctx = ssl; + return mbedtls_ssl_write(ctx->ssl, buf, len); +} +static int +mbedtls_context_read(void *ssl, unsigned char *buf, size_t len) +{ + struct mbedtls_context *ctx = ssl; + return mbedtls_ssl_read(ctx->ssl, buf, len); +} +static size_t +mbedtls_context_pending(void *ssl) +{ + struct mbedtls_context *ctx = ssl; + return mbedtls_ssl_get_bytes_avail(ctx->ssl); +} +static int +mbedtls_context_handshake(void *ssl) +{ + struct mbedtls_context *ctx = ssl; + return mbedtls_ssl_handshake(ctx->ssl); +} +static int +mbedtls_get_error(void *ssl, int ret) +{ + return ret; +} +static void +mbedtls_clear_error(void) +{ +} +static int +mbedtls_clear(void *ssl) +{ + return 1; +} +static void +mbedtls_set_ssl_noops(void *ssl) +{ +} +static int +mbedtls_is_ok(int err) +{ + return err == 0; +} +static int +mbedtls_is_want_read(int err) +{ + return err == MBEDTLS_ERR_SSL_WANT_READ; +} +static int +mbedtls_is_want_write(int err) +{ + return err == MBEDTLS_ERR_SSL_WANT_WRITE; +} + +static evutil_socket_t +be_mbedtls_get_fd(void *ssl) +{ + struct bufferevent_ssl *bev = ssl; + struct mbedtls_context *ctx = bev->ssl; + return ctx->net.fd; +} + +static int be_mbedtls_bio_set_fd( + struct bufferevent_ssl *bev_ssl, evutil_socket_t fd); + +#if 0 +static void +print_err(int val) +{ + char buf[1024]; + mbedtls_strerror(val, buf, sizeof(buf)); + printf("Error was %d:%s\n", val, buf); +} +#else +static void +print_err(int val) +{ +} +#endif + +/* Called to extract data from the BIO. */ +static int +bio_bufferevent_read(void *ctx, unsigned char *out, size_t outlen) +{ + struct bufferevent *bufev = (struct bufferevent *)ctx; + int r = 0; + struct evbuffer *input; + + if (!out) + return 0; + if (!bufev) + return MBEDTLS_ERR_NET_INVALID_CONTEXT; + + input = bufferevent_get_input(bufev); + if (evbuffer_get_length(input) == 0) { + /* If there's no data to read, say so. */ + return MBEDTLS_ERR_SSL_WANT_READ; + } else { + r = evbuffer_remove(input, out, outlen); + } + + return r; +} + +/* Called to write data into the BIO */ +static int +bio_bufferevent_write(void *ctx, const unsigned char *in, size_t inlen) +{ + struct bufferevent *bufev = (struct bufferevent *)ctx; + struct evbuffer *output; + size_t outlen; + + if (!bufev) + return MBEDTLS_ERR_NET_INVALID_CONTEXT; + + output = bufferevent_get_output(bufev); + outlen = evbuffer_get_length(output); + + /* Copy only as much data onto the output buffer as can fit under the + * high-water mark. */ + if (bufev->wm_write.high && bufev->wm_write.high <= (outlen + inlen)) { + if (bufev->wm_write.high <= outlen) { + /* If no data can fit, we'll need to retry later. */ + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + inlen = bufev->wm_write.high - outlen; + } + + EVUTIL_ASSERT(inlen > 0); + evbuffer_add(output, in, inlen); + return inlen; +} + +static void +conn_closed(struct bufferevent_ssl *bev_ssl, int when, int errcode, int ret) +{ + int event = BEV_EVENT_ERROR; + char buf[100]; + + if (when & BEV_EVENT_READING && ret == 0) { + if (bev_ssl->allow_dirty_shutdown) + event = BEV_EVENT_EOF; + } else { + mbedtls_strerror(errcode, buf, sizeof(buf)); + switch (errcode) { + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: + event = BEV_EVENT_EOF; + break; + case MBEDTLS_ERR_SSL_CLIENT_RECONNECT: + event_warnx("BUG: Unsupported feature %d: %s", errcode, buf); + break; + default: + /* should be impossible; treat as normal error. */ + event_warnx( + "BUG: Unexpected mbedtls error code %d: %s", errcode, buf); + break; + } + + bufferevent_ssl_put_error(bev_ssl, errcode); + } + + bufferevent_ssl_stop_reading(bev_ssl); + bufferevent_ssl_stop_writing(bev_ssl); + + bufferevent_run_eventcb_(&bev_ssl->bev.bev, when | event, 0); +} + +static int +be_mbedtls_bio_set_fd(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd) +{ + struct mbedtls_context *ctx = bev_ssl->ssl; + if (!bev_ssl->underlying) { + ctx->net.fd = fd; + mbedtls_ssl_set_bio( + ctx->ssl, &ctx->net, mbedtls_net_send, mbedtls_net_recv, NULL); + } else { + mbedtls_ssl_set_bio(ctx->ssl, bev_ssl->underlying, + bio_bufferevent_write, bio_bufferevent_read, NULL); + } + return 0; +} + +int +bufferevent_mbedtls_get_allow_dirty_shutdown(struct bufferevent *bev) +{ + return bufferevent_ssl_get_allow_dirty_shutdown(bev); +} + +void +bufferevent_mbedtls_set_allow_dirty_shutdown( + struct bufferevent *bev, int allow_dirty_shutdown) +{ + bufferevent_ssl_set_allow_dirty_shutdown(bev, allow_dirty_shutdown); +} + +mbedtls_ssl_context * +bufferevent_mbedtls_get_ssl(struct bufferevent *bufev) +{ + struct mbedtls_context *ctx = NULL; + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev); + if (!bev_ssl) + return NULL; + ctx = bev_ssl->ssl; + return ctx->ssl; +} + +int +bufferevent_mbedtls_renegotiate(struct bufferevent *bufev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev); + if (!bev_ssl) + return -1; + return bufferevent_ssl_renegotiate_impl(bufev); +} + +unsigned long +bufferevent_get_mbedtls_error(struct bufferevent *bufev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev); + if (!bev_ssl) + return -1; + return bufferevent_get_ssl_error(bufev); +} + +static struct le_ssl_ops le_mbedtls_ops = { + mbedtls_context_init, + mbedtls_context_free, + (void (*)(void *))mbedtls_ssl_free, + mbedtls_context_renegotiate, + mbedtls_context_write, + mbedtls_context_read, + mbedtls_context_pending, + mbedtls_context_handshake, + mbedtls_get_error, + mbedtls_clear_error, + mbedtls_clear, + mbedtls_set_ssl_noops, + mbedtls_set_ssl_noops, + mbedtls_is_ok, + mbedtls_is_want_read, + mbedtls_is_want_write, + be_mbedtls_get_fd, + be_mbedtls_bio_set_fd, + (void (*)(struct bufferevent_ssl *))mbedtls_set_ssl_noops, + (void (*)(struct bufferevent_ssl *))mbedtls_set_ssl_noops, + conn_closed, + print_err, +}; + +struct bufferevent * +bufferevent_mbedtls_filter_new(struct event_base *base, + struct bufferevent *underlying, mbedtls_ssl_context *ssl, + enum bufferevent_ssl_state state, int options) +{ + struct bufferevent *bev; + + if (!underlying) + goto err; + + bev = bufferevent_ssl_new_impl( + base, underlying, -1, ssl, state, options, &le_mbedtls_ops); + + if (bev) { + be_mbedtls_bio_set_fd(bufferevent_ssl_upcast(bev), -1); + } + + return bev; + +err: + if (options & BEV_OPT_CLOSE_ON_FREE) + mbedtls_ssl_free(ssl); + return NULL; +} + +struct bufferevent * +bufferevent_mbedtls_socket_new(struct event_base *base, evutil_socket_t fd, + mbedtls_ssl_context *ssl, enum bufferevent_ssl_state state, int options) +{ + long have_fd = -1; + struct bufferevent *bev; + + if (ssl->p_bio) { + /* The SSL is already configured with bio. */ + if (ssl->f_send == mbedtls_net_send && + ssl->f_recv == mbedtls_net_recv) { + have_fd = ((mbedtls_net_context *)ssl->p_bio)->fd; + } else if (ssl->f_send == bio_bufferevent_write && + ssl->f_recv == bio_bufferevent_read) { + have_fd = bufferevent_getfd(ssl->p_bio); + } else { + /* We don't known the fd. */ + have_fd = LONG_MAX; + } + } + + if (have_fd >= 0) { + if (fd < 0) { + /* We should learn the fd from the SSL. */ + fd = (evutil_socket_t)have_fd; + } else if (have_fd == (long)fd) { + /* We already know the fd from the SSL; do nothing */ + } else { + /* We specified an fd different from that of the SSL. + This is probably an error on our part. Fail. */ + goto err; + } + } else { + if (fd >= 0) { + /* ... and we have an fd we want to use. */ + } else { + /* Leave the fd unset. */ + } + } + + bev = bufferevent_ssl_new_impl( + base, NULL, fd, ssl, state, options, &le_mbedtls_ops); + + if (bev) { + be_mbedtls_bio_set_fd(bufferevent_ssl_upcast(bev), fd); + } + + return bev; +err: + return NULL; +} diff --git a/bufferevent_openssl.c b/bufferevent_openssl.c index b51b834b..c50c022d 100644 --- a/bufferevent_openssl.c +++ b/bufferevent_openssl.c @@ -24,49 +24,18 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// Get rid of OSX 10.7 and greater deprecation warnings. -#if defined(__APPLE__) && defined(__clang__) -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - -#include "event2/event-config.h" -#include "evconfig-private.h" - -#include - -#ifdef EVENT__HAVE_SYS_TIME_H -#include -#endif - -#include -#include -#include #include -#ifdef EVENT__HAVE_STDARG_H -#include -#endif -#ifdef EVENT__HAVE_UNISTD_H -#include -#endif - -#ifdef _WIN32 -#include -#endif - -#include "event2/bufferevent.h" -#include "event2/bufferevent_struct.h" -#include "event2/bufferevent_ssl.h" -#include "event2/buffer.h" -#include "event2/event.h" - -#include "mm-internal.h" -#include "bufferevent-internal.h" -#include "log-internal.h" #include #include #include "openssl-compat.h" +#include "event2/bufferevent.h" +#include "event2/bufferevent_struct.h" +#include "event2/buffer.h" + +#include "ssl-compat.h" + /* * Define an OpenSSL bio that targets a bufferevent. */ @@ -102,7 +71,10 @@ print_err(int val) } } #else -#define print_err(v) ((void)0) +static void +print_err(int val) +{ +} #endif /* Called to initialize a new BIO */ @@ -172,7 +144,7 @@ bio_bufferevent_write(BIO *b, const char *in, int inlen) /* Copy only as much data onto the output buffer as can fit under the * high-water mark. */ - if (bufev->wm_write.high && bufev->wm_write.high <= (outlen+inlen)) { + if (bufev->wm_write.high && bufev->wm_write.high <= (outlen + inlen)) { if (bufev->wm_write.high <= outlen) { /* If no data can fit, we'll need to retry later. */ BIO_set_retry_write(b); @@ -265,235 +237,8 @@ BIO_new_bufferevent(struct bufferevent *bufferevent) return result; } -/* -------------------- - Now, here's the OpenSSL-based implementation of bufferevent. - - The implementation comes in two flavors: one that connects its SSL object - to an underlying bufferevent using a BIO_bufferevent, and one that has the - SSL object connect to a socket directly. The latter should generally be - faster, except on Windows, where your best bet is using a - bufferevent_async. - - (OpenSSL supports many other BIO types, too. But we can't use any unless - we have a good way to get notified when they become readable/writable.) - -------------------- */ - -struct bio_data_counts { - unsigned long n_written; - unsigned long n_read; -}; - -struct bufferevent_openssl { - /* Shared fields with common bufferevent implementation code. - If we were set up with an underlying bufferevent, we use the - events here as timers only. If we have an SSL, then we use - the events as socket events. - */ - struct bufferevent_private bev; - /* An underlying bufferevent that we're directing our output to. - If it's NULL, then we're connected to an fd, not an evbuffer. */ - struct bufferevent *underlying; - /* The SSL object doing our encryption. */ - SSL *ssl; - - /* A callback that's invoked when data arrives on our outbuf so we - know to write data to the SSL. */ - struct evbuffer_cb_entry *outbuf_cb; - - /* A count of how much data the bios have read/written total. Used - for rate-limiting. */ - struct bio_data_counts counts; - - /* If this value is greater than 0, then the last SSL_write blocked, - * and we need to try it again with this many bytes. */ - ev_ssize_t last_write; - -#define NUM_ERRORS 3 - ev_uint32_t errors[NUM_ERRORS]; - - /* When we next get available space, we should say "read" instead of - "write". This can happen if there's a renegotiation during a read - operation. */ - unsigned read_blocked_on_write : 1; - /* When we next get data, we should say "write" instead of "read". */ - unsigned write_blocked_on_read : 1; - /* Treat TCP close before SSL close on SSL >= v3 as clean EOF. */ - unsigned allow_dirty_shutdown : 1; - /* XXX */ - unsigned n_errors : 2; - - /* Are we currently connecting, accepting, or doing IO? */ - unsigned state : 2; - /* If we reset fd, we sould reset state too */ - unsigned old_state : 2; -}; - -static int be_openssl_enable(struct bufferevent *, short); -static int be_openssl_disable(struct bufferevent *, short); -static void be_openssl_unlink(struct bufferevent *); -static void be_openssl_destruct(struct bufferevent *); -static int be_openssl_adj_timeouts(struct bufferevent *); -static int be_openssl_flush(struct bufferevent *bufev, - short iotype, enum bufferevent_flush_mode mode); -static int be_openssl_ctrl(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *); - -const struct bufferevent_ops bufferevent_ops_openssl = { - "ssl", - evutil_offsetof(struct bufferevent_openssl, bev.bev), - be_openssl_enable, - be_openssl_disable, - be_openssl_unlink, - be_openssl_destruct, - be_openssl_adj_timeouts, - be_openssl_flush, - be_openssl_ctrl, -}; - -/* Given a bufferevent, return a pointer to the bufferevent_openssl that - * contains it, if any. */ -static inline struct bufferevent_openssl * -upcast(struct bufferevent *bev) -{ - struct bufferevent_openssl *bev_o; - if (!BEV_IS_OPENSSL(bev)) - return NULL; - bev_o = (void*)( ((char*)bev) - - evutil_offsetof(struct bufferevent_openssl, bev.bev)); - EVUTIL_ASSERT(BEV_IS_OPENSSL(&bev_o->bev.bev)); - return bev_o; -} - -static inline void -put_error(struct bufferevent_openssl *bev_ssl, unsigned long err) -{ - if (bev_ssl->n_errors == NUM_ERRORS) - return; - /* The error type according to openssl is "unsigned long", but - openssl never uses more than 32 bits of it. It _can't_ use more - than 32 bits of it, since it needs to report errors on systems - where long is only 32 bits. - */ - bev_ssl->errors[bev_ssl->n_errors++] = (ev_uint32_t) err; -} - -/* Have the base communications channel (either the underlying bufferevent or - * ev_read and ev_write) start reading. Take the read-blocked-on-write flag - * into account. */ -static int -start_reading(struct bufferevent_openssl *bev_ssl) -{ - if (bev_ssl->underlying) { - bufferevent_unsuspend_read_(bev_ssl->underlying, - BEV_SUSPEND_FILT_READ); - return 0; - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - int r; - r = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); - if (r == 0 && bev_ssl->read_blocked_on_write) - r = bufferevent_add_event_(&bev->ev_write, - &bev->timeout_write); - return r; - } -} - -/* Have the base communications channel (either the underlying bufferevent or - * ev_read and ev_write) start writing. Take the write-blocked-on-read flag - * into account. */ -static int -start_writing(struct bufferevent_openssl *bev_ssl) -{ - int r = 0; - if (bev_ssl->underlying) { - if (bev_ssl->write_blocked_on_read) { - bufferevent_unsuspend_read_(bev_ssl->underlying, - BEV_SUSPEND_FILT_READ); - } - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - r = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); - if (!r && bev_ssl->write_blocked_on_read) - r = bufferevent_add_event_(&bev->ev_read, - &bev->timeout_read); - } - return r; -} - static void -stop_reading(struct bufferevent_openssl *bev_ssl) -{ - if (bev_ssl->write_blocked_on_read) - return; - if (bev_ssl->underlying) { - bufferevent_suspend_read_(bev_ssl->underlying, - BEV_SUSPEND_FILT_READ); - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - event_del(&bev->ev_read); - } -} - -static void -stop_writing(struct bufferevent_openssl *bev_ssl) -{ - if (bev_ssl->read_blocked_on_write) - return; - if (bev_ssl->underlying) { - bufferevent_unsuspend_read_(bev_ssl->underlying, - BEV_SUSPEND_FILT_READ); - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - event_del(&bev->ev_write); - } -} - -static int -set_rbow(struct bufferevent_openssl *bev_ssl) -{ - if (!bev_ssl->underlying) - stop_reading(bev_ssl); - bev_ssl->read_blocked_on_write = 1; - return start_writing(bev_ssl); -} - -static int -set_wbor(struct bufferevent_openssl *bev_ssl) -{ - if (!bev_ssl->underlying) - stop_writing(bev_ssl); - bev_ssl->write_blocked_on_read = 1; - return start_reading(bev_ssl); -} - -static int -clear_rbow(struct bufferevent_openssl *bev_ssl) -{ - struct bufferevent *bev = &bev_ssl->bev.bev; - int r = 0; - bev_ssl->read_blocked_on_write = 0; - if (!(bev->enabled & EV_WRITE)) - stop_writing(bev_ssl); - if (bev->enabled & EV_READ) - r = start_reading(bev_ssl); - return r; -} - - -static int -clear_wbor(struct bufferevent_openssl *bev_ssl) -{ - struct bufferevent *bev = &bev_ssl->bev.bev; - int r = 0; - bev_ssl->write_blocked_on_read = 0; - if (!(bev->enabled & EV_READ)) - stop_reading(bev_ssl); - if (bev->enabled & EV_WRITE) - r = start_writing(bev_ssl); - return r; -} - -static void -conn_closed(struct bufferevent_openssl *bev_ssl, int when, int errcode, int ret) +conn_closed(struct bufferevent_ssl *bev_ssl, int when, int errcode, int ret) { int event = BEV_EVENT_ERROR; int dirty_shutdown = 0; @@ -511,15 +256,15 @@ conn_closed(struct bufferevent_openssl *bev_ssl, int when, int errcode, int ret) /* IO error; possibly a dirty shutdown. */ if ((ret == 0 || ret == -1) && ERR_peek_error() == 0) dirty_shutdown = 1; - put_error(bev_ssl, errcode); + bufferevent_ssl_put_error(bev_ssl, errcode); break; case SSL_ERROR_SSL: /* Protocol error. */ - put_error(bev_ssl, errcode); + bufferevent_ssl_put_error(bev_ssl, errcode); break; case SSL_ERROR_WANT_X509_LOOKUP: /* XXXX handle this. */ - put_error(bev_ssl, errcode); + bufferevent_ssl_put_error(bev_ssl, errcode); break; case SSL_ERROR_NONE: case SSL_ERROR_WANT_READ: @@ -533,14 +278,14 @@ conn_closed(struct bufferevent_openssl *bev_ssl, int when, int errcode, int ret) } while ((err = ERR_get_error())) { - put_error(bev_ssl, err); + bufferevent_ssl_put_error(bev_ssl, err); } if (dirty_shutdown && bev_ssl->allow_dirty_shutdown) event = BEV_EVENT_EOF; - stop_reading(bev_ssl); - stop_writing(bev_ssl); + bufferevent_ssl_stop_reading(bev_ssl); + bufferevent_ssl_stop_writing(bev_ssl); /* when is BEV_EVENT_{READING|WRITING} */ event = when | event; @@ -548,7 +293,7 @@ conn_closed(struct bufferevent_openssl *bev_ssl, int when, int errcode, int ret) } static void -init_bio_counts(struct bufferevent_openssl *bev_ssl) +init_bio_counts(struct bufferevent_ssl *bev_ssl) { BIO *rbio, *wbio; @@ -559,7 +304,7 @@ init_bio_counts(struct bufferevent_openssl *bev_ssl) } static inline void -decrement_buckets(struct bufferevent_openssl *bev_ssl) +decrement_buckets(struct bufferevent_ssl *bev_ssl) { unsigned long num_w = BIO_number_written(SSL_get_wbio(bev_ssl->ssl)); unsigned long num_r = BIO_number_read(SSL_get_rbio(bev_ssl->ssl)); @@ -574,842 +319,104 @@ decrement_buckets(struct bufferevent_openssl *bev_ssl) bev_ssl->counts.n_read = num_r; } -#define OP_MADE_PROGRESS 1 -#define OP_BLOCKED 2 -#define OP_ERR 4 - -/* Return a bitmask of OP_MADE_PROGRESS (if we read anything); OP_BLOCKED (if - we're now blocked); and OP_ERR (if an error occurred). */ -static int -do_read(struct bufferevent_openssl *bev_ssl, int n_to_read) { - /* Requires lock */ - struct bufferevent *bev = &bev_ssl->bev.bev; - struct evbuffer *input = bev->input; - int r, n, i, n_used = 0, atmost; - struct evbuffer_iovec space[2]; - int result = 0; - - if (bev_ssl->bev.read_suspended) - return 0; - - atmost = bufferevent_get_read_max_(&bev_ssl->bev); - if (n_to_read > atmost) - n_to_read = atmost; - - n = evbuffer_reserve_space(input, n_to_read, space, 2); - if (n < 0) - return OP_ERR; - - for (i=0; ibev.read_suspended) - break; - ERR_clear_error(); - r = SSL_read(bev_ssl->ssl, space[i].iov_base, space[i].iov_len); - if (r>0) { - result |= OP_MADE_PROGRESS; - if (bev_ssl->read_blocked_on_write) - if (clear_rbow(bev_ssl) < 0) - return OP_ERR | result; - ++n_used; - space[i].iov_len = r; - decrement_buckets(bev_ssl); - } else { - int err = SSL_get_error(bev_ssl->ssl, r); - print_err(err); - switch (err) { - case SSL_ERROR_WANT_READ: - /* Can't read until underlying has more data. */ - if (bev_ssl->read_blocked_on_write) - if (clear_rbow(bev_ssl) < 0) - return OP_ERR | result; - break; - case SSL_ERROR_WANT_WRITE: - /* This read operation requires a write, and the - * underlying is full */ - if (!bev_ssl->read_blocked_on_write) - if (set_rbow(bev_ssl) < 0) - return OP_ERR | result; - break; - default: - conn_closed(bev_ssl, BEV_EVENT_READING, err, r); - break; - } - result |= OP_BLOCKED; - break; /* out of the loop */ - } - } - - if (n_used) { - evbuffer_commit_space(input, space, n_used); - if (bev_ssl->underlying) - BEV_RESET_GENERIC_READ_TIMEOUT(bev); - } - - return result; -} - -/* Return a bitmask of OP_MADE_PROGRESS (if we wrote anything); OP_BLOCKED (if - we're now blocked); and OP_ERR (if an error occurred). */ -static int -do_write(struct bufferevent_openssl *bev_ssl, int atmost) +static void * +SSL_init(void *ssl) { - int i, r, n, n_written = 0; - struct bufferevent *bev = &bev_ssl->bev.bev; - struct evbuffer *output = bev->output; - struct evbuffer_iovec space[8]; - int result = 0; - - if (bev_ssl->last_write > 0) - atmost = bev_ssl->last_write; - else - atmost = bufferevent_get_write_max_(&bev_ssl->bev); - - n = evbuffer_peek(output, atmost, NULL, space, 8); - if (n < 0) - return OP_ERR | result; - - if (n > 8) - n = 8; - for (i=0; i < n; ++i) { - if (bev_ssl->bev.write_suspended) - break; - - /* SSL_write will (reasonably) return 0 if we tell it to - send 0 data. Skip this case so we don't interpret the - result as an error */ - if (space[i].iov_len == 0) - continue; - - ERR_clear_error(); - r = SSL_write(bev_ssl->ssl, space[i].iov_base, - space[i].iov_len); - if (r > 0) { - result |= OP_MADE_PROGRESS; - if (bev_ssl->write_blocked_on_read) - if (clear_wbor(bev_ssl) < 0) - return OP_ERR | result; - n_written += r; - bev_ssl->last_write = -1; - decrement_buckets(bev_ssl); - } else { - int err = SSL_get_error(bev_ssl->ssl, r); - print_err(err); - switch (err) { - case SSL_ERROR_WANT_WRITE: - /* Can't read until underlying has more data. */ - if (bev_ssl->write_blocked_on_read) - if (clear_wbor(bev_ssl) < 0) - return OP_ERR | result; - bev_ssl->last_write = space[i].iov_len; - break; - case SSL_ERROR_WANT_READ: - /* This read operation requires a write, and the - * underlying is full */ - if (!bev_ssl->write_blocked_on_read) - if (set_wbor(bev_ssl) < 0) - return OP_ERR | result; - bev_ssl->last_write = space[i].iov_len; - break; - default: - conn_closed(bev_ssl, BEV_EVENT_WRITING, err, r); - bev_ssl->last_write = -1; - break; - } - result |= OP_BLOCKED; - break; - } - } - if (n_written) { - evbuffer_drain(output, n_written); - if (bev_ssl->underlying) - BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); - - bufferevent_trigger_nolock_(bev, EV_WRITE, BEV_OPT_DEFER_CALLBACKS); - } - return result; -} - -#define WRITE_FRAME 15000 - -#define READ_DEFAULT 4096 - -/* Try to figure out how many bytes to read; return 0 if we shouldn't be - * reading. */ -static int -bytes_to_read(struct bufferevent_openssl *bev) -{ - struct evbuffer *input = bev->bev.bev.input; - struct event_watermark *wm = &bev->bev.bev.wm_read; - int result = READ_DEFAULT; - ev_ssize_t limit; - /* XXX 99% of this is generic code that nearly all bufferevents will - * want. */ - - if (bev->write_blocked_on_read) { - return 0; - } - - if (! (bev->bev.bev.enabled & EV_READ)) { - return 0; - } - - if (bev->bev.read_suspended) { - return 0; - } - - if (wm->high) { - if (evbuffer_get_length(input) >= wm->high) { - return 0; - } - - result = wm->high - evbuffer_get_length(input); - } else { - result = READ_DEFAULT; - } - - /* Respect the rate limit */ - limit = bufferevent_get_read_max_(&bev->bev); - if (result > limit) { - result = limit; - } - - return result; -} - - -/* Things look readable. If write is blocked on read, write till it isn't. - * Read from the underlying buffer until we block or we hit our high-water - * mark. - */ -static void -consider_reading(struct bufferevent_openssl *bev_ssl) -{ - int r; - int n_to_read; - int all_result_flags = 0; - - while (bev_ssl->write_blocked_on_read) { - r = do_write(bev_ssl, WRITE_FRAME); - if (r & (OP_BLOCKED|OP_ERR)) - break; - } - if (bev_ssl->write_blocked_on_read) - return; - - n_to_read = bytes_to_read(bev_ssl); - - while (n_to_read) { - r = do_read(bev_ssl, n_to_read); - all_result_flags |= r; - - if (r & (OP_BLOCKED|OP_ERR)) - break; - - if (bev_ssl->bev.read_suspended) - break; - - /* Read all pending data. This won't hit the network - * again, and will (most importantly) put us in a state - * where we don't need to read anything else until the - * socket is readable again. It'll potentially make us - * overrun our read high-watermark (somewhat - * regrettable). The damage to the rate-limit has - * already been done, since OpenSSL went and read a - * whole SSL record anyway. */ - n_to_read = SSL_pending(bev_ssl->ssl); - - /* XXX This if statement is actually a bad bug, added to avoid - * XXX a worse bug. - * - * The bad bug: It can potentially cause resource unfairness - * by reading too much data from the underlying bufferevent; - * it can potentially cause read looping if the underlying - * bufferevent is a bufferevent_pair and deferred callbacks - * aren't used. - * - * The worse bug: If we didn't do this, then we would - * potentially not read any more from bev_ssl->underlying - * until more data arrived there, which could lead to us - * waiting forever. - */ - if (!n_to_read && bev_ssl->underlying) - n_to_read = bytes_to_read(bev_ssl); - } - - if (all_result_flags & OP_MADE_PROGRESS) { - struct bufferevent *bev = &bev_ssl->bev.bev; - - bufferevent_trigger_nolock_(bev, EV_READ, 0); - } - - if (!bev_ssl->underlying) { - /* Should be redundant, but let's avoid busy-looping */ - if (bev_ssl->bev.read_suspended || - !(bev_ssl->bev.bev.enabled & EV_READ)) { - event_del(&bev_ssl->bev.bev.ev_read); - } - } -} - -static void -consider_writing(struct bufferevent_openssl *bev_ssl) -{ - int r; - struct evbuffer *output = bev_ssl->bev.bev.output; - struct evbuffer *target = NULL; - struct event_watermark *wm = NULL; - - while (bev_ssl->read_blocked_on_write) { - r = do_read(bev_ssl, 1024); /* XXXX 1024 is a hack */ - if (r & OP_MADE_PROGRESS) { - struct bufferevent *bev = &bev_ssl->bev.bev; - - bufferevent_trigger_nolock_(bev, EV_READ, 0); - } - if (r & (OP_ERR|OP_BLOCKED)) - break; - } - if (bev_ssl->read_blocked_on_write) - return; - if (bev_ssl->underlying) { - target = bev_ssl->underlying->output; - wm = &bev_ssl->underlying->wm_write; - } - while ((bev_ssl->bev.bev.enabled & EV_WRITE) && - (! bev_ssl->bev.write_suspended) && - evbuffer_get_length(output) && - (!target || (! wm->high || evbuffer_get_length(target) < wm->high))) { - int n_to_write; - if (wm && wm->high) - n_to_write = wm->high - evbuffer_get_length(target); - else - n_to_write = WRITE_FRAME; - r = do_write(bev_ssl, n_to_write); - if (r & (OP_BLOCKED|OP_ERR)) - break; - } - - if (!bev_ssl->underlying) { - if (evbuffer_get_length(output) == 0) { - event_del(&bev_ssl->bev.bev.ev_write); - } else if (bev_ssl->bev.write_suspended || - !(bev_ssl->bev.bev.enabled & EV_WRITE)) { - /* Should be redundant, but let's avoid busy-looping */ - event_del(&bev_ssl->bev.bev.ev_write); - } - } -} - -static void -be_openssl_readcb(struct bufferevent *bev_base, void *ctx) -{ - struct bufferevent_openssl *bev_ssl = ctx; - consider_reading(bev_ssl); -} - -static void -be_openssl_writecb(struct bufferevent *bev_base, void *ctx) -{ - struct bufferevent_openssl *bev_ssl = ctx; - consider_writing(bev_ssl); -} - -static void -be_openssl_eventcb(struct bufferevent *bev_base, short what, void *ctx) -{ - struct bufferevent_openssl *bev_ssl = ctx; - int event = 0; - - if (what & BEV_EVENT_EOF) { - if (bev_ssl->allow_dirty_shutdown) - event = BEV_EVENT_EOF; - else - event = BEV_EVENT_ERROR; - } else if (what & BEV_EVENT_TIMEOUT) { - /* We sure didn't set this. Propagate it to the user. */ - event = what; - } else if (what & BEV_EVENT_ERROR) { - /* An error occurred on the connection. Propagate it to the user. */ - event = what; - } else if (what & BEV_EVENT_CONNECTED) { - /* Ignore it. We're saying SSL_connect() already, which will - eat it. */ - } - if (event) - bufferevent_run_eventcb_(&bev_ssl->bev.bev, event, 0); -} - -static void -be_openssl_readeventcb(evutil_socket_t fd, short what, void *ptr) -{ - struct bufferevent_openssl *bev_ssl = ptr; - bufferevent_incref_and_lock_(&bev_ssl->bev.bev); - if (what == EV_TIMEOUT) { - bufferevent_run_eventcb_(&bev_ssl->bev.bev, - BEV_EVENT_TIMEOUT|BEV_EVENT_READING, 0); - } else { - consider_reading(bev_ssl); - } - bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); -} - -static void -be_openssl_writeeventcb(evutil_socket_t fd, short what, void *ptr) -{ - struct bufferevent_openssl *bev_ssl = ptr; - bufferevent_incref_and_lock_(&bev_ssl->bev.bev); - if (what == EV_TIMEOUT) { - bufferevent_run_eventcb_(&bev_ssl->bev.bev, - BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING, 0); - } else { - consider_writing(bev_ssl); - } - bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); -} - -static evutil_socket_t -be_openssl_auto_fd(struct bufferevent_openssl *bev_ssl, evutil_socket_t fd) -{ - if (!bev_ssl->underlying) { - struct bufferevent *bev = &bev_ssl->bev.bev; - if (event_initialized(&bev->ev_read) && fd < 0) { - fd = event_get_fd(&bev->ev_read); - } - } - return fd; -} - -static int -set_open_callbacks(struct bufferevent_openssl *bev_ssl, evutil_socket_t fd) -{ - if (bev_ssl->underlying) { - bufferevent_setcb(bev_ssl->underlying, - be_openssl_readcb, be_openssl_writecb, be_openssl_eventcb, - bev_ssl); - return 0; - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - int rpending=0, wpending=0, r1=0, r2=0; - - if (event_initialized(&bev->ev_read)) { - rpending = event_pending(&bev->ev_read, EV_READ, NULL); - wpending = event_pending(&bev->ev_write, EV_WRITE, NULL); - - event_del(&bev->ev_read); - event_del(&bev->ev_write); - } - - event_assign(&bev->ev_read, bev->ev_base, fd, - EV_READ|EV_PERSIST|EV_FINALIZE, - be_openssl_readeventcb, bev_ssl); - event_assign(&bev->ev_write, bev->ev_base, fd, - EV_WRITE|EV_PERSIST|EV_FINALIZE, - be_openssl_writeeventcb, bev_ssl); - - if (rpending) - r1 = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); - if (wpending) - r2 = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); - - return (r1<0 || r2<0) ? -1 : 0; - } -} - -static int -do_handshake(struct bufferevent_openssl *bev_ssl) -{ - int r; - - switch (bev_ssl->state) { - default: - case BUFFEREVENT_SSL_OPEN: - EVUTIL_ASSERT(0); - return -1; - case BUFFEREVENT_SSL_CONNECTING: - case BUFFEREVENT_SSL_ACCEPTING: - ERR_clear_error(); - r = SSL_do_handshake(bev_ssl->ssl); - break; - } - decrement_buckets(bev_ssl); - - if (r==1) { - evutil_socket_t fd = event_get_fd(&bev_ssl->bev.bev.ev_read); - /* We're done! */ - bev_ssl->state = BUFFEREVENT_SSL_OPEN; - set_open_callbacks(bev_ssl, fd); /* XXXX handle failure */ - /* Call do_read and do_write as needed */ - bufferevent_enable(&bev_ssl->bev.bev, bev_ssl->bev.bev.enabled); - bufferevent_run_eventcb_(&bev_ssl->bev.bev, - BEV_EVENT_CONNECTED, 0); - return 1; - } else { - int err = SSL_get_error(bev_ssl->ssl, r); - print_err(err); - switch (err) { - case SSL_ERROR_WANT_WRITE: - stop_reading(bev_ssl); - return start_writing(bev_ssl); - case SSL_ERROR_WANT_READ: - stop_writing(bev_ssl); - return start_reading(bev_ssl); - default: - conn_closed(bev_ssl, BEV_EVENT_READING, err, r); - return -1; - } - } -} - -static void -be_openssl_handshakecb(struct bufferevent *bev_base, void *ctx) -{ - struct bufferevent_openssl *bev_ssl = ctx; - do_handshake(bev_ssl);/* XXX handle failure */ -} - -static void -be_openssl_handshakeeventcb(evutil_socket_t fd, short what, void *ptr) -{ - struct bufferevent_openssl *bev_ssl = ptr; - - bufferevent_incref_and_lock_(&bev_ssl->bev.bev); - if (what & EV_TIMEOUT) { - bufferevent_run_eventcb_(&bev_ssl->bev.bev, BEV_EVENT_TIMEOUT, 0); - } else - do_handshake(bev_ssl);/* XXX handle failure */ - bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); -} - -static int -set_handshake_callbacks(struct bufferevent_openssl *bev_ssl, evutil_socket_t fd) -{ - if (bev_ssl->underlying) { - bufferevent_setcb(bev_ssl->underlying, - be_openssl_handshakecb, be_openssl_handshakecb, - be_openssl_eventcb, - bev_ssl); - - if (fd < 0) - return 0; - - if (bufferevent_setfd(bev_ssl->underlying, fd)) - return 1; - - return do_handshake(bev_ssl); - } else { - struct bufferevent *bev = &bev_ssl->bev.bev; - - if (event_initialized(&bev->ev_read)) { - event_del(&bev->ev_read); - event_del(&bev->ev_write); - } - - event_assign(&bev->ev_read, bev->ev_base, fd, - EV_READ|EV_PERSIST|EV_FINALIZE, - be_openssl_handshakeeventcb, bev_ssl); - event_assign(&bev->ev_write, bev->ev_base, fd, - EV_WRITE|EV_PERSIST|EV_FINALIZE, - be_openssl_handshakeeventcb, bev_ssl); - if (fd >= 0) - bufferevent_enable(bev, bev->enabled); - return 0; - } -} - -int -bufferevent_ssl_renegotiate(struct bufferevent *bev) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - if (!bev_ssl) - return -1; - if (SSL_renegotiate(bev_ssl->ssl) < 0) - return -1; - bev_ssl->state = BUFFEREVENT_SSL_CONNECTING; - if (set_handshake_callbacks(bev_ssl, be_openssl_auto_fd(bev_ssl, -1)) < 0) - return -1; - if (!bev_ssl->underlying) - return do_handshake(bev_ssl); - return 0; -} - -static void -be_openssl_outbuf_cb(struct evbuffer *buf, - const struct evbuffer_cb_info *cbinfo, void *arg) -{ - struct bufferevent_openssl *bev_ssl = arg; - int r = 0; - /* XXX need to hold a reference here. */ - - if (cbinfo->n_added && bev_ssl->state == BUFFEREVENT_SSL_OPEN) { - if (cbinfo->orig_size == 0) - r = bufferevent_add_event_(&bev_ssl->bev.bev.ev_write, - &bev_ssl->bev.bev.timeout_write); - - if (bev_ssl->underlying) - consider_writing(bev_ssl); - } - /* XXX Handle r < 0 */ - (void)r; -} - - -static int -be_openssl_enable(struct bufferevent *bev, short events) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - int r1 = 0, r2 = 0; - - if (events & EV_READ) - r1 = start_reading(bev_ssl); - if (events & EV_WRITE) - r2 = start_writing(bev_ssl); - - if (bev_ssl->underlying) { - if (events & EV_READ) - BEV_RESET_GENERIC_READ_TIMEOUT(bev); - if (events & EV_WRITE) - BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); - - if (events & EV_READ) - consider_reading(bev_ssl); - if (events & EV_WRITE) - consider_writing(bev_ssl); - } - return (r1 < 0 || r2 < 0) ? -1 : 0; -} - -static int -be_openssl_disable(struct bufferevent *bev, short events) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - - if (events & EV_READ) - stop_reading(bev_ssl); - if (events & EV_WRITE) - stop_writing(bev_ssl); - - if (bev_ssl->underlying) { - if (events & EV_READ) - BEV_DEL_GENERIC_READ_TIMEOUT(bev); - if (events & EV_WRITE) - BEV_DEL_GENERIC_WRITE_TIMEOUT(bev); - } - return 0; -} - -static void -be_openssl_unlink(struct bufferevent *bev) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - - if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { - if (bev_ssl->underlying) { - if (BEV_UPCAST(bev_ssl->underlying)->refcnt < 2) { - event_warnx("BEV_OPT_CLOSE_ON_FREE set on an " - "bufferevent with too few references"); - } else { - bufferevent_free(bev_ssl->underlying); - /* We still have a reference to it, via our - * BIO. So we don't drop this. */ - // bev_ssl->underlying = NULL; - } - } - } else { - if (bev_ssl->underlying) { - if (bev_ssl->underlying->errorcb == be_openssl_eventcb) - bufferevent_setcb(bev_ssl->underlying, - NULL,NULL,NULL,NULL); - bufferevent_unsuspend_read_(bev_ssl->underlying, - BEV_SUSPEND_FILT_READ); - } - } -} - -static void -be_openssl_destruct(struct bufferevent *bev) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - - if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { - if (! bev_ssl->underlying) { - evutil_socket_t fd = EVUTIL_INVALID_SOCKET; - BIO *bio = SSL_get_wbio(bev_ssl->ssl); - if (bio) - fd = BIO_get_fd(bio, NULL); - if (fd >= 0) - evutil_closesocket(fd); - } - SSL_free(bev_ssl->ssl); - } -} - -static int -be_openssl_adj_timeouts(struct bufferevent *bev) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - - if (bev_ssl->underlying) { - return bufferevent_generic_adj_timeouts_(bev); - } else { - return bufferevent_generic_adj_existing_timeouts_(bev); - } -} - -static int -be_openssl_flush(struct bufferevent *bufev, - short iotype, enum bufferevent_flush_mode mode) -{ - /* XXXX Implement this. */ - return 0; -} - -static int -be_openssl_set_fd(struct bufferevent_openssl *bev_ssl, - enum bufferevent_ssl_state state, evutil_socket_t fd) -{ - bev_ssl->state = state; - - switch (state) { - case BUFFEREVENT_SSL_ACCEPTING: - if (!SSL_clear(bev_ssl->ssl)) - return -1; - SSL_set_accept_state(bev_ssl->ssl); - if (set_handshake_callbacks(bev_ssl, fd) < 0) - return -1; - break; - case BUFFEREVENT_SSL_CONNECTING: - if (!SSL_clear(bev_ssl->ssl)) - return -1; - SSL_set_connect_state(bev_ssl->ssl); - if (set_handshake_callbacks(bev_ssl, fd) < 0) - return -1; - break; - case BUFFEREVENT_SSL_OPEN: - if (set_open_callbacks(bev_ssl, fd) < 0) - return -1; - break; - default: - return -1; - } - - return 0; -} - -static int -be_openssl_ctrl(struct bufferevent *bev, - enum bufferevent_ctrl_op op, union bufferevent_ctrl_data *data) -{ - struct bufferevent_openssl *bev_ssl = upcast(bev); - switch (op) { - case BEV_CTRL_SET_FD: - if (!bev_ssl->underlying) { - BIO *bio; - bio = BIO_new_socket((int)data->fd, 0); - SSL_set_bio(bev_ssl->ssl, bio, bio); - } else { - BIO *bio; - if (!(bio = BIO_new_bufferevent(bev_ssl->underlying))) - return -1; - SSL_set_bio(bev_ssl->ssl, bio, bio); - } - - return be_openssl_set_fd(bev_ssl, bev_ssl->old_state, data->fd); - case BEV_CTRL_GET_FD: - if (bev_ssl->underlying) { - data->fd = event_get_fd(&bev_ssl->underlying->ev_read); - } else { - data->fd = event_get_fd(&bev->ev_read); - } - return 0; - case BEV_CTRL_GET_UNDERLYING: - data->ptr = bev_ssl->underlying; - return 0; - case BEV_CTRL_CANCEL_ALL: - default: - return -1; - } -} - -SSL * -bufferevent_openssl_get_ssl(struct bufferevent *bufev) -{ - struct bufferevent_openssl *bev_ssl = upcast(bufev); - if (!bev_ssl) - return NULL; - return bev_ssl->ssl; -} - -static struct bufferevent * -bufferevent_openssl_new_impl(struct event_base *base, - struct bufferevent *underlying, - evutil_socket_t fd, - SSL *ssl, - enum bufferevent_ssl_state state, - int options) -{ - struct bufferevent_openssl *bev_ssl = NULL; - struct bufferevent_private *bev_p = NULL; - int tmp_options = options & ~BEV_OPT_THREADSAFE; - - /* Only one can be set. */ - if (underlying != NULL && fd >= 0) - goto err; - - if (!(bev_ssl = mm_calloc(1, sizeof(struct bufferevent_openssl)))) - goto err; - - bev_p = &bev_ssl->bev; - - if (bufferevent_init_common_(bev_p, base, - &bufferevent_ops_openssl, tmp_options) < 0) - goto err; - /* Don't explode if we decide to realloc a chunk we're writing from in * the output buffer. */ SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - bev_ssl->underlying = underlying; - bev_ssl->ssl = ssl; - - bev_ssl->outbuf_cb = evbuffer_add_cb(bev_p->bev.output, - be_openssl_outbuf_cb, bev_ssl); - - if (options & BEV_OPT_THREADSAFE) - bufferevent_enable_locking_(&bev_ssl->bev.bev, NULL); - - if (underlying) { - bufferevent_init_generic_timeout_cbs_(&bev_ssl->bev.bev); - bufferevent_incref_(underlying); - } - - bev_ssl->old_state = state; - bev_ssl->last_write = -1; - - init_bio_counts(bev_ssl); - - fd = be_openssl_auto_fd(bev_ssl, fd); - if (be_openssl_set_fd(bev_ssl, state, fd)) - goto err; - - if (underlying) { - bufferevent_setwatermark(underlying, EV_READ, 0, 0); - bufferevent_enable(underlying, EV_READ|EV_WRITE); - if (state == BUFFEREVENT_SSL_OPEN) - bufferevent_suspend_read_(underlying, - BEV_SUSPEND_FILT_READ); - } - - return &bev_ssl->bev.bev; -err: - if (options & BEV_OPT_CLOSE_ON_FREE) - SSL_free(ssl); - if (bev_ssl) { - bev_ssl->ssl = NULL; - bufferevent_free(&bev_ssl->bev.bev); - } - return NULL; + return ssl; } +static void +SSL_context_free(void *ssl, int flags) +{ + if (flags & BEV_OPT_CLOSE_ON_FREE) + SSL_free(ssl); +} + +static int +SSL_is_ok(int err) +{ + return err == 1; +} + +static int +SSL_is_want_read(int err) +{ + return err == SSL_ERROR_WANT_READ; +} + +static int +SSL_is_want_write(int err) +{ + return err == SSL_ERROR_WANT_WRITE; +} + +static int +openssl_read(void *ssl, unsigned char *buf, size_t len) +{ + return SSL_read(ssl, buf, len); +} + +static int +openssl_write(void *ssl, const unsigned char *buf, size_t len) +{ + return SSL_write(ssl, buf, len); +} + +static evutil_socket_t +be_openssl_get_fd(struct bufferevent_ssl *bev_ssl) +{ + evutil_socket_t fd = EVUTIL_INVALID_SOCKET; + BIO *bio = SSL_get_wbio(bev_ssl->ssl); + if (bio) + fd = BIO_get_fd(bio, NULL); + return fd; +} + +static int +be_openssl_bio_set_fd(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd) +{ + if (!bev_ssl->underlying) { + BIO *bio; + bio = BIO_new_socket((int)fd, 0); + SSL_set_bio(bev_ssl->ssl, bio, bio); + } else { + BIO *bio; + if (!(bio = BIO_new_bufferevent(bev_ssl->underlying))) + return -1; + SSL_set_bio(bev_ssl->ssl, bio, bio); + } + return 0; +} + +static struct le_ssl_ops le_openssl_ops = { + SSL_init, + SSL_context_free, + (void (*)(void *))SSL_free, + (int (*)(void *))SSL_renegotiate, + openssl_write, + openssl_read, + (size_t(*)(void *))SSL_pending, + (int (*)(void *))SSL_do_handshake, + (int (*)(void *, int))SSL_get_error, + ERR_clear_error, + (int (*)(void *))SSL_clear, + (void (*)(void *))SSL_set_connect_state, + (void (*)(void *))SSL_set_accept_state, + SSL_is_ok, + SSL_is_want_read, + SSL_is_want_write, + (int (*)(void *))be_openssl_get_fd, + be_openssl_bio_set_fd, + init_bio_counts, + decrement_buckets, + conn_closed, + print_err, +}; + struct bufferevent * bufferevent_openssl_filter_new(struct event_base *base, struct bufferevent *underlying, @@ -1427,8 +434,8 @@ bufferevent_openssl_filter_new(struct event_base *base, SSL_set_bio(ssl, bio, bio); - bev = bufferevent_openssl_new_impl( - base, underlying, -1, ssl, state, options); + bev = bufferevent_ssl_new_impl( + base, underlying, -1, ssl, state, options, &le_openssl_ops); return bev; err: @@ -1475,8 +482,8 @@ bufferevent_openssl_socket_new(struct event_base *base, } } - return bufferevent_openssl_new_impl( - base, NULL, fd, ssl, state, options); + return bufferevent_ssl_new_impl( + base, NULL, fd, ssl, state, options, &le_openssl_ops); err: if (options & BEV_OPT_CLOSE_ON_FREE) @@ -1484,41 +491,39 @@ err: return NULL; } +int +bufferevent_ssl_renegotiate(struct bufferevent *bev) +{ + return bufferevent_ssl_renegotiate_impl(bev); +} + +SSL * +bufferevent_openssl_get_ssl(struct bufferevent *bufev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev); + if (!bev_ssl) + return NULL; + return bev_ssl->ssl; +} + int bufferevent_openssl_get_allow_dirty_shutdown(struct bufferevent *bev) { - int allow_dirty_shutdown = -1; - struct bufferevent_openssl *bev_ssl; - BEV_LOCK(bev); - bev_ssl = upcast(bev); - if (bev_ssl) - allow_dirty_shutdown = bev_ssl->allow_dirty_shutdown; - BEV_UNLOCK(bev); - return allow_dirty_shutdown; + return bufferevent_ssl_get_allow_dirty_shutdown(bev); } void -bufferevent_openssl_set_allow_dirty_shutdown(struct bufferevent *bev, - int allow_dirty_shutdown) +bufferevent_openssl_set_allow_dirty_shutdown( + struct bufferevent *bev, int allow_dirty_shutdown) { - struct bufferevent_openssl *bev_ssl; - BEV_LOCK(bev); - bev_ssl = upcast(bev); - if (bev_ssl) - bev_ssl->allow_dirty_shutdown = !!allow_dirty_shutdown; - BEV_UNLOCK(bev); + bufferevent_ssl_set_allow_dirty_shutdown(bev, allow_dirty_shutdown); } unsigned long -bufferevent_get_openssl_error(struct bufferevent *bev) +bufferevent_get_openssl_error(struct bufferevent *bufev) { - unsigned long err = 0; - struct bufferevent_openssl *bev_ssl; - BEV_LOCK(bev); - bev_ssl = upcast(bev); - if (bev_ssl && bev_ssl->n_errors) { - err = bev_ssl->errors[--bev_ssl->n_errors]; - } - BEV_UNLOCK(bev); - return err; + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev); + if (!bev_ssl) + return -1; + return bufferevent_get_ssl_error(bufev); } diff --git a/bufferevent_ssl.c b/bufferevent_ssl.c new file mode 100644 index 00000000..5c6956d5 --- /dev/null +++ b/bufferevent_ssl.c @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Get rid of OSX 10.7 and greater deprecation warnings. +#if defined(__APPLE__) && defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include "event2/event-config.h" +#include "evconfig-private.h" + +#include + +#ifdef EVENT__HAVE_SYS_TIME_H +#include +#endif + +#include +#include +#include +#include +#ifdef EVENT__HAVE_STDARG_H +#include +#endif +#ifdef EVENT__HAVE_UNISTD_H +#include +#endif + +#ifdef _WIN32 +#include +#endif + +#include "event2/bufferevent.h" +#include "event2/bufferevent_struct.h" +#include "event2/bufferevent_ssl.h" +#include "event2/buffer.h" +#include "event2/event.h" + +#include "mm-internal.h" +#include "bufferevent-internal.h" +#include "log-internal.h" +#include "ssl-compat.h" + +/* -------------------- + Now, here's the OpenSSL-based implementation of bufferevent. + + The implementation comes in two flavors: one that connects its SSL object + to an underlying bufferevent using a BIO_bufferevent, and one that has the + SSL object connect to a socket directly. The latter should generally be + faster, except on Windows, where your best bet is using a + bufferevent_async. + + (OpenSSL supports many other BIO types, too. But we can't use any unless + we have a good way to get notified when they become readable/writable.) + -------------------- */ + + +static int be_ssl_enable(struct bufferevent *, short); +static int be_ssl_disable(struct bufferevent *, short); +static void be_ssl_unlink(struct bufferevent *); +static void be_ssl_destruct(struct bufferevent *); +static int be_ssl_adj_timeouts(struct bufferevent *); +static int be_ssl_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode); +static int be_ssl_ctrl(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *); + +const struct bufferevent_ops bufferevent_ops_ssl = { + "ssl", + evutil_offsetof(struct bufferevent_ssl, bev.bev), + be_ssl_enable, + be_ssl_disable, + be_ssl_unlink, + be_ssl_destruct, + be_ssl_adj_timeouts, + be_ssl_flush, + be_ssl_ctrl, +}; + +/* Given a bufferevent, return a pointer to the bufferevent_ssl that + * contains it, if any. */ +struct bufferevent_ssl * +bufferevent_ssl_upcast(struct bufferevent *bev) +{ + struct bufferevent_ssl *bev_o; + if (!BEV_IS_SSL(bev)) + return NULL; + bev_o = (void*)( ((char*)bev) - + evutil_offsetof(struct bufferevent_ssl, bev.bev)); + EVUTIL_ASSERT(BEV_IS_SSL(&bev_o->bev.bev)); + return bev_o; +} + +void +bufferevent_ssl_put_error(struct bufferevent_ssl *bev_ssl, unsigned long err) +{ + if (bev_ssl->n_errors == NUM_ERRORS) + return; + /* The error type according to openssl is "unsigned long", but + openssl never uses more than 32 bits of it. It _can't_ use more + than 32 bits of it, since it needs to report errors on systems + where long is only 32 bits. + */ + bev_ssl->errors[bev_ssl->n_errors++] = (ev_uint32_t) err; +} + +/* Have the base communications channel (either the underlying bufferevent or + * ev_read and ev_write) start reading. Take the read-blocked-on-write flag + * into account. */ +static int +start_reading(struct bufferevent_ssl *bev_ssl) +{ + if (bev_ssl->underlying) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + return 0; + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + int r; + r = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); + if (r == 0 && bev_ssl->read_blocked_on_write) + r = bufferevent_add_event_(&bev->ev_write, + &bev->timeout_write); + return r; + } +} + +/* Have the base communications channel (either the underlying bufferevent or + * ev_read and ev_write) start writing. Take the write-blocked-on-read flag + * into account. */ +static int +start_writing(struct bufferevent_ssl *bev_ssl) +{ + int r = 0; + if (bev_ssl->underlying) { + if (bev_ssl->write_blocked_on_read) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + r = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); + if (!r && bev_ssl->write_blocked_on_read) + r = bufferevent_add_event_(&bev->ev_read, + &bev->timeout_read); + } + return r; +} + +void +bufferevent_ssl_stop_reading(struct bufferevent_ssl *bev_ssl) +{ + if (bev_ssl->write_blocked_on_read) + return; + if (bev_ssl->underlying) { + bufferevent_suspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_read); + } +} + +void +bufferevent_ssl_stop_writing(struct bufferevent_ssl *bev_ssl) +{ + if (bev_ssl->read_blocked_on_write) + return; + if (bev_ssl->underlying) { + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + event_del(&bev->ev_write); + } +} + +static int +set_rbow(struct bufferevent_ssl *bev_ssl) +{ + if (!bev_ssl->underlying) + bufferevent_ssl_stop_reading(bev_ssl); + bev_ssl->read_blocked_on_write = 1; + return start_writing(bev_ssl); +} + +static int +set_wbor(struct bufferevent_ssl *bev_ssl) +{ + if (!bev_ssl->underlying) + bufferevent_ssl_stop_writing(bev_ssl); + bev_ssl->write_blocked_on_read = 1; + return start_reading(bev_ssl); +} + +static int +clear_rbow(struct bufferevent_ssl *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + int r = 0; + bev_ssl->read_blocked_on_write = 0; + if (!(bev->enabled & EV_WRITE)) + bufferevent_ssl_stop_writing(bev_ssl); + if (bev->enabled & EV_READ) + r = start_reading(bev_ssl); + return r; +} + + +static int +clear_wbor(struct bufferevent_ssl *bev_ssl) +{ + struct bufferevent *bev = &bev_ssl->bev.bev; + int r = 0; + bev_ssl->write_blocked_on_read = 0; + if (!(bev->enabled & EV_READ)) + bufferevent_ssl_stop_reading(bev_ssl); + if (bev->enabled & EV_WRITE) + r = start_writing(bev_ssl); + return r; +} + +#define OP_MADE_PROGRESS 1 +#define OP_BLOCKED 2 +#define OP_ERR 4 + +/* Return a bitmask of OP_MADE_PROGRESS (if we read anything); OP_BLOCKED (if + we're now blocked); and OP_ERR (if an error occurred). */ +static int +do_read(struct bufferevent_ssl *bev_ssl, int n_to_read) { + /* Requires lock */ + struct bufferevent *bev = &bev_ssl->bev.bev; + struct evbuffer *input = bev->input; + int r, n, i, n_used = 0, atmost; + struct evbuffer_iovec space[2]; + int result = 0; + + if (bev_ssl->bev.read_suspended) + return 0; + + atmost = bufferevent_get_read_max_(&bev_ssl->bev); + if (n_to_read > atmost) + n_to_read = atmost; + + n = evbuffer_reserve_space(input, n_to_read, space, 2); + if (n < 0) + return OP_ERR; + + for (i=0; ibev.read_suspended) + break; + bev_ssl->ssl_ops->clear_error(); + r = bev_ssl->ssl_ops->read(bev_ssl->ssl, space[i].iov_base, space[i].iov_len); + if (r>0) { + result |= OP_MADE_PROGRESS; + if (bev_ssl->read_blocked_on_write) + if (clear_rbow(bev_ssl) < 0) + return OP_ERR | result; + ++n_used; + space[i].iov_len = r; + bev_ssl->ssl_ops->decrement_buckets(bev_ssl); + } else { + int err = bev_ssl->ssl_ops->get_error(bev_ssl->ssl, r); + bev_ssl->ssl_ops->print_err(err); + if (bev_ssl->ssl_ops->err_is_want_read(err)) { + /* Can't read until underlying has more data. */ + if (bev_ssl->read_blocked_on_write) + if (clear_rbow(bev_ssl) < 0) + return OP_ERR | result; + } else if(bev_ssl->ssl_ops->err_is_want_write(err)) { + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->read_blocked_on_write) + if (set_rbow(bev_ssl) < 0) + return OP_ERR | result; + } else { + bev_ssl->ssl_ops->conn_closed(bev_ssl, BEV_EVENT_READING, err, r); + } + result |= OP_BLOCKED; + break; /* out of the loop */ + } + } + + if (n_used) { + evbuffer_commit_space(input, space, n_used); + if (bev_ssl->underlying) + BEV_RESET_GENERIC_READ_TIMEOUT(bev); + } + + return result; +} + +/* Return a bitmask of OP_MADE_PROGRESS (if we wrote anything); OP_BLOCKED (if + we're now blocked); and OP_ERR (if an error occurred). */ +static int +do_write(struct bufferevent_ssl *bev_ssl, int atmost) +{ + int i, r, n, n_written = 0; + struct bufferevent *bev = &bev_ssl->bev.bev; + struct evbuffer *output = bev->output; + struct evbuffer_iovec space[8]; + int result = 0; + + if (bev_ssl->last_write > 0) + atmost = bev_ssl->last_write; + else + atmost = bufferevent_get_write_max_(&bev_ssl->bev); + + n = evbuffer_peek(output, atmost, NULL, space, 8); + if (n < 0) + return OP_ERR | result; + + if (n > 8) + n = 8; + for (i=0; i < n; ++i) { + if (bev_ssl->bev.write_suspended) + break; + + /* SSL_write will (reasonably) return 0 if we tell it to + send 0 data. Skip this case so we don't interpret the + result as an error */ + if (space[i].iov_len == 0) + continue; + + bev_ssl->ssl_ops->clear_error(); + r = bev_ssl->ssl_ops->write(bev_ssl->ssl, space[i].iov_base, + space[i].iov_len); + if (r > 0) { + result |= OP_MADE_PROGRESS; + if (bev_ssl->write_blocked_on_read) + if (clear_wbor(bev_ssl) < 0) + return OP_ERR | result; + n_written += r; + bev_ssl->last_write = -1; + bev_ssl->ssl_ops->decrement_buckets(bev_ssl); + } else { + int err = bev_ssl->ssl_ops->get_error(bev_ssl->ssl, r); + bev_ssl->ssl_ops->print_err(err); + if (bev_ssl->ssl_ops->err_is_want_write(err)) { + /* Can't read until underlying has more data. */ + if (bev_ssl->write_blocked_on_read) + if (clear_wbor(bev_ssl) < 0) + return OP_ERR | result; + bev_ssl->last_write = space[i].iov_len; + } else if (bev_ssl->ssl_ops->err_is_want_read(err)) { + /* This read operation requires a write, and the + * underlying is full */ + if (!bev_ssl->write_blocked_on_read) + if (set_wbor(bev_ssl) < 0) + return OP_ERR | result; + bev_ssl->last_write = space[i].iov_len; + } else { + bev_ssl->ssl_ops->conn_closed(bev_ssl, BEV_EVENT_WRITING, err, r); + bev_ssl->last_write = -1; + } + result |= OP_BLOCKED; + break; + } + } + if (n_written) { + evbuffer_drain(output, n_written); + if (bev_ssl->underlying) + BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); + + bufferevent_trigger_nolock_(bev, EV_WRITE, BEV_OPT_DEFER_CALLBACKS); + } + return result; +} + +#define WRITE_FRAME 15000 + +#define READ_DEFAULT 4096 + +/* Try to figure out how many bytes to read; return 0 if we shouldn't be + * reading. */ +static int +bytes_to_read(struct bufferevent_ssl *bev) +{ + struct evbuffer *input = bev->bev.bev.input; + struct event_watermark *wm = &bev->bev.bev.wm_read; + int result = READ_DEFAULT; + ev_ssize_t limit; + /* XXX 99% of this is generic code that nearly all bufferevents will + * want. */ + + if (bev->write_blocked_on_read) { + return 0; + } + + if (! (bev->bev.bev.enabled & EV_READ)) { + return 0; + } + + if (bev->bev.read_suspended) { + return 0; + } + + if (wm->high) { + if (evbuffer_get_length(input) >= wm->high) { + return 0; + } + + result = wm->high - evbuffer_get_length(input); + } else { + result = READ_DEFAULT; + } + + /* Respect the rate limit */ + limit = bufferevent_get_read_max_(&bev->bev); + if (result > limit) { + result = limit; + } + + return result; +} + + +/* Things look readable. If write is blocked on read, write till it isn't. + * Read from the underlying buffer until we block or we hit our high-water + * mark. + */ +static void +consider_reading(struct bufferevent_ssl *bev_ssl) +{ + int r; + int n_to_read; + int all_result_flags = 0; + + while (bev_ssl->write_blocked_on_read) { + r = do_write(bev_ssl, WRITE_FRAME); + if (r & (OP_BLOCKED|OP_ERR)) + break; + } + if (bev_ssl->write_blocked_on_read) + return; + + n_to_read = bytes_to_read(bev_ssl); + + while (n_to_read) { + r = do_read(bev_ssl, n_to_read); + all_result_flags |= r; + + if (r & (OP_BLOCKED|OP_ERR)) + break; + + if (bev_ssl->bev.read_suspended) + break; + + /* Read all pending data. This won't hit the network + * again, and will (most importantly) put us in a state + * where we don't need to read anything else until the + * socket is readable again. It'll potentially make us + * overrun our read high-watermark (somewhat + * regrettable). The damage to the rate-limit has + * already been done, since OpenSSL went and read a + * whole SSL record anyway. */ + n_to_read = bev_ssl->ssl_ops->pending(bev_ssl->ssl); + + /* XXX This if statement is actually a bad bug, added to avoid + * XXX a worse bug. + * + * The bad bug: It can potentially cause resource unfairness + * by reading too much data from the underlying bufferevent; + * it can potentially cause read looping if the underlying + * bufferevent is a bufferevent_pair and deferred callbacks + * aren't used. + * + * The worse bug: If we didn't do this, then we would + * potentially not read any more from bev_ssl->underlying + * until more data arrived there, which could lead to us + * waiting forever. + */ + if (!n_to_read && bev_ssl->underlying) + n_to_read = bytes_to_read(bev_ssl); + } + + if (all_result_flags & OP_MADE_PROGRESS) { + struct bufferevent *bev = &bev_ssl->bev.bev; + + bufferevent_trigger_nolock_(bev, EV_READ, 0); + } + + if (!bev_ssl->underlying) { + /* Should be redundant, but let's avoid busy-looping */ + if (bev_ssl->bev.read_suspended || + !(bev_ssl->bev.bev.enabled & EV_READ)) { + event_del(&bev_ssl->bev.bev.ev_read); + } + } +} + +static void +consider_writing(struct bufferevent_ssl *bev_ssl) +{ + int r; + struct evbuffer *output = bev_ssl->bev.bev.output; + struct evbuffer *target = NULL; + struct event_watermark *wm = NULL; + + while (bev_ssl->read_blocked_on_write) { + r = do_read(bev_ssl, 1024); /* XXXX 1024 is a hack */ + if (r & OP_MADE_PROGRESS) { + struct bufferevent *bev = &bev_ssl->bev.bev; + + bufferevent_trigger_nolock_(bev, EV_READ, 0); + } + if (r & (OP_ERR|OP_BLOCKED)) + break; + } + if (bev_ssl->read_blocked_on_write) + return; + if (bev_ssl->underlying) { + target = bev_ssl->underlying->output; + wm = &bev_ssl->underlying->wm_write; + } + while ((bev_ssl->bev.bev.enabled & EV_WRITE) && + (! bev_ssl->bev.write_suspended) && + evbuffer_get_length(output) && + (!target || (! wm->high || evbuffer_get_length(target) < wm->high))) { + int n_to_write; + if (wm && wm->high) + n_to_write = wm->high - evbuffer_get_length(target); + else + n_to_write = WRITE_FRAME; + r = do_write(bev_ssl, n_to_write); + if (r & (OP_BLOCKED|OP_ERR)) + break; + } + + if (!bev_ssl->underlying) { + if (evbuffer_get_length(output) == 0) { + event_del(&bev_ssl->bev.bev.ev_write); + } else if (bev_ssl->bev.write_suspended || + !(bev_ssl->bev.bev.enabled & EV_WRITE)) { + /* Should be redundant, but let's avoid busy-looping */ + event_del(&bev_ssl->bev.bev.ev_write); + } + } +} + +static void +be_ssl_readcb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_ssl *bev_ssl = ctx; + consider_reading(bev_ssl); +} + +static void +be_ssl_writecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_ssl *bev_ssl = ctx; + consider_writing(bev_ssl); +} + +static void +be_ssl_eventcb(struct bufferevent *bev_base, short what, void *ctx) +{ + struct bufferevent_ssl *bev_ssl = ctx; + int event = 0; + + if (what & BEV_EVENT_EOF) { + if (bev_ssl->allow_dirty_shutdown) + event = BEV_EVENT_EOF; + else + event = BEV_EVENT_ERROR; + } else if (what & BEV_EVENT_TIMEOUT) { + /* We sure didn't set this. Propagate it to the user. */ + event = what; + } else if (what & BEV_EVENT_ERROR) { + /* An error occurred on the connection. Propagate it to the user. */ + event = what; + } else if (what & BEV_EVENT_CONNECTED) { + /* Ignore it. We're saying SSL_connect() already, which will + eat it. */ + } + if (event) + bufferevent_run_eventcb_(&bev_ssl->bev.bev, event, 0); +} + +static void +be_ssl_readeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_ssl *bev_ssl = ptr; + bufferevent_incref_and_lock_(&bev_ssl->bev.bev); + if (what == EV_TIMEOUT) { + bufferevent_run_eventcb_(&bev_ssl->bev.bev, + BEV_EVENT_TIMEOUT|BEV_EVENT_READING, 0); + } else { + consider_reading(bev_ssl); + } + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static void +be_ssl_writeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_ssl *bev_ssl = ptr; + bufferevent_incref_and_lock_(&bev_ssl->bev.bev); + if (what == EV_TIMEOUT) { + bufferevent_run_eventcb_(&bev_ssl->bev.bev, + BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING, 0); + } else { + consider_writing(bev_ssl); + } + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static evutil_socket_t +be_ssl_auto_fd(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd) +{ + if (!bev_ssl->underlying) { + struct bufferevent *bev = &bev_ssl->bev.bev; + if (event_initialized(&bev->ev_read) && fd < 0) { + fd = event_get_fd(&bev->ev_read); + } + } + return fd; +} + +static int +set_open_callbacks(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_ssl_readcb, be_ssl_writecb, be_ssl_eventcb, + bev_ssl); + return 0; + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + int rpending=0, wpending=0, r1=0, r2=0; + + if (event_initialized(&bev->ev_read)) { + rpending = event_pending(&bev->ev_read, EV_READ, NULL); + wpending = event_pending(&bev->ev_write, EV_WRITE, NULL); + + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + + event_assign(&bev->ev_read, bev->ev_base, fd, + EV_READ|EV_PERSIST|EV_FINALIZE, + be_ssl_readeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST|EV_FINALIZE, + be_ssl_writeeventcb, bev_ssl); + + if (rpending) + r1 = bufferevent_add_event_(&bev->ev_read, &bev->timeout_read); + if (wpending) + r2 = bufferevent_add_event_(&bev->ev_write, &bev->timeout_write); + + return (r1<0 || r2<0) ? -1 : 0; + } +} + +static int +do_handshake(struct bufferevent_ssl *bev_ssl) +{ + int r; + + switch (bev_ssl->state) { + default: + case BUFFEREVENT_SSL_OPEN: + EVUTIL_ASSERT(0); + return -1; + case BUFFEREVENT_SSL_CONNECTING: + case BUFFEREVENT_SSL_ACCEPTING: + bev_ssl->ssl_ops->clear_error(); + r = bev_ssl->ssl_ops->handshake(bev_ssl->ssl); + break; + } + bev_ssl->ssl_ops->decrement_buckets(bev_ssl); + + if (bev_ssl->ssl_ops->err_is_ok(r)) { + evutil_socket_t fd = event_get_fd(&bev_ssl->bev.bev.ev_read); + /* We're done! */ + bev_ssl->state = BUFFEREVENT_SSL_OPEN; + set_open_callbacks(bev_ssl, fd); /* XXXX handle failure */ + /* Call do_read and do_write as needed */ + bufferevent_enable(&bev_ssl->bev.bev, bev_ssl->bev.bev.enabled); + bufferevent_run_eventcb_(&bev_ssl->bev.bev, + BEV_EVENT_CONNECTED, 0); + return 1; + } else { + int err = bev_ssl->ssl_ops->get_error(bev_ssl->ssl, r); + bev_ssl->ssl_ops->print_err(err); + if (bev_ssl->ssl_ops->err_is_want_write(err)) { + bufferevent_ssl_stop_reading(bev_ssl); + return start_writing(bev_ssl); + } else if (bev_ssl->ssl_ops->err_is_want_read(err)) { + bufferevent_ssl_stop_writing(bev_ssl); + return start_reading(bev_ssl); + } else { + bev_ssl->ssl_ops->conn_closed(bev_ssl, BEV_EVENT_READING, err, r); + return -1; + } + } +} + +static void +be_ssl_handshakecb(struct bufferevent *bev_base, void *ctx) +{ + struct bufferevent_ssl *bev_ssl = ctx; + do_handshake(bev_ssl);/* XXX handle failure */ +} + +static void +be_ssl_handshakeeventcb(evutil_socket_t fd, short what, void *ptr) +{ + struct bufferevent_ssl *bev_ssl = ptr; + + bufferevent_incref_and_lock_(&bev_ssl->bev.bev); + if (what & EV_TIMEOUT) { + bufferevent_run_eventcb_(&bev_ssl->bev.bev, BEV_EVENT_TIMEOUT, 0); + } else + do_handshake(bev_ssl);/* XXX handle failure */ + bufferevent_decref_and_unlock_(&bev_ssl->bev.bev); +} + +static int +set_handshake_callbacks(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd) +{ + if (bev_ssl->underlying) { + bufferevent_setcb(bev_ssl->underlying, + be_ssl_handshakecb, be_ssl_handshakecb, + be_ssl_eventcb, + bev_ssl); + + if (fd < 0) + return 0; + + if (bufferevent_setfd(bev_ssl->underlying, fd)) + return 1; + + return do_handshake(bev_ssl); + } else { + struct bufferevent *bev = &bev_ssl->bev.bev; + + if (event_initialized(&bev->ev_read)) { + event_del(&bev->ev_read); + event_del(&bev->ev_write); + } + + event_assign(&bev->ev_read, bev->ev_base, fd, + EV_READ|EV_PERSIST|EV_FINALIZE, + be_ssl_handshakeeventcb, bev_ssl); + event_assign(&bev->ev_write, bev->ev_base, fd, + EV_WRITE|EV_PERSIST|EV_FINALIZE, + be_ssl_handshakeeventcb, bev_ssl); + if (fd >= 0) + bufferevent_enable(bev, bev->enabled); + return 0; + } +} + +int +bufferevent_ssl_renegotiate_impl(struct bufferevent *bev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + if (!bev_ssl) + return -1; + if (bev_ssl->ssl_ops->renegotiate(bev_ssl->ssl) < 0) + return -1; + bev_ssl->state = BUFFEREVENT_SSL_CONNECTING; + if (set_handshake_callbacks(bev_ssl, be_ssl_auto_fd(bev_ssl, -1)) < 0) + return -1; + if (!bev_ssl->underlying) + return do_handshake(bev_ssl); + return 0; +} + +static void +be_ssl_outbuf_cb(struct evbuffer *buf, + const struct evbuffer_cb_info *cbinfo, void *arg) +{ + struct bufferevent_ssl *bev_ssl = arg; + int r = 0; + /* XXX need to hold a reference here. */ + + if (cbinfo->n_added && bev_ssl->state == BUFFEREVENT_SSL_OPEN) { + if (cbinfo->orig_size == 0) + r = bufferevent_add_event_(&bev_ssl->bev.bev.ev_write, + &bev_ssl->bev.bev.timeout_write); + + if (bev_ssl->underlying) + consider_writing(bev_ssl); + } + /* XXX Handle r < 0 */ + (void)r; +} + + +static int +be_ssl_enable(struct bufferevent *bev, short events) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + int r1 = 0, r2 = 0; + + if (events & EV_READ) + r1 = start_reading(bev_ssl); + if (events & EV_WRITE) + r2 = start_writing(bev_ssl); + + if (bev_ssl->underlying) { + if (events & EV_READ) + BEV_RESET_GENERIC_READ_TIMEOUT(bev); + if (events & EV_WRITE) + BEV_RESET_GENERIC_WRITE_TIMEOUT(bev); + + if (events & EV_READ) + consider_reading(bev_ssl); + if (events & EV_WRITE) + consider_writing(bev_ssl); + } + return (r1 < 0 || r2 < 0) ? -1 : 0; +} + +static int +be_ssl_disable(struct bufferevent *bev, short events) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + + if (events & EV_READ) + bufferevent_ssl_stop_reading(bev_ssl); + if (events & EV_WRITE) + bufferevent_ssl_stop_writing(bev_ssl); + + if (bev_ssl->underlying) { + if (events & EV_READ) + BEV_DEL_GENERIC_READ_TIMEOUT(bev); + if (events & EV_WRITE) + BEV_DEL_GENERIC_WRITE_TIMEOUT(bev); + } + return 0; +} + +static void +be_ssl_unlink(struct bufferevent *bev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { + if (bev_ssl->underlying) { + if (BEV_UPCAST(bev_ssl->underlying)->refcnt < 2) { + event_warnx("BEV_OPT_CLOSE_ON_FREE set on an " + "bufferevent with too few references"); + } else { + bufferevent_free(bev_ssl->underlying); + /* We still have a reference to it, via our + * BIO. So we don't drop this. */ + // bev_ssl->underlying = NULL; + } + } + } else { + if (bev_ssl->underlying) { + if (bev_ssl->underlying->errorcb == be_ssl_eventcb) + bufferevent_setcb(bev_ssl->underlying, + NULL,NULL,NULL,NULL); + bufferevent_unsuspend_read_(bev_ssl->underlying, + BEV_SUSPEND_FILT_READ); + } + } +} + +static void +be_ssl_destruct(struct bufferevent *bev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + + if (bev_ssl->bev.options & BEV_OPT_CLOSE_ON_FREE) { + if (! bev_ssl->underlying) { + evutil_socket_t fd = bev_ssl->ssl_ops->get_fd(bev_ssl); + if (fd >= 0) + evutil_closesocket(fd); + } + } + bev_ssl->ssl_ops->free(bev_ssl->ssl, bev_ssl->bev.options); +} + +static int +be_ssl_adj_timeouts(struct bufferevent *bev) +{ + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + + if (bev_ssl->underlying) { + return bufferevent_generic_adj_timeouts_(bev); + } else { + return bufferevent_generic_adj_existing_timeouts_(bev); + } +} + +static int +be_ssl_flush(struct bufferevent *bufev, + short iotype, enum bufferevent_flush_mode mode) +{ + /* XXXX Implement this. */ + return 0; +} + +static int +be_ssl_set_fd(struct bufferevent_ssl *bev_ssl, + enum bufferevent_ssl_state state, evutil_socket_t fd) +{ + bev_ssl->state = state; + + switch (state) { + case BUFFEREVENT_SSL_ACCEPTING: + if (!bev_ssl->ssl_ops->clear(bev_ssl->ssl)) + return -1; + bev_ssl->ssl_ops->set_accept_state(bev_ssl->ssl); + if (set_handshake_callbacks(bev_ssl, fd) < 0) + return -1; + break; + case BUFFEREVENT_SSL_CONNECTING: + if (!bev_ssl->ssl_ops->clear(bev_ssl->ssl)) + return -1; + bev_ssl->ssl_ops->set_connect_state(bev_ssl->ssl); + if (set_handshake_callbacks(bev_ssl, fd) < 0) + return -1; + break; + case BUFFEREVENT_SSL_OPEN: + if (set_open_callbacks(bev_ssl, fd) < 0) + return -1; + break; + default: + return -1; + } + + return 0; +} + +static int +be_ssl_ctrl(struct bufferevent *bev, + enum bufferevent_ctrl_op op, union bufferevent_ctrl_data *data) +{ + int ret = 0; + struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bev); + switch (op) { + case BEV_CTRL_SET_FD: + if ((ret = bev_ssl->ssl_ops->bio_set_fd(bev_ssl, data->fd)) != 0) + return ret; + return be_ssl_set_fd(bev_ssl, bev_ssl->old_state, data->fd); + case BEV_CTRL_GET_FD: + if (bev_ssl->underlying) { + data->fd = event_get_fd(&bev_ssl->underlying->ev_read); + } else { + data->fd = event_get_fd(&bev->ev_read); + } + return 0; + case BEV_CTRL_GET_UNDERLYING: + data->ptr = bev_ssl->underlying; + return 0; + case BEV_CTRL_CANCEL_ALL: + default: + return -1; + } +} + +struct bufferevent * +bufferevent_ssl_new_impl(struct event_base *base, + struct bufferevent *underlying, + evutil_socket_t fd, + void *ssl, + enum bufferevent_ssl_state state, + int options, + struct le_ssl_ops *ssl_ops) +{ + struct bufferevent_ssl *bev_ssl = NULL; + struct bufferevent_private *bev_p = NULL; + int tmp_options = options & ~BEV_OPT_THREADSAFE; + + /* Only one can be set. */ + if (underlying != NULL && fd >= 0) + goto err; + + if (!(bev_ssl = mm_calloc(1, sizeof(struct bufferevent_ssl)))) + goto err; + + bev_p = &bev_ssl->bev; + + if (bufferevent_init_common_(bev_p, base, + &bufferevent_ops_ssl, tmp_options) < 0) + goto err; + + bev_ssl->ssl_ops = ssl_ops; + + bev_ssl->ssl = bev_ssl->ssl_ops->init(ssl); + + bev_ssl->underlying = underlying; + + bev_ssl->outbuf_cb = evbuffer_add_cb(bev_p->bev.output, + be_ssl_outbuf_cb, bev_ssl); + + if (options & BEV_OPT_THREADSAFE) + bufferevent_enable_locking_(&bev_ssl->bev.bev, NULL); + + if (underlying) { + bufferevent_init_generic_timeout_cbs_(&bev_ssl->bev.bev); + bufferevent_incref_(underlying); + } + + bev_ssl->old_state = state; + bev_ssl->last_write = -1; + + bev_ssl->ssl_ops->init_bio_counts(bev_ssl); + + fd = be_ssl_auto_fd(bev_ssl, fd); + if (be_ssl_set_fd(bev_ssl, state, fd)) + goto err; + + if (underlying) { + bufferevent_setwatermark(underlying, EV_READ, 0, 0); + bufferevent_enable(underlying, EV_READ|EV_WRITE); + if (state == BUFFEREVENT_SSL_OPEN) + bufferevent_suspend_read_(underlying, + BEV_SUSPEND_FILT_READ); + } + + return &bev_ssl->bev.bev; +err: + if (bev_ssl) { + if (bev_ssl->ssl && (options & BEV_OPT_CLOSE_ON_FREE)) + bev_ssl->ssl_ops->free(bev_ssl->ssl, options); + bev_ssl->ssl = NULL; + bufferevent_free(&bev_ssl->bev.bev); + } else { + if (ssl && (options & BEV_OPT_CLOSE_ON_FREE)) + bev_ssl->ssl_ops->free_raw(bev_ssl->ssl); + } + return NULL; +} + +int +bufferevent_ssl_get_allow_dirty_shutdown(struct bufferevent *bev) +{ + int allow_dirty_shutdown = -1; + struct bufferevent_ssl *bev_ssl; + BEV_LOCK(bev); + bev_ssl = bufferevent_ssl_upcast(bev); + if (bev_ssl) + allow_dirty_shutdown = bev_ssl->allow_dirty_shutdown; + BEV_UNLOCK(bev); + return allow_dirty_shutdown; +} + +void +bufferevent_ssl_set_allow_dirty_shutdown(struct bufferevent *bev, + int allow_dirty_shutdown) +{ + struct bufferevent_ssl *bev_ssl; + BEV_LOCK(bev); + bev_ssl = bufferevent_ssl_upcast(bev); + if (bev_ssl) + bev_ssl->allow_dirty_shutdown = !!allow_dirty_shutdown; + BEV_UNLOCK(bev); +} + +unsigned long +bufferevent_get_ssl_error(struct bufferevent *bev) +{ + unsigned long err = 0; + struct bufferevent_ssl *bev_ssl; + BEV_LOCK(bev); + bev_ssl = bufferevent_ssl_upcast(bev); + if (bev_ssl && bev_ssl->n_errors) { + err = bev_ssl->errors[--bev_ssl->n_errors]; + } + BEV_UNLOCK(bev); + return err; +} diff --git a/cmake/FindMbedTLS.cmake b/cmake/FindMbedTLS.cmake new file mode 100644 index 00000000..c430269d --- /dev/null +++ b/cmake/FindMbedTLS.cmake @@ -0,0 +1,159 @@ +# Copyright 2017-2019 AVSystem +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#.rst: +# FindMbedTLS +# ----------- +# +# Find the mbedTLS encryption library. +# +# Imported Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following :prop_tgt:`IMPORTED` targets: +# +# ``mbedtls`` +# The mbedTLS ``mbedtls`` library, if found. +# ``mbedcrypto`` +# The mbedtls ``crypto`` library, if found. +# ``mbedx509`` +# The mbedtls ``x509`` library, if found. +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# This module will set the following variables in your project: +# +# ``MBEDTLS_FOUND`` +# System has the mbedTLS library. +# ``MBEDTLS_INCLUDE_DIR`` +# The mbedTLS include directory. +# ``MBEDTLS_LIBRARY`` +# The mbedTLS SSL library. +# ``MBEDTLS_CRYPTO_LIBRARY`` +# The mbedTLS crypto library. +# ``MBEDTLS_X509_LIBRARY`` +# The mbedTLS x509 library. +# ``MBEDTLS_LIBRARIES`` +# All mbedTLS libraries. +# ``MBEDTLS_VERSION`` +# This is set to ``$major.$minor.$patch``. +# ``MBEDTLS_VERSION_MAJOR`` +# Set to major mbedTLS version number. +# ``MBEDTLS_VERSION_MINOR`` +# Set to minor mbedTLS version number. +# ``MBEDTLS_VERSION_PATCH`` +# Set to patch mbedTLS version number. +# +# Hints +# ^^^^^ +# +# Set ``MBEDTLS_ROOT_DIR`` to the root directory of an mbedTLS installation. +# Set ``MBEDTLS_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries. + +if(MBEDTLS_ROOT_DIR) + # Disable re-rooting paths in find_path/find_library. + # This assumes MBEDTLS_ROOT_DIR is an absolute path. + set(_EXTRA_FIND_ARGS "NO_CMAKE_FIND_ROOT_PATH") +endif() + +find_path(MBEDTLS_INCLUDE_DIR + NAMES mbedtls/ssl.h + PATH_SUFFIXES include + HINTS ${MBEDTLS_ROOT_DIR} + ${_EXTRA_FIND_ARGS}) + +# based on https://github.com/ARMmbed/mbedtls/issues/298 +if(MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_STRING_LINE REGEX "^#define MBEDTLS_VERSION_STRING[ \\t\\n\\r]+\"[^\"]*\"$") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_MAJOR_LINE REGEX "^#define MBEDTLS_VERSION_MAJOR[ \\t\\n\\r]+[0-9]+$") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_MINOR_LINE REGEX "^#define MBEDTLS_VERSION_MINOR[ \\t\\n\\r]+[0-9]+$") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" VERSION_PATCH_LINE REGEX "^#define MBEDTLS_VERSION_PATCH[ \\t\\n\\r]+[0-9]+$") + + string(REGEX REPLACE "^#define MBEDTLS_VERSION_STRING[ \\t\\n\\r]+\"([^\"]*)\"$" "\\1" MBEDTLS_VERSION "${VERSION_STRING_LINE}") + string(REGEX REPLACE "^#define MBEDTLS_VERSION_MAJOR[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_MAJOR "${VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define MBEDTLS_VERSION_MINOR[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_MINOR "${VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define MBEDTLS_VERSION_PATCH[ \\t\\n\\r]+([0-9]+)$" "\\1" MBEDTLS_VERSION_PATCH "${VERSION_PATCH_LINE}") +endif() + + +if(MBEDTLS_USE_STATIC_LIBS) + set(_MBEDTLS_LIB_NAME libmbedtls.a) + set(_MBEDTLS_CRYPTO_LIB_NAME libmbedcrypto.a) + set(_MBEDTLS_X509_LIB_NAME libmbedx509.a) +else() + set(_MBEDTLS_LIB_NAME mbedtls) + set(_MBEDTLS_CRYPTO_LIB_NAME mbedcrypto) + set(_MBEDTLS_X509_LIB_NAME mbedx509) +endif() + +find_library(MBEDTLS_LIBRARY + NAMES ${_MBEDTLS_LIB_NAME} + PATH_SUFFIXES lib + HINTS ${MBEDTLS_ROOT_DIR} + ${_EXTRA_FIND_ARGS}) + +find_library(MBEDTLS_CRYPTO_LIBRARY + NAMES ${_MBEDTLS_CRYPTO_LIB_NAME} + PATH_SUFFIXES lib + HINTS ${MBEDTLS_ROOT_DIR} + ${_EXTRA_FIND_ARGS}) + +find_library(MBEDTLS_X509_LIBRARY + NAMES ${_MBEDTLS_X509_LIB_NAME} + PATH_SUFFIXES lib + HINTS ${MBEDTLS_ROOT_DIR} + ${_EXTRA_FIND_ARGS}) + +set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} ${MBEDTLS_X509_LIBRARY}) + +if(MBEDTLS_INCLUDE_DIR) + set(MBEDTLS_FOUND TRUE) +endif() + + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MbedTLS + FOUND_VAR MBEDTLS_FOUND + REQUIRED_VARS + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARY + MBEDTLS_CRYPTO_LIBRARY + MBEDTLS_X509_LIBRARY + MBEDTLS_LIBRARIES + MBEDTLS_VERSION + VERSION_VAR MBEDTLS_VERSION) + + +if(NOT TARGET mbedtls) + add_library(mbedtls UNKNOWN IMPORTED) + set_target_properties(mbedtls PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_LIBRARY}") +endif() + +if(NOT TARGET mbedcrypto) + add_library(mbedcrypto UNKNOWN IMPORTED) + set_target_properties(mbedcrypto PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_CRYPTO_LIBRARY}") +endif() + +if(NOT TARGET mbedx509) + add_library(mbedx509 UNKNOWN IMPORTED) + set_target_properties(mbedx509 PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_X509_LIBRARY}") +endif() diff --git a/configure.ac b/configure.ac index 7caa85a4..2599c225 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,9 @@ AC_ARG_ENABLE(malloc-replacement, AC_ARG_ENABLE(openssl, AS_HELP_STRING(--disable-openssl, disable support for openssl encryption), [], [enable_openssl=yes]) +AC_ARG_ENABLE(mbedtls, + AS_HELP_STRING(--disable-mbedtls, disable support for mbedtls encryption), + [], [enable_mbedtls=yes]) AC_ARG_ENABLE(debug-mode, AS_HELP_STRING(--disable-debug-mode, disable support for running in debug mode), [], [enable_debug_mode=yes]) @@ -179,6 +182,7 @@ AC_SUBST(OPENSSL_LIBADD) AC_SYS_LARGEFILE LIBEVENT_OPENSSL +LIBEVENT_MBEDTLS dnl Checks for header files. AC_CHECK_HEADERS([ \ @@ -804,7 +808,7 @@ if test x$enable_debug_mode = xno; then [Define if libevent should build without support for a debug mode]) fi -dnl check if we should enable verbose debugging +dnl check if we should enable verbose debugging if test x$enable_verbose_debug = xyes; then CFLAGS="$CFLAGS -DUSE_DEBUG" fi @@ -812,6 +816,9 @@ fi dnl check if we have and should use OpenSSL AM_CONDITIONAL(OPENSSL, [test "$enable_openssl" != "no" && test "$have_openssl" = "yes"]) +# check if we have and should use mbedtls +AM_CONDITIONAL(MBEDTLS, [test "$enable_mbedtls" != "no" && test "$have_mbedtls" = "yes"]) + dnl enable some warnings by default AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"],[],[-Werror]) @@ -961,5 +968,5 @@ DX_INIT_DOXYGEN([libevent], [${top_srcdir}/Doxyfile], [doxygen]) AM_CONDITIONAL([ENABLE_DOXYGEN], [test "$DX_FLAG_doc" = "1"]) AM_CONDITIONAL([ENABLE_DOXYGEN_MAN], [test "$DX_FLAG_man" = "1"]) -AC_CONFIG_FILES( [libevent.pc libevent_openssl.pc libevent_pthreads.pc libevent_core.pc libevent_extra.pc] ) +AC_CONFIG_FILES( [libevent.pc libevent_mbedtls.pc libevent_openssl.pc libevent_pthreads.pc libevent_core.pc libevent_extra.pc] ) AC_OUTPUT(Makefile) diff --git a/event-config.h.cmake b/event-config.h.cmake index b8f59fef..1e7adb1c 100644 --- a/event-config.h.cmake +++ b/event-config.h.cmake @@ -214,6 +214,9 @@ /* Define if the system has openssl */ #cmakedefine EVENT__HAVE_OPENSSL 1 +/* Define if the system has mbedtls */ +#cmakedefine EVENT__HAVE_MBEDTLS 1 + /* Define to 1 if you have the `pipe' function. */ #cmakedefine EVENT__HAVE_PIPE 1 diff --git a/include/event2/bufferevent_ssl.h b/include/event2/bufferevent_ssl.h index 63f3a90b..69fb40b7 100644 --- a/include/event2/bufferevent_ssl.h +++ b/include/event2/bufferevent_ssl.h @@ -39,9 +39,6 @@ extern "C" { #endif -/* This is what openssl's SSL objects are underneath. */ -struct ssl_st; - /** The state of an SSL object to be used when creating a new SSL bufferevent. @@ -53,6 +50,9 @@ enum bufferevent_ssl_state { }; #if defined(EVENT__HAVE_OPENSSL) || defined(EVENT_IN_DOXYGEN_) +/* This is what openssl's SSL objects are underneath. */ +struct ssl_st; + /** Create a new SSL bufferevent to send its data over another bufferevent. @@ -125,6 +125,81 @@ int bufferevent_ssl_renegotiate(struct bufferevent *bev); EVENT2_EXPORT_SYMBOL unsigned long bufferevent_get_openssl_error(struct bufferevent *bev); +#endif +#if defined(EVENT__HAVE_MBEDTLS) || defined(EVENT_IN_DOXYGEN_) +struct mbedtls_ssl_context; +/** + Create a new SSL bufferevent to send its data over another bufferevent. + + @param base An event_base to use to detect reading and writing. It + must also be the base for the underlying bufferevent. + @param underlying A socket to use for this SSL + @param ssl A SSL* object from openssl. + @param state The current state of the SSL connection + @param options One or more bufferevent_options + @return A new bufferevent on success, or NULL on failure +*/ +EVENT2_EXPORT_SYMBOL +struct bufferevent * +bufferevent_mbedtls_filter_new(struct event_base *base, + struct bufferevent *underlying, + struct mbedtls_ssl_context *ssl, + enum bufferevent_ssl_state state, + int options); + +/** + Create a new SSL bufferevent to send its data over an SSL * on a socket. + + @param base An event_base to use to detect reading and writing + @param fd A socket to use for this SSL + @param ssl A SSL* object from mbedtls. + @param state The current state of the SSL connection + @param options One or more bufferevent_options + @return A new bufferevent on success, or NULL on failure. +*/ +EVENT2_EXPORT_SYMBOL +struct bufferevent * +bufferevent_mbedtls_socket_new(struct event_base *base, + evutil_socket_t fd, + struct mbedtls_ssl_context *ssl, + enum bufferevent_ssl_state state, + int options); + +/** Control how to report dirty SSL shutdowns. + + If the peer (or the network, or an attacker) closes the TCP + connection before closing the SSL channel, and the protocol is SSL >= v3, + this is a "dirty" shutdown. If allow_dirty_shutdown is 0 (default), + this is reported as BEV_EVENT_ERROR. + + If instead allow_dirty_shutdown=1, a dirty shutdown is reported as + BEV_EVENT_EOF. + + (Note that if the protocol is < SSLv3, you will always receive + BEV_EVENT_EOF, since SSL 2 and earlier cannot distinguish a secure + connection close from a dirty one. This is one reason (among many) + not to use SSL 2.) +*/ + +EVENT2_EXPORT_SYMBOL +int bufferevent_mbedtls_get_allow_dirty_shutdown(struct bufferevent *bev); +EVENT2_EXPORT_SYMBOL +void bufferevent_mbedtls_set_allow_dirty_shutdown(struct bufferevent *bev, + int allow_dirty_shutdown); + +/** Return the underlying mbedtls SSL * object for an SSL bufferevent. */ +EVENT2_EXPORT_SYMBOL +struct mbedtls_ssl_context * +bufferevent_mbedtls_get_ssl(struct bufferevent *bufev); + +/** Tells a bufferevent to begin SSL renegotiation. */ +EVENT2_EXPORT_SYMBOL +int bufferevent_mbedtls_renegotiate(struct bufferevent *bev); + +/** Return the most recent OpenSSL error reported on an SSL bufferevent. */ +EVENT2_EXPORT_SYMBOL +unsigned long bufferevent_get_mbedtls_error(struct bufferevent *bev); + #endif #ifdef __cplusplus diff --git a/include/event2/visibility.h b/include/event2/visibility.h index 006bbf06..2b07994a 100644 --- a/include/event2/visibility.h +++ b/include/event2/visibility.h @@ -33,7 +33,8 @@ defined(event_extra_shared_EXPORTS) || \ defined(event_core_shared_EXPORTS) || \ defined(event_pthreads_shared_EXPORTS) || \ - defined(event_openssl_shared_EXPORTS) + defined(event_openssl_shared_EXPORTS) || \ + defined(event_mbedtls_shared_EXPORTS) # if defined (__SUNPRO_C) && (__SUNPRO_C >= 0x550) # define EVENT2_EXPORT_SYMBOL __global diff --git a/libevent_mbedtls.pc.in b/libevent_mbedtls.pc.in new file mode 100644 index 00000000..822cb5da --- /dev/null +++ b/libevent_mbedtls.pc.in @@ -0,0 +1,16 @@ +#libevent pkg-config source file + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libevent_mbedtls +Description: libevent_mbedtls adds mbedtls-based TLS support to libevent +Version: @VERSION@ +Requires: libevent +Conflicts: +Libs: -L${libdir} -levent_mbedtls +Libs.private: @LIBS@ @MBEDTLS_LIBS@ +Cflags: -I${includedir} @MBEDTLS_INCS@ + diff --git a/m4/libevent_mbedtls.m4 b/m4/libevent_mbedtls.m4 new file mode 100644 index 00000000..4b903b9b --- /dev/null +++ b/m4/libevent_mbedtls.m4 @@ -0,0 +1,38 @@ +dnl ###################################################################### +dnl mbedtls support +AC_DEFUN([LIBEVENT_MBEDTLS], [ +AC_REQUIRE([NTP_PKG_CONFIG])dnl + +case "$enable_mbedtls" in + yes) + case "$have_mbedtls" in + yes) ;; + *) + save_LIBS="$LIBS" + LIBS="" + MBEDTLS_LIBS="" + # clear cache + unset ac_cv_search_mbedtls_ssl_init + AC_SEARCH_LIBS([mbedtls_ssl_init], [mbedtls], + [have_mbedtls=yes + MBEDTLS_LIBS="$LIBS -lmbedtls -lmbedcrypto -lmbedx509 $EV_LIB_GDI $EV_LIB_WS32"], + [have_mbedtls=no], + [-lmbedtls -lmbedcrypto -lmbedx509 $EV_LIB_GDI $EV_LIB_WS32]) + LIBS="$save_LIBS" + test "$have_mbedtls" = "yes" && break + esac + CPPFLAGS_SAVE=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $MBEDTLS_INCS" + AC_CHECK_HEADERS([mbedtls/ssl.h], [], [have_mbedtls=no]) + CPPFLAGS=$CPPFLAGS_SAVE + AC_SUBST(MBEDTLS_INCS) + AC_SUBST(MBEDTLS_LIBS) + case "$have_mbedtls" in + yes) AC_DEFINE(HAVE_MBEDTLS, 1, [Define if the system has mbedtls]) ;; + esac + ;; +esac + +# check if we have and should use mbedtls +AM_CONDITIONAL(MBEDTLS, [test "$enable_mbedtls" != "no" && test "$have_mbedtls" = "yes"]) +]) diff --git a/sample/include.am b/sample/include.am index 301787db..19d6a9a6 100644 --- a/sample/include.am +++ b/sample/include.am @@ -40,6 +40,13 @@ noinst_HEADERS += \ sample/openssl_hostname_validation.h endif +if MBEDTLS +SAMPLES += sample/ssl-client-mbedtls +sample_ssl_client_mbedtls_SOURCES = sample/ssl-client-mbedtls.c +sample_ssl_client_mbedtls_LDADD = libevent.la libevent_mbedtls.la $(MBEDTLS_LIBS) $(MBEDTLS_LIBADD) +sample_ssl_client_mbedtls_CPPFLAGS = $(AM_CPPFLAGS) $(MBEDTLS_INCS) +endif + if BUILD_SAMPLES noinst_PROGRAMS += $(SAMPLES) endif diff --git a/sample/ssl-client-mbedtls.c b/sample/ssl-client-mbedtls.c new file mode 100644 index 00000000..965913b2 --- /dev/null +++ b/sample/ssl-client-mbedtls.c @@ -0,0 +1,264 @@ +/* + * SSL client demonstration program + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This file is part of mbed TLS (https://tls.mbed.org) + */ + +#include "mbedtls/config.h" +#include "mbedtls/platform.h" + +#include "mbedtls/net_sockets.h" +#include "mbedtls/debug.h" +#include "mbedtls/ssl.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/error.h" +#include "mbedtls/certs.h" + +#include + +#include +#include +#include +#include +#include + +#define SERVER_PORT "443" +#define SERVER_NAME "amazon.com" +#define GET_REQUEST "GET / HTTP/1.0\r\n\r\n" + +#define DEBUG_LEVEL 1 + +static void +my_debug(void *ctx, int level, const char *file, int line, const char *str) +{ + ((void)level); + + mbedtls_fprintf((FILE *)ctx, "%s:%04d: %s", file, line, str); + fflush((FILE *)ctx); +} + +static void +writecb(struct bufferevent *bev, void *arg) +{ + fprintf(stderr, "writecb\n"); +} + +static void +readcb(struct bufferevent *bev, void *arg) +{ + char buf[1000]; + size_t r = 0; + int i; + for (i = 0; i < 10; ++i) { + r = bufferevent_read(bev, buf, 800); + fprintf(stderr, "readcb %zu\n\n", r); + if (r > 1) { + fwrite(buf, 1, r, stdout); + fwrite("\n", 1, r, stdout); + fflush(stdout); + } + } +} + +static void +eventcb(struct bufferevent *bev, short what, void *arg) +{ + fprintf(stderr, "\n---------------eventcb %d\n", what); + if (what & BEV_EVENT_CONNECTED) { + const char headers[] = "GET / HTTP/1.1\r\n" + "HOST: " SERVER_NAME "\r\n" + "User-Agent: curl/7.65.1\r\n" + "Connection: Keep-Alive\r\n" + "\r\n"; + bufferevent_write( + bev, headers, sizeof(headers) - 1); // without ending '\0' + // bufferevent_disable(bev, EV_WRITE); + fprintf(stderr, "write request completely\n"); + } else if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + fprintf(stderr, "closed\n"); + bufferevent_free(bev); + } +} + + +int +main(void) +{ + int ret; + mbedtls_net_context server_fd; + const char *pers = "ssl_client1"; + + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt cacert; + + struct event_base *evbase; + struct evdns_base *evdns; + struct bufferevent *bev; + struct bufferevent *bevf; + +#ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + int err; + + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); +#endif + + +#if defined(MBEDTLS_DEBUG_C) + mbedtls_debug_set_threshold(DEBUG_LEVEL); +#endif + + /* + * 0. Initialize the RNG and the session data + */ + mbedtls_net_init(&server_fd); + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_x509_crt_init(&cacert); + mbedtls_ctr_drbg_init(&ctr_drbg); + + mbedtls_printf("\n . Seeding the random number generator..."); + fflush(stdout); + + mbedtls_entropy_init(&entropy); + if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *)pers, strlen(pers))) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ctr_drbg_seed returned %d\n", ret); + goto exit; + } + + mbedtls_printf(" ok\n"); + + /* + * 0. Initialize certificates + */ + mbedtls_printf(" . Loading the CA root certificate ..."); + fflush(stdout); + + ret = mbedtls_x509_crt_parse(&cacert, + (const unsigned char *)mbedtls_test_cas_pem, mbedtls_test_cas_pem_len); + if (ret < 0) { + mbedtls_printf( + " failed\n ! mbedtls_x509_crt_parse returned -0x%x\n\n", -ret); + goto exit; + } + + mbedtls_printf(" ok (%d skipped)\n", ret); + + /* + * 1. Start the connection + */ + mbedtls_printf(" . Connecting to tcp/%s/%s...", SERVER_NAME, SERVER_PORT); + fflush(stdout); + + if ((ret = mbedtls_net_connect(&server_fd, SERVER_NAME, SERVER_PORT, + MBEDTLS_NET_PROTO_TCP)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_net_connect returned %d\n\n", ret); + goto exit; + } + + mbedtls_printf(" ok\n"); + + /* + * 2. Setup stuff + */ + mbedtls_printf(" . Setting up the SSL/TLS structure..."); + fflush(stdout); + + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + mbedtls_printf( + " failed\n ! mbedtls_ssl_config_defaults returned %d\n\n", ret); + goto exit; + } + + mbedtls_printf(" ok\n"); + + /* OPTIONAL is not optimal for security, + * but makes interop easier in this simplified example */ + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg); + mbedtls_ssl_conf_dbg(&conf, my_debug, stdout); + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0) { + mbedtls_printf(" failed\n ! mbedtls_ssl_setup returned %d\n\n", ret); + goto exit; + } + + if ((ret = mbedtls_ssl_set_hostname(&ssl, SERVER_NAME)) != 0) { + mbedtls_printf( + " failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret); + goto exit; + } + fflush(stdout); + + event_enable_debug_mode(); + evbase = event_base_new(); + evdns = evdns_base_new(evbase, 1); + evdns_base_set_option(evdns, "randomize-case:", "0"); + + evutil_make_socket_nonblocking(server_fd.fd); + + bev = bufferevent_socket_new(evbase, server_fd.fd, BEV_OPT_CLOSE_ON_FREE); + bevf = bufferevent_mbedtls_filter_new( + evbase, bev, &ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE); + bev = bevf; + bufferevent_setcb(bev, readcb, writecb, eventcb, NULL); + + bufferevent_enable(bev, EV_READ); + + + event_base_loop(evbase, 0); + event_base_free(evbase); + + +exit: + +#ifdef MBEDTLS_ERROR_C + if (ret != 0) { + char error_buf[100]; + mbedtls_strerror(ret, error_buf, 100); + mbedtls_printf("Last error was: %d - %s\n\n", ret, error_buf); + } +#endif + + mbedtls_net_free(&server_fd); + + mbedtls_x509_crt_free(&cacert); + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + +#if defined(_WIN32) + mbedtls_printf(" + Press Enter to exit this program.\n"); + fflush(stdout); + getchar(); +#endif + + return (ret); +} diff --git a/ssl-compat.h b/ssl-compat.h new file mode 100644 index 00000000..a19c457f --- /dev/null +++ b/ssl-compat.h @@ -0,0 +1,101 @@ +#ifndef SSL_COMPACT_H +#define SSL_COMPACT_H + +#include "event.h" +#include "bufferevent-internal.h" +#include "event2/bufferevent_ssl.h" +struct bufferevent_ssl; + +struct le_ssl_ops { + void *(*init)(void *ssl); + void (*free)(void *ssl, int flags); + void (*free_raw)(void *ssl); + int (*renegotiate)(void *ssl); + int (*write)(void *ssl, const unsigned char *buf, size_t len); + int (*read)(void *ssl, unsigned char *buf, size_t len); + size_t (*pending)(void *ssl); + int (*handshake)(void *ssl); + int (*get_error)(void *ssl, int ret); + void (*clear_error)(void); + int (*clear)(void *ssl); + void (*set_connect_state)(void *ssl); + void (*set_accept_state)(void *ssl); + int (*err_is_ok)(int err); + int (*err_is_want_read)(int err); + int (*err_is_want_write)(int err); + evutil_socket_t (*get_fd)(void *ssl); + int (*bio_set_fd)(struct bufferevent_ssl *ssl, evutil_socket_t fd); + void (*init_bio_counts)(struct bufferevent_ssl *bev); + void (*decrement_buckets)(struct bufferevent_ssl *bev); + void (*conn_closed)( + struct bufferevent_ssl *bev, int when, int errcode, int ret); + void (*print_err)(int err); +}; + +struct bio_data_counts { + unsigned long n_written; + unsigned long n_read; +}; + +struct bufferevent_ssl { + /* Shared fields with common bufferevent implementation code. + If we were set up with an underlying bufferevent, we use the + events here as timers only. If we have an SSL, then we use + the events as socket events. + */ + struct bufferevent_private bev; + /* An underlying bufferevent that we're directing our output to. + If it's NULL, then we're connected to an fd, not an evbuffer. */ + struct bufferevent *underlying; + /* The SSL context doing our encryption. */ + void *ssl; + /* The SSL operations doing on ssl. */ + struct le_ssl_ops *ssl_ops; + + /* A callback that's invoked when data arrives on our outbuf so we + know to write data to the SSL. */ + struct evbuffer_cb_entry *outbuf_cb; + + /* A count of how much data the bios have read/written total. Used + for rate-limiting. */ + struct bio_data_counts counts; + + /* If this value is greater than 0, then the last SSL_write blocked, + * and we need to try it again with this many bytes. */ + ev_ssize_t last_write; + +#define NUM_ERRORS 3 + ev_uint32_t errors[NUM_ERRORS]; + + /* When we next get available space, we should say "read" instead of + "write". This can happen if there's a renegotiation during a read + operation. */ + unsigned read_blocked_on_write : 1; + /* When we next get data, we should say "write" instead of "read". */ + unsigned write_blocked_on_read : 1; + /* Treat TCP close before SSL close on SSL >= v3 as clean EOF. */ + unsigned allow_dirty_shutdown : 1; + /* XXX */ + unsigned n_errors : 2; + + /* Are we currently connecting, accepting, or doing IO? */ + unsigned state : 2; + /* If we reset fd, we sould reset state too */ + unsigned old_state : 2; +}; + +struct bufferevent *bufferevent_ssl_new_impl(struct event_base *base, + struct bufferevent *underlying, evutil_socket_t fd, void *ssl, + enum bufferevent_ssl_state state, int options, struct le_ssl_ops *ssl_ops); +struct bufferevent_ssl *bufferevent_ssl_upcast(struct bufferevent *bev); +void bufferevent_ssl_put_error( + struct bufferevent_ssl *bev_ssl, unsigned long err); +void bufferevent_ssl_stop_reading(struct bufferevent_ssl *bev_ssl); +void bufferevent_ssl_stop_writing(struct bufferevent_ssl *bev_ssl); +int bufferevent_ssl_renegotiate_impl(struct bufferevent *bev); +unsigned long bufferevent_get_ssl_error(struct bufferevent *bev); +int bufferevent_ssl_get_allow_dirty_shutdown(struct bufferevent *bev); +void bufferevent_ssl_set_allow_dirty_shutdown( + struct bufferevent *bev, int allow_dirty_shutdown); + +#endif /* SSL_COMPACT_H */ diff --git a/test-export/test-export.c b/test-export/test-export.c index 90917775..3dbeadb5 100644 --- a/test-export/test-export.c +++ b/test-export/test-export.c @@ -9,6 +9,9 @@ #include #include #include +#elif defined(EVENT_EXPORT_TEST_COMPONENT_MBEDTLS) +#include +#include #endif #if defined(EVENT_EXPORT_TEST_COMPONENT_EXTRA) @@ -90,6 +93,54 @@ error: SSL_free(ssl); return r; } +#elif defined(EVENT_EXPORT_TEST_COMPONENT_MBEDTLS) +static int +test() +{ + struct event_base *base = NULL; + mbedtls_ssl_config *conf = NULL; + mbedtls_ssl_context *ssl = NULL; + struct bufferevent *bev; + int r = 1; + + base = event_base_new(); + if (!base) { + goto error; + } + + conf = malloc(sizeof(*conf)); + if (!conf) { + goto error; + } + mbedtls_ssl_config_init(conf); + + ssl = malloc(sizeof(*ssl)); + if (!ssl) { + goto error; + } + mbedtls_ssl_init(ssl); + mbedtls_ssl_setup(ssl, conf); + + bev = bufferevent_mbedtls_socket_new(base, -1, ssl, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + if (bev == NULL) { + goto error; + } + r = 0; +error: + if (base) + event_base_free(base); + if (ssl) { + mbedtls_ssl_free(ssl); + free(ssl); + } + if (conf) { + mbedtls_ssl_config_free(conf); + free(conf); + } + return r; +} #else static int test() diff --git a/test-export/test-export.py b/test-export/test-export.py index d71e5dba..a6def1ca 100644 --- a/test-export/test-export.py +++ b/test-export/test-export.py @@ -88,13 +88,18 @@ def test_group(): testcase("core", "core", 0) testcase("extra", "extra", 0) testcase("openssl", "openssl", 0) + testcase("mbedtls", "mbedtls", 0) testcase("", "", 0) testcase("extra", "core", 0) testcase("openssl", "core", 0) + testcase("mbedtls", "core", 0) testcase("core", "extra", 1) testcase("core", "openssl", 1) testcase("extra", "openssl", 1) testcase("openssl", "extra", 1) + testcase("core", "mbedtls", 1) + testcase("extra", "mbedtls", 1) + testcase("mbedtls", "extra", 1) if platform.system() != "Windows": testcase("pthreads", "pthreads", 0) testcase("pthreads", "core", 0) @@ -103,6 +108,8 @@ def test_group(): testcase("pthreads", "extra", 1) testcase("pthreads", "openssl", 1) testcase("openssl", "pthreads", 1) + testcase("pthreads", "mbedtls", 1) + testcase("mbedtls", "pthreads", 1) def config_restore(): diff --git a/test/include.am b/test/include.am index 50e9cdbd..8ec8d534 100644 --- a/test/include.am +++ b/test/include.am @@ -149,11 +149,17 @@ test_regress_CPPFLAGS = $(AM_CPPFLAGS) $(PTHREAD_CFLAGS) $(ZLIB_CFLAGS) -Itest test_regress_LDFLAGS = $(PTHREAD_CFLAGS) if OPENSSL -test_regress_SOURCES += test/regress_ssl.c +test_regress_SOURCES += test/regress_openssl.c test_regress_CPPFLAGS += $(OPENSSL_INCS) test_regress_LDADD += libevent_openssl.la $(OPENSSL_LIBS) ${OPENSSL_LIBADD} endif +if MBEDTLS +test_regress_SOURCES += test/regress_mbedtls.c +test_regress_CPPFLAGS += $(MBEDTLS_INCS) +test_regress_LDADD += libevent_mbedtls.la $(MBEDTLS_LIBS) +endif + test_bench_SOURCES = test/bench.c test_bench_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la test_bench_cascade_SOURCES = test/bench_cascade.c diff --git a/test/regress.h b/test/regress.h index 55a2fddb..b7c4e0bc 100644 --- a/test/regress.h +++ b/test/regress.h @@ -49,7 +49,8 @@ extern struct testcase_t rpc_testcases[]; extern struct testcase_t edgetriggered_testcases[]; extern struct testcase_t minheap_testcases[]; extern struct testcase_t iocp_testcases[]; -extern struct testcase_t ssl_testcases[]; +extern struct testcase_t openssl_testcases[]; +extern struct testcase_t mbedtls_testcases[]; extern struct testcase_t listener_testcases[]; extern struct testcase_t listener_iocp_testcases[]; extern struct testcase_t thread_testcases[]; diff --git a/test/regress_main.c b/test/regress_main.c index 2951e3d4..9f0dcaf2 100644 --- a/test/regress_main.c +++ b/test/regress_main.c @@ -451,7 +451,10 @@ struct testgroup_t testgroups[] = { { "iocp/http/", http_iocp_testcases }, #endif #ifdef EVENT__HAVE_OPENSSL - { "ssl/", ssl_testcases }, + { "openssl/", openssl_testcases }, +#endif +#ifdef EVENT__HAVE_MBEDTLS + { "mbedtls/", mbedtls_testcases }, #endif END_OF_GROUPS }; diff --git a/test/regress_mbedtls.c b/test/regress_mbedtls.c new file mode 100644 index 00000000..44434fe4 --- /dev/null +++ b/test/regress_mbedtls.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** For event_debug() usage/coverage */ +#define EVENT_VISIBILITY_WANT_DLLIMPORT + +#include "event2/util.h" +#include +#include +#include +#include +#include "regress.h" +#include "tinytest.h" + +#define TESTCASES_NAME mbedtls_testcases + +#ifdef OPENSSL_VERSION_NUMBER +#undef OPENSSL_VERSION_NUMBER +#endif +#define OPENSSL_VERSION_NUMBER 0 +#define SSL_IS_CLIENT MBEDTLS_SSL_IS_CLIENT +#define SSL_IS_SERVER MBEDTLS_SSL_IS_SERVER + +#define get_ssl_ctx get_mbedtls_config + +#define SSL_renegotiate mbedtls_ssl_renegotiate +#define SSL_get_peer_certificate mbedtls_ssl_get_peer_cert +#define SSL_new mbedtls_ssl_new +#define SSL_use_certificate(a, b) \ + do { \ + } while (0); +#define SSL_use_PrivateKey(a, b) \ + do { \ + } while (0); +#define X509_free(x) \ + do { \ + } while (0); + +#define X509 const mbedtls_x509_crt +#define SSL mbedtls_ssl_context + +#define bufferevent_ssl_get_ssl bufferevent_mbedtls_get_ssl +#define bufferevent_ssl_set_allow_dirty_shutdown \ + bufferevent_mbedtls_set_allow_dirty_shutdown +#define bufferevent_ssl_socket_new bufferevent_mbedtls_socket_new +#define bufferevent_ssl_filter_new bufferevent_mbedtls_filter_new + +struct rwcount; +static void BIO_setup(SSL *ssl, struct rwcount *rw); +static mbedtls_ssl_config *get_mbedtls_config(int endpoint); +static mbedtls_ssl_context *mbedtls_ssl_new(mbedtls_ssl_config *config); +static void *mbedtls_test_setup(const struct testcase_t *testcase); +static int mbedtls_test_cleanup(const struct testcase_t *testcase, void *ptr); +static const struct testcase_setup_t ssl_setup = { + mbedtls_test_setup, mbedtls_test_cleanup}; +#include "regress_ssl.c" +static mbedtls_ssl_config *the_mbedtls_conf[2] = {NULL, NULL}; +static mbedtls_ssl_context *the_mbedtls_ctx[1024] = {NULL}; +static int the_mbedtls_ctx_count = 0; +static mbedtls_entropy_context entropy; +static mbedtls_ctr_drbg_context ctr_drbg; +static mbedtls_x509_crt *the_cert; +static mbedtls_pk_context *the_key; + +static void +mbedtls_debug( + void *userdata, int level, const char *file, int line, const char *str) +{ + int loglen = strlen(str); + if (str[loglen - 1] == '\n') + loglen--; + event_debug(("[mbedtls][%s][%d][%s][%d]%.*s", (char *)userdata, level, file, + line, loglen, str)); +} + +static mbedtls_pk_context * +mbedtls_getkey(void) +{ + int ret = 0; + mbedtls_pk_context *pk = malloc(sizeof(mbedtls_pk_context)); + tt_assert(pk); + mbedtls_pk_init(pk); + ret = mbedtls_pk_parse_key( + pk, (const unsigned char *)KEY, sizeof(KEY), NULL, 0); + tt_assert(ret == 0); + return pk; +end: + if (pk) { + mbedtls_pk_free(pk); + free(pk); + } + return NULL; +} + +static void +create_tm_from_unix_epoch(struct tm *cur_p, const time_t t) +{ +#ifdef _WIN32 + struct tm *tmp = gmtime(&t); + if (!tmp) { + fprintf(stderr, "gmtime: %s (%i)", strerror(errno), (int)t); + exit(1); + } + *cur_p = *tmp; +#else + gmtime_r(&t, cur_p); +#endif +} + +static mbedtls_x509_crt * +mbedtls_getcert(mbedtls_pk_context *pk) +{ + const char *name = "commonName=example.com"; + time_t now = time(NULL); + char now_string[32] = ""; + char not_before[32] = ""; + char not_after[32] = ""; + unsigned char certbuf[8192]; + struct tm tm; + mbedtls_x509_crt *crt = NULL; + int ret = 0; + + mbedtls_mpi serial; + mbedtls_x509write_cert write_cert; + + snprintf(now_string, sizeof(now_string), "%lld", (long long)now); + + create_tm_from_unix_epoch(&tm, now); + strftime(not_before, sizeof(not_before), "%Y%m%d%H%M%S", &tm); + now += 3600; + create_tm_from_unix_epoch(&tm, now); + strftime(not_after, sizeof(not_after), "%Y%m%d%H%M%S", &tm); + + mbedtls_x509write_crt_init(&write_cert); + mbedtls_x509write_crt_set_version(&write_cert, 2); + + mbedtls_mpi_init(&serial); + ret = mbedtls_mpi_read_string(&serial, 10, now_string); + tt_assert(ret == 0); + ret = mbedtls_x509write_crt_set_serial(&write_cert, &serial); + tt_assert(ret == 0); + mbedtls_mpi_free(&serial); + + ret = mbedtls_x509write_crt_set_subject_name(&write_cert, name); + tt_assert(ret == 0); + ret = mbedtls_x509write_crt_set_issuer_name(&write_cert, name); + tt_assert(ret == 0); + + mbedtls_x509write_crt_set_md_alg(&write_cert, MBEDTLS_MD_SHA256); + + ret = + mbedtls_x509write_crt_set_validity(&write_cert, not_before, not_after); + tt_assert(ret == 0); + mbedtls_x509write_crt_set_issuer_key(&write_cert, pk); + mbedtls_x509write_crt_set_subject_key(&write_cert, pk); + + ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *)name, strlen(name)); + tt_assert(ret == 0); + ret = mbedtls_x509write_crt_pem(&write_cert, certbuf, sizeof(certbuf), + mbedtls_ctr_drbg_random, &ctr_drbg); + tt_assert(ret == 0); + mbedtls_x509write_crt_free(&write_cert); + + crt = malloc(sizeof(mbedtls_x509_crt)); + tt_assert(crt); + mbedtls_x509_crt_init(crt); + ret = mbedtls_x509_crt_parse(crt, certbuf, strlen((char *)certbuf) + 1); + tt_assert(ret == 0); + return crt; +end: + if (crt) { + mbedtls_x509_crt_free(crt); + free(crt); + } + return NULL; +} + +static mbedtls_ssl_config * +get_mbedtls_config(int endpoint) +{ + if (the_mbedtls_conf[endpoint]) + return the_mbedtls_conf[endpoint]; + the_mbedtls_conf[endpoint] = malloc(sizeof(mbedtls_ssl_config)); + if (!the_mbedtls_conf[endpoint]) + return NULL; + mbedtls_ssl_config_init(the_mbedtls_conf[endpoint]); + mbedtls_ssl_conf_renegotiation( + the_mbedtls_conf[endpoint], MBEDTLS_SSL_RENEGOTIATION_ENABLED); + mbedtls_ssl_conf_dbg(the_mbedtls_conf[endpoint], mbedtls_debug, + (void *)(endpoint == MBEDTLS_SSL_IS_SERVER ? "server" : "client")); + mbedtls_ssl_config_defaults(the_mbedtls_conf[endpoint], endpoint, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + mbedtls_ssl_conf_rng( + the_mbedtls_conf[endpoint], mbedtls_ctr_drbg_random, &ctr_drbg); + if (disable_tls_11_and_12) { + mbedtls_ssl_conf_max_version(the_mbedtls_conf[endpoint], + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_1); + } + if (endpoint == MBEDTLS_SSL_IS_SERVER) { + mbedtls_ssl_conf_own_cert( + the_mbedtls_conf[endpoint], the_cert, the_key); + } else { /* MBEDTLS_SSL_IS_CLIENT */ + mbedtls_ssl_conf_ca_chain(the_mbedtls_conf[endpoint], the_cert, NULL); + } + return the_mbedtls_conf[endpoint]; +} + +static void +init_mbedtls(void) +{ + mbedtls_debug_set_threshold(5); +} + +static void * +mbedtls_test_setup(const struct testcase_t *testcase) +{ + init_mbedtls(); + + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + (const unsigned char *)"libevent", sizeof("libevent")); + + the_key = mbedtls_getkey(); + EVUTIL_ASSERT(the_key); + + the_cert = mbedtls_getcert(the_key); + EVUTIL_ASSERT(the_cert); + + disable_tls_11_and_12 = 0; + + return basic_test_setup(testcase); +} +static int +mbedtls_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + int i; + int ret = basic_test_cleanup(testcase, ptr); + if (!ret) { + return ret; + } + + test_is_done = 0; + n_connected = 0; + got_close = 0; + got_error = 0; + got_timeout = 0; + renegotiate_at = -1; + stop_when_connected = 0; + pending_connect_events = 0; + exit_base = NULL; + + mbedtls_x509_crt_free(the_cert); + free(the_cert); + mbedtls_pk_free(the_key); + free(the_key); + + for (i = 0; i < the_mbedtls_ctx_count; i++) { + mbedtls_ssl_free(the_mbedtls_ctx[i]); + } + if (the_mbedtls_conf[0]) { + mbedtls_ssl_config_free(the_mbedtls_conf[0]); + free(the_mbedtls_conf[0]); + the_mbedtls_conf[0] = NULL; + } + if (the_mbedtls_conf[1]) { + mbedtls_ssl_config_free(the_mbedtls_conf[1]); + free(the_mbedtls_conf[1]); + the_mbedtls_conf[1] = NULL; + } + + return 1; +} + +static mbedtls_ssl_context * +mbedtls_ssl_new(mbedtls_ssl_config *config) +{ + mbedtls_ssl_context *ssl = malloc(sizeof(*ssl)); + mbedtls_ssl_init(ssl); + mbedtls_ssl_setup(ssl, config); + the_mbedtls_ctx[the_mbedtls_ctx_count++] = ssl; + return ssl; +} + +static int +bio_rwcount_read(void *ctx, unsigned char *out, size_t outlen) +{ + struct rwcount *rw = ctx; + ev_ssize_t ret = recv(rw->fd, out, outlen, 0); + ++rw->read; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + return MBEDTLS_ERR_SSL_WANT_READ; + } + return ret; +} +static int +bio_rwcount_write(void *ctx, const unsigned char *in, size_t inlen) +{ + struct rwcount *rw = ctx; + ev_ssize_t ret = send(rw->fd, in, inlen, 0); + ++rw->write; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + return MBEDTLS_ERR_SSL_WANT_WRITE; + } + return ret; +} +static void +BIO_setup(SSL *ssl, struct rwcount *rw) +{ + mbedtls_ssl_set_bio(ssl, rw, bio_rwcount_write, bio_rwcount_read, + NULL); +} diff --git a/test/regress_openssl.c b/test/regress_openssl.c new file mode 100644 index 00000000..1bf0b50d --- /dev/null +++ b/test/regress_openssl.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "event2/util.h" +#include +#include +#include "openssl-compat.h" +#include "regress.h" +#include "tinytest.h" +#define TESTCASES_NAME openssl_testcases +static void *ssl_test_setup(const struct testcase_t *testcase); +static int ssl_test_cleanup(const struct testcase_t *testcase, void *ptr); +static const struct testcase_setup_t ssl_setup = { + ssl_test_setup, ssl_test_cleanup}; + +static X509 *the_cert; +EVP_PKEY *the_key; + +#define SSL_IS_CLIENT +#define SSL_IS_SERVER + +#define bufferevent_ssl_get_ssl bufferevent_openssl_get_ssl +#define bufferevent_ssl_set_allow_dirty_shutdown \ + bufferevent_openssl_set_allow_dirty_shutdown +#define bufferevent_ssl_socket_new bufferevent_openssl_socket_new +#define bufferevent_ssl_filter_new bufferevent_openssl_filter_new + +struct rwcount; +static void BIO_setup(SSL *ssl, struct rwcount *rw); +#include "regress_ssl.c" + +EVP_PKEY * +ssl_getkey(void) +{ + EVP_PKEY *key; + BIO *bio; + + /* new read-only BIO backed by KEY. */ + bio = BIO_new_mem_buf((char *)KEY, -1); + tt_assert(bio); + + key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + BIO_free(bio); + tt_assert(key); + + return key; +end: + return NULL; +} + +X509 * +ssl_getcert(EVP_PKEY *key) +{ + /* Dummy code to make a quick-and-dirty valid certificate with + OpenSSL. Don't copy this code into your own program! It does a + number of things in a stupid and insecure way. */ + X509 *x509 = NULL; + X509_NAME *name = NULL; + int nid; + time_t now = time(NULL); + + tt_assert(key); + + x509 = X509_new(); + tt_assert(x509); + tt_assert(0 != X509_set_version(x509, 2)); + tt_assert(0 != ASN1_INTEGER_set(X509_get_serialNumber(x509), (long)now)); + + name = X509_NAME_new(); + tt_assert(name); + nid = OBJ_txt2nid("commonName"); + tt_assert(NID_undef != nid); + tt_assert(0 != X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, + (unsigned char *)"example.com", -1, -1, 0)); + + X509_set_subject_name(x509, name); + X509_set_issuer_name(x509, name); + X509_NAME_free(name); + + X509_time_adj(X509_getm_notBefore(x509), 0, &now); + now += 3600; + X509_time_adj(X509_getm_notAfter(x509), 0, &now); + X509_set_pubkey(x509, key); + tt_assert(0 != X509_sign(x509, key, EVP_sha1())); + + return x509; +end: + X509_free(x509); + X509_NAME_free(name); + return NULL; +} + +static SSL_CTX *the_ssl_ctx = NULL; + +SSL_CTX * +get_ssl_ctx(void) +{ + if (the_ssl_ctx) + return the_ssl_ctx; + the_ssl_ctx = SSL_CTX_new(SSLv23_method()); + if (!the_ssl_ctx) + return NULL; + if (disable_tls_11_and_12) { +#ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_2); +#endif +#ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_1); +#endif + } + return the_ssl_ctx; +} + +void +init_ssl(void) +{ +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x20700000L) + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + if (SSLeay() != OPENSSL_VERSION_NUMBER) { + TT_DECLARE("WARN", ("Version mismatch for openssl: compiled with %lx " + "but running with %lx", + (unsigned long)OPENSSL_VERSION_NUMBER, + (unsigned long)SSLeay())); + } +#endif +} + +static void * +ssl_test_setup(const struct testcase_t *testcase) +{ + init_ssl(); + + the_key = ssl_getkey(); + EVUTIL_ASSERT(the_key); + + the_cert = ssl_getcert(the_key); + EVUTIL_ASSERT(the_cert); + + disable_tls_11_and_12 = 0; + + return basic_test_setup(testcase); +} +static int +ssl_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + int ret = basic_test_cleanup(testcase, ptr); + if (!ret) { + return ret; + } + + test_is_done = 0; + n_connected = 0; + got_close = 0; + got_error = 0; + got_timeout = 0; + renegotiate_at = -1; + stop_when_connected = 0; + pending_connect_events = 0; + exit_base = NULL; + + X509_free(the_cert); + EVP_PKEY_free(the_key); + + SSL_CTX_free(the_ssl_ctx); + the_ssl_ctx = NULL; + + return 1; +} + +static int +bio_rwcount_new(BIO *b) +{ + BIO_set_init(b, 0); + BIO_set_data(b, NULL); + return 1; +} +static int +bio_rwcount_free(BIO *b) +{ + TT_BLATHER(("bio_rwcount_free: %p", b)); + if (!b) + return 0; + if (BIO_get_shutdown(b)) { + BIO_set_init(b, 0); + BIO_set_data(b, NULL); + } + return 1; +} +static int +bio_rwcount_read(BIO *b, char *out, int outlen) +{ + struct rwcount *rw = BIO_get_data(b); + ev_ssize_t ret = recv(rw->fd, out, outlen, 0); + ++rw->read; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + BIO_set_retry_read(b); + } + return ret; +} +static int +bio_rwcount_write(BIO *b, const char *in, int inlen) +{ + struct rwcount *rw = BIO_get_data(b); + ev_ssize_t ret = send(rw->fd, in, inlen, 0); + ++rw->write; + if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { + BIO_set_retry_write(b); + } + return ret; +} +static long +bio_rwcount_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + struct rwcount *rw = BIO_get_data(b); + long ret = 0; + switch (cmd) { + case BIO_C_GET_FD: + ret = rw->fd; + break; + case BIO_CTRL_GET_CLOSE: + ret = BIO_get_shutdown(b); + break; + case BIO_CTRL_SET_CLOSE: + BIO_set_shutdown(b, (int)num); + break; + case BIO_CTRL_PENDING: + ret = 0; + break; + case BIO_CTRL_WPENDING: + ret = 0; + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + } + return ret; +} +static int +bio_rwcount_puts(BIO *b, const char *s) +{ + return bio_rwcount_write(b, s, strlen(s)); +} +#define BIO_TYPE_LIBEVENT_RWCOUNT 0xff1 +static BIO_METHOD *methods_rwcount; + +static BIO_METHOD * +BIO_s_rwcount(void) +{ + if (methods_rwcount == NULL) { + methods_rwcount = BIO_meth_new(BIO_TYPE_LIBEVENT_RWCOUNT, "rwcount"); + if (methods_rwcount == NULL) + return NULL; + BIO_meth_set_write(methods_rwcount, bio_rwcount_write); + BIO_meth_set_read(methods_rwcount, bio_rwcount_read); + BIO_meth_set_puts(methods_rwcount, bio_rwcount_puts); + BIO_meth_set_ctrl(methods_rwcount, bio_rwcount_ctrl); + BIO_meth_set_create(methods_rwcount, bio_rwcount_new); + BIO_meth_set_destroy(methods_rwcount, bio_rwcount_free); + } + return methods_rwcount; +} +static BIO * +BIO_new_rwcount(int close_flag) +{ + BIO *result; + if (!(result = BIO_new(BIO_s_rwcount()))) + return NULL; + BIO_set_init(result, 1); + BIO_set_data(result, NULL); + BIO_set_shutdown(result, !!close_flag); + return result; +} +static void +BIO_setup(SSL *ssl, struct rwcount *rw) +{ + BIO *bio; + bio = BIO_new_rwcount(0); + tt_assert(bio); + BIO_set_data(bio, rw); + SSL_set_bio(ssl, bio, bio); +end: + return; +} diff --git a/test/regress_ssl.c b/test/regress_ssl.c index 37dc334d..b5c37b80 100644 --- a/test/regress_ssl.c +++ b/test/regress_ssl.c @@ -42,21 +42,15 @@ #include #endif -#include "event2/util.h" #include "event2/event.h" #include "event2/bufferevent_ssl.h" #include "event2/bufferevent_struct.h" #include "event2/buffer.h" #include "event2/listener.h" -#include "regress.h" #include "tinytest.h" #include "tinytest_macros.h" -#include -#include -#include "openssl-compat.h" - #include #ifdef _WIN32 #include @@ -98,91 +92,7 @@ static const char KEY[] = "lhdEOj7mAgHwGwwVZWOgs9Lq6vfztnSuhqjha1daESY6kDscPIQ=\n" "-----END RSA PRIVATE KEY-----\n"; -EVP_PKEY * -ssl_getkey(void) -{ - EVP_PKEY *key; - BIO *bio; - - /* new read-only BIO backed by KEY. */ - bio = BIO_new_mem_buf((char*)KEY, -1); - tt_assert(bio); - - key = PEM_read_bio_PrivateKey(bio,NULL,NULL,NULL); - BIO_free(bio); - tt_assert(key); - - return key; -end: - return NULL; -} - -X509 * -ssl_getcert(EVP_PKEY *key) -{ - /* Dummy code to make a quick-and-dirty valid certificate with - OpenSSL. Don't copy this code into your own program! It does a - number of things in a stupid and insecure way. */ - X509 *x509 = NULL; - X509_NAME *name = NULL; - int nid; - time_t now = time(NULL); - - tt_assert(key); - - x509 = X509_new(); - tt_assert(x509); - tt_assert(0 != X509_set_version(x509, 2)); - tt_assert(0 != ASN1_INTEGER_set(X509_get_serialNumber(x509), - (long)now)); - - name = X509_NAME_new(); - tt_assert(name); - nid = OBJ_txt2nid("commonName"); - tt_assert(NID_undef != nid); - tt_assert(0 != X509_NAME_add_entry_by_NID( - name, nid, MBSTRING_ASC, (unsigned char*)"example.com", - -1, -1, 0)); - - X509_set_subject_name(x509, name); - X509_set_issuer_name(x509, name); - X509_NAME_free(name); - - X509_time_adj(X509_getm_notBefore(x509), 0, &now); - now += 3600; - X509_time_adj(X509_getm_notAfter(x509), 0, &now); - X509_set_pubkey(x509, key); - tt_assert(0 != X509_sign(x509, key, EVP_sha1())); - - return x509; -end: - X509_free(x509); - X509_NAME_free(name); - return NULL; -} - static int disable_tls_11_and_12 = 0; -static SSL_CTX *the_ssl_ctx = NULL; - -SSL_CTX * -get_ssl_ctx(void) -{ - if (the_ssl_ctx) - return the_ssl_ctx; - the_ssl_ctx = SSL_CTX_new(SSLv23_method()); - if (!the_ssl_ctx) - return NULL; - if (disable_tls_11_and_12) { -#ifdef SSL_OP_NO_TLSv1_2 - SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_2); -#endif -#ifdef SSL_OP_NO_TLSv1_1 - SSL_CTX_set_options(the_ssl_ctx, SSL_OP_NO_TLSv1_1); -#endif - } - return the_ssl_ctx; -} - static int test_is_done; static int n_connected; static int got_close; @@ -192,70 +102,6 @@ static int renegotiate_at = -1; static int stop_when_connected; static int pending_connect_events; static struct event_base *exit_base; -static X509 *the_cert; -EVP_PKEY *the_key; - -void -init_ssl(void) -{ -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ - (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) - SSL_library_init(); - ERR_load_crypto_strings(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - if (SSLeay() != OPENSSL_VERSION_NUMBER) { - TT_DECLARE("WARN", - ("Version mismatch for openssl: compiled with %lx but running with %lx", - (unsigned long)OPENSSL_VERSION_NUMBER, (unsigned long)SSLeay())); - } -#endif -} - -static void * -ssl_test_setup(const struct testcase_t *testcase) -{ - init_ssl(); - - the_key = ssl_getkey(); - EVUTIL_ASSERT(the_key); - - the_cert = ssl_getcert(the_key); - EVUTIL_ASSERT(the_cert); - - disable_tls_11_and_12 = 0; - - return basic_test_setup(testcase); -} -static int -ssl_test_cleanup(const struct testcase_t *testcase, void *ptr) -{ - int ret = basic_test_cleanup(testcase, ptr); - if (!ret) { - return ret; - } - - test_is_done = 0; - n_connected = 0; - got_close = 0; - got_error = 0; - got_timeout = 0; - renegotiate_at = -1; - stop_when_connected = 0; - pending_connect_events = 0; - exit_base = NULL; - - X509_free(the_cert); - EVP_PKEY_free(the_key); - - SSL_CTX_free(the_ssl_ctx); - the_ssl_ctx = NULL; - - return 1; -} -const struct testcase_setup_t ssl_setup = { - ssl_test_setup, ssl_test_cleanup -}; /* ==================== @@ -285,7 +131,7 @@ enum regress_openssl_type }; static void -bufferevent_openssl_check_fd(struct bufferevent *bev, int filter) +bufferevent_ssl_check_fd(struct bufferevent *bev, int filter) { tt_fd_op(bufferevent_getfd(bev), !=, EVUTIL_INVALID_SOCKET); tt_fd_op(bufferevent_setfd(bev, EVUTIL_INVALID_SOCKET), ==, 0); @@ -299,7 +145,7 @@ end: ; } static void -bufferevent_openssl_check_freed(struct bufferevent *bev) +bufferevent_ssl_check_freed(struct bufferevent *bev) { tt_int_op(event_pending(&bev->ev_read, EVLIST_ALL, NULL), ==, 0); tt_int_op(event_pending(&bev->ev_write, EVLIST_ALL, NULL), ==, 0); @@ -339,14 +185,15 @@ respond_to_number(struct bufferevent *bev, void *ctx) return; } if ((type & REGRESS_OPENSSL_CLIENT) && n == renegotiate_at) { - SSL_renegotiate(bufferevent_openssl_get_ssl(bev)); + SSL_renegotiate(bufferevent_ssl_get_ssl(bev)); } ++n; evbuffer_add_printf(bufferevent_get_output(bev), "%d\n", n); TT_BLATHER(("Done reading; now writing.")); bufferevent_enable(bev, EV_WRITE); - bufferevent_disable(bev, EV_READ); + // we shouldn't disable EV_READ here, otherwise we wouldn't got close cb + // bufferevent_disable(bev, EV_READ); } static void @@ -372,7 +219,7 @@ eventcb(struct bufferevent *bev, short what, void *ctx) if (what & BEV_EVENT_CONNECTED) { SSL *ssl; ++n_connected; - ssl = bufferevent_openssl_get_ssl(bev); + ssl = bufferevent_ssl_get_ssl(bev); tt_assert(ssl); peer_cert = SSL_get_peer_certificate(ssl); if (type & REGRESS_OPENSSL_SERVER) { @@ -391,30 +238,30 @@ eventcb(struct bufferevent *bev, short what, void *ctx) TT_BLATHER(("Got a good EOF")); ++got_close; if (type & REGRESS_OPENSSL_FD) { - bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + bufferevent_ssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); } if (type & REGRESS_OPENSSL_FREED) { - bufferevent_openssl_check_freed(bev); + bufferevent_ssl_check_freed(bev); } bufferevent_free(bev); } else if (what & BEV_EVENT_ERROR) { TT_BLATHER(("Got an error.")); ++got_error; if (type & REGRESS_OPENSSL_FD) { - bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + bufferevent_ssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); } if (type & REGRESS_OPENSSL_FREED) { - bufferevent_openssl_check_freed(bev); + bufferevent_ssl_check_freed(bev); } bufferevent_free(bev); } else if (what & BEV_EVENT_TIMEOUT) { TT_BLATHER(("Got timeout.")); ++got_timeout; if (type & REGRESS_OPENSSL_FD) { - bufferevent_openssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); + bufferevent_ssl_check_fd(bev, type & REGRESS_OPENSSL_FILTER); } if (type & REGRESS_OPENSSL_FREED) { - bufferevent_openssl_check_freed(bev); + bufferevent_ssl_check_freed(bev); } bufferevent_free(bev); } @@ -434,14 +281,14 @@ open_ssl_bufevs(struct bufferevent **bev1_out, struct bufferevent **bev2_out, int state2 = is_open ? BUFFEREVENT_SSL_OPEN :BUFFEREVENT_SSL_ACCEPTING; int dirty_shutdown = type & REGRESS_OPENSSL_DIRTY_SHUTDOWN; if (fd_pair) { - *bev1_out = bufferevent_openssl_socket_new( + *bev1_out = bufferevent_ssl_socket_new( base, fd_pair[0], ssl1, state1, flags); - *bev2_out = bufferevent_openssl_socket_new( + *bev2_out = bufferevent_ssl_socket_new( base, fd_pair[1], ssl2, state2, flags); } else { - *bev1_out = bufferevent_openssl_filter_new( + *bev1_out = bufferevent_ssl_filter_new( base, underlying_pair[0], ssl1, state1, flags); - *bev2_out = bufferevent_openssl_filter_new( + *bev2_out = bufferevent_ssl_filter_new( base, underlying_pair[1], ssl2, state2, flags); } @@ -450,8 +297,8 @@ open_ssl_bufevs(struct bufferevent **bev1_out, struct bufferevent **bev2_out, bufferevent_setcb(*bev2_out, respond_to_number, done_writing_cb, eventcb, (void*)(REGRESS_OPENSSL_SERVER | (long)type)); - bufferevent_openssl_set_allow_dirty_shutdown(*bev1_out, dirty_shutdown); - bufferevent_openssl_set_allow_dirty_shutdown(*bev2_out, dirty_shutdown); + bufferevent_ssl_set_allow_dirty_shutdown(*bev1_out, dirty_shutdown); + bufferevent_ssl_set_allow_dirty_shutdown(*bev2_out, dirty_shutdown); } static void @@ -478,8 +325,8 @@ regress_bufferevent_openssl(void *arg) renegotiate_at = 600; } - ssl1 = SSL_new(get_ssl_ctx()); - ssl2 = SSL_new(get_ssl_ctx()); + ssl1 = SSL_new(get_ssl_ctx(SSL_IS_CLIENT)); + ssl2 = SSL_new(get_ssl_ctx(SSL_IS_SERVER)); SSL_use_certificate(ssl2, the_cert); SSL_use_PrivateKey(ssl2, the_key); @@ -581,14 +428,14 @@ acceptcb(struct evconnlistener *listener, evutil_socket_t fd, struct basic_test_data *data = arg; struct bufferevent *bev; enum regress_openssl_type type; - SSL *ssl = SSL_new(get_ssl_ctx()); + SSL *ssl = SSL_new(get_ssl_ctx(SSL_IS_SERVER)); type = (enum regress_openssl_type)data->setup_data; SSL_use_certificate(ssl, the_cert); SSL_use_PrivateKey(ssl, the_key); - bev = bufferevent_openssl_socket_new( + bev = bufferevent_ssl_socket_new( data->base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); tt_assert(bev); @@ -618,110 +465,6 @@ struct rwcount size_t read; size_t write; }; -static int -bio_rwcount_new(BIO *b) -{ - BIO_set_init(b, 0); - BIO_set_data(b, NULL); - return 1; -} -static int -bio_rwcount_free(BIO *b) -{ - TT_BLATHER(("bio_rwcount_free: %p", b)); - if (!b) - return 0; - if (BIO_get_shutdown(b)) { - BIO_set_init(b, 0); - BIO_set_data(b, NULL); - } - return 1; -} -static int -bio_rwcount_read(BIO *b, char *out, int outlen) -{ - struct rwcount *rw = BIO_get_data(b); - ev_ssize_t ret = recv(rw->fd, out, outlen, 0); - ++rw->read; - if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { - BIO_set_retry_read(b); - } - return ret; -} -static int -bio_rwcount_write(BIO *b, const char *in, int inlen) -{ - struct rwcount *rw = BIO_get_data(b); - ev_ssize_t ret = send(rw->fd, in, inlen, 0); - ++rw->write; - if (ret == -1 && EVUTIL_ERR_RW_RETRIABLE(EVUTIL_SOCKET_ERROR())) { - BIO_set_retry_write(b); - } - return ret; -} -static long -bio_rwcount_ctrl(BIO *b, int cmd, long num, void *ptr) -{ - struct rwcount *rw = BIO_get_data(b); - long ret = 0; - switch (cmd) { - case BIO_C_GET_FD: - ret = rw->fd; - break; - case BIO_CTRL_GET_CLOSE: - ret = BIO_get_shutdown(b); - break; - case BIO_CTRL_SET_CLOSE: - BIO_set_shutdown(b, (int)num); - break; - case BIO_CTRL_PENDING: - ret = 0; - break; - case BIO_CTRL_WPENDING: - ret = 0; - break; - case BIO_CTRL_DUP: - case BIO_CTRL_FLUSH: - ret = 1; - break; - } - return ret; -} -static int -bio_rwcount_puts(BIO *b, const char *s) -{ - return bio_rwcount_write(b, s, strlen(s)); -} -#define BIO_TYPE_LIBEVENT_RWCOUNT 0xff1 -static BIO_METHOD *methods_rwcount; - -static BIO_METHOD * -BIO_s_rwcount(void) -{ - if (methods_rwcount == NULL) { - methods_rwcount = BIO_meth_new(BIO_TYPE_LIBEVENT_RWCOUNT, "rwcount"); - if (methods_rwcount == NULL) - return NULL; - BIO_meth_set_write(methods_rwcount, bio_rwcount_write); - BIO_meth_set_read(methods_rwcount, bio_rwcount_read); - BIO_meth_set_puts(methods_rwcount, bio_rwcount_puts); - BIO_meth_set_ctrl(methods_rwcount, bio_rwcount_ctrl); - BIO_meth_set_create(methods_rwcount, bio_rwcount_new); - BIO_meth_set_destroy(methods_rwcount, bio_rwcount_free); - } - return methods_rwcount; -} -static BIO * -BIO_new_rwcount(int close_flag) -{ - BIO *result; - if (!(result = BIO_new(BIO_s_rwcount()))) - return NULL; - BIO_set_init(result, 1); - BIO_set_data(result, NULL); - BIO_set_shutdown(result, !!close_flag); - return result; -} static void regress_bufferevent_openssl_connect(void *arg) @@ -755,10 +498,10 @@ regress_bufferevent_openssl_connect(void *arg) tt_assert(listener); tt_assert(evconnlistener_get_fd(listener) >= 0); - ssl = SSL_new(get_ssl_ctx()); + ssl = SSL_new(get_ssl_ctx(SSL_IS_CLIENT)); tt_assert(ssl); - bev = bufferevent_openssl_socket_new( + bev = bufferevent_ssl_socket_new( data->base, -1, ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); @@ -777,13 +520,8 @@ regress_bufferevent_openssl_connect(void *arg) /* Possible only when we have fd, since be_openssl can and will overwrite * bio otherwise before */ if (type & REGRESS_OPENSSL_SLEEP) { - BIO *bio; - rw.fd = bufferevent_getfd(bev); - bio = BIO_new_rwcount(0); - tt_assert(bio); - BIO_set_data(bio, &rw); - SSL_set_bio(ssl, bio, bio); + BIO_setup(ssl, &rw); } evbuffer_add_printf(bufferevent_get_output(bev), "1\n"); bufferevent_enable(bev, EV_READ|EV_WRITE); @@ -859,12 +597,12 @@ wm_acceptcb(struct evconnlistener *listener, evutil_socket_t fd, struct wm_context *ctx = arg; struct bufferevent *bev; struct event_base *base = evconnlistener_get_base(listener); - SSL *ssl = SSL_new(get_ssl_ctx()); + SSL *ssl = SSL_new(get_ssl_ctx(SSL_IS_SERVER)); SSL_use_certificate(ssl, the_cert); SSL_use_PrivateKey(ssl, the_key); - bev = bufferevent_openssl_socket_new( + bev = bufferevent_ssl_socket_new( base, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, ctx->flags); TT_BLATHER(("wm_transfer-%s(%p): accept", @@ -940,16 +678,16 @@ regress_bufferevent_openssl_wm(void *arg) tt_assert(listener); tt_assert(evconnlistener_get_fd(listener) >= 0); - ssl = SSL_new(get_ssl_ctx()); + ssl = SSL_new(get_ssl_ctx(SSL_IS_CLIENT)); tt_assert(ssl); if (type & REGRESS_OPENSSL_FILTER) { bev = bufferevent_socket_new(data->base, -1, client.flags); tt_assert(bev); - bev = bufferevent_openssl_filter_new( + bev = bufferevent_ssl_filter_new( base, bev, ssl, BUFFEREVENT_SSL_CONNECTING, client.flags); } else { - bev = bufferevent_openssl_socket_new( + bev = bufferevent_ssl_socket_new( data->base, -1, ssl, BUFFEREVENT_SSL_CONNECTING, client.flags); @@ -988,7 +726,7 @@ end: event_base_loop(base, EVLOOP_ONCE); } -struct testcase_t ssl_testcases[] = { +struct testcase_t TESTCASES_NAME[] = { #define T(a) ((void *)(a)) { "bufferevent_socketpair", regress_bufferevent_openssl, TT_ISOLATED, &ssl_setup, T(REGRESS_OPENSSL_SOCKETPAIR) }, @@ -1057,12 +795,10 @@ struct testcase_t ssl_testcases[] = { { "bufferevent_socketpair_timeout_freed_fd", regress_bufferevent_openssl, TT_ISOLATED, &ssl_setup, T(REGRESS_OPENSSL_SOCKETPAIR | REGRESS_OPENSSL_TIMEOUT | REGRESS_OPENSSL_FREED | REGRESS_OPENSSL_FD) }, - { "bufferevent_connect", regress_bufferevent_openssl_connect, TT_FORK|TT_NEED_BASE, &ssl_setup, NULL }, { "bufferevent_connect_sleep", regress_bufferevent_openssl_connect, TT_FORK|TT_NEED_BASE, &ssl_setup, T(REGRESS_OPENSSL_SLEEP) }, - { "bufferevent_wm", regress_bufferevent_openssl_wm, TT_FORK|TT_NEED_BASE, &ssl_setup, NULL }, { "bufferevent_wm_filter", regress_bufferevent_openssl_wm,