From 7296fb5b151920a4a8d272da69196df8ca155aa1 Mon Sep 17 00:00:00 2001 From: somdoron Date: Sun, 29 Sep 2019 18:30:37 +0300 Subject: [PATCH] problem: unsecured websocket is rarely used in production Solution: support websocket with tls (wss) --- .gitignore | 1 + .travis.yml | 11 +- Makefile.am | 21 +++- builds/valgrind/ci_build.sh | 4 + ci_build.sh | 4 + configure.ac | 33 ++++-- include/zmq.h | 6 ++ src/address.cpp | 10 ++ src/address.hpp | 3 + src/ctx.cpp | 12 +++ src/options.cpp | 23 ++++- src/options.hpp | 7 ++ src/session_base.cpp | 26 ++++- src/session_base.hpp | 5 + src/socket_base.cpp | 15 ++- src/stream_engine_base.cpp | 27 +++-- src/stream_engine_base.hpp | 4 +- src/ws_connecter.cpp | 27 ++++- src/ws_connecter.hpp | 7 +- src/ws_engine.cpp | 39 ++++--- src/ws_engine.hpp | 2 +- src/ws_listener.cpp | 45 +++++++- src/ws_listener.hpp | 14 ++- src/wss_engine.cpp | 194 +++++++++++++++++++++++++++++++++++ src/wss_engine.hpp | 66 ++++++++++++ src/zmtp_engine.cpp | 9 +- tests/test_wss_transport.cpp | 150 +++++++++++++++++++++++++++ 27 files changed, 705 insertions(+), 60 deletions(-) create mode 100644 src/wss_engine.cpp create mode 100644 src/wss_engine.hpp create mode 100644 tests/test_wss_transport.cpp diff --git a/.gitignore b/.gitignore index f21c5e17..402eb661 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ test_xpub_verbose test_mock_pub_sub test_proxy_hwm test_ws_transport +test_wss_transport unittest_ip_resolver unittest_mtrie unittest_poller diff --git a/.travis.yml b/.travis.yml index 30b2113a..aaebff3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,12 +50,14 @@ matrix: apt: packages: - lcov - - env: BUILD_TYPE=valgrind CURVE=tweetnacl DRAFT=enabled + - env: BUILD_TYPE=valgrind CURVE=tweetnacl DRAFT=enabled TLS=enabled os: linux + dist: xenial addons: apt: packages: - valgrind + - libgnutls-dev - env: BUILD_TYPE=default CURVE=libsodium GSSAPI=enabled PGM=enabled NORM=enabled os: linux addons: @@ -70,6 +72,13 @@ matrix: - libsodium-dev - asciidoc - xmlto + - env: BUILD_TYPE=default DRAFT=enabled TLS=enabled + os: linux + dist: xenial + addons: + apt: + packages: + - libgnutls-dev - env: BUILD_TYPE=default CURVE=libsodium DRAFT=enabled GSSAPI=enabled PGM=enabled NORM=enabled TIPC=enabled USE_NSS=yes os: linux addons: diff --git a/Makefile.am b/Makefile.am index 1b0e282a..a0a32586 100644 --- a/Makefile.am +++ b/Makefile.am @@ -305,6 +305,12 @@ src_libzmq_la_SOURCES += \ external/sha1/sha1.h endif +if HAVE_WSS +src_libzmq_la_SOURCES += \ + src/wss_engine.cpp \ + src/wss_engine.hpp +endif + if ON_MINGW src_libzmq_la_LDFLAGS = \ -no-undefined \ @@ -342,11 +348,16 @@ src_libzmq_la_CXXFLAGS = @LIBZMQ_EXTRA_CXXFLAGS@ $(CODE_COVERAGE_CXXFLAGS) \ $(LIBUNWIND_CFLAGS) src_libzmq_la_LIBADD = $(CODE_COVERAGE_LDFLAGS) $(LIBUNWIND_LIBS) -if HAVE_WS +if USE_NSS src_libzmq_la_CPPFLAGS += ${NSS3_CFLAGS} src_libzmq_la_LIBADD += ${NSS3_LIBS} endif +if USE_GNUTLS +src_libzmq_la_CPPFLAGS += ${GNUTLS_CFLAGS} +src_libzmq_la_LIBADD += ${GNUTLS_LIBS} +endif + if USE_LIBSODIUM src_libzmq_la_CPPFLAGS += ${sodium_CFLAGS} src_libzmq_la_LIBADD += ${sodium_LIBS} @@ -850,6 +861,14 @@ tests_test_ws_transport_LDADD = ${TESTUTIL_LIBS} src/libzmq.la ${NSS3_LIBS} tests_test_ws_transport_CPPFLAGS = ${TESTUTIL_CPPFLAGS} ${NSS3_CFLAGS} endif +if HAVE_WSS +test_apps += \ + tests/test_wss_transport +tests_test_wss_transport_SOURCES = tests/test_wss_transport.cpp +tests_test_wss_transport_LDADD = ${TESTUTIL_LIBS} src/libzmq.la ${GNUTLS_LIBS} +tests_test_wss_transport_CPPFLAGS = ${TESTUTIL_CPPFLAGS} ${GNUTLS_CFLAGS} +endif + if !ON_MINGW if !ON_CYGWIN test_apps += \ diff --git a/builds/valgrind/ci_build.sh b/builds/valgrind/ci_build.sh index 4e816158..cc7db104 100755 --- a/builds/valgrind/ci_build.sh +++ b/builds/valgrind/ci_build.sh @@ -14,6 +14,10 @@ CONFIG_OPTS+=("PKG_CONFIG_PATH=${BUILD_PREFIX}/lib/pkgconfig") CONFIG_OPTS+=("--prefix=${BUILD_PREFIX}") CONFIG_OPTS+=("--enable-valgrind") +if [ -n "$TLS" ] && [ "$TLS" == "enabled" ]; then + CONFIG_OPTS+=("--with-tls=yes") +fi + if [ -z $CURVE ]; then CONFIG_OPTS+=("--disable-curve") elif [ $CURVE == "libsodium" ]; then diff --git a/ci_build.sh b/ci_build.sh index 849c4560..962d4f5c 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -60,6 +60,10 @@ if [ $BUILD_TYPE == "default" ]; then CONFIG_OPTS+=("--with-poller=${POLLER}") fi + if [ -n "$TLS" ] && [ "$TLS" == "enabled" ]; then + CONFIG_OPTS+=("--with-tls=yes") + fi + if [ -z $DRAFT ] || [ $DRAFT == "disabled" ]; then CONFIG_OPTS+=("--enable-drafts=no") elif [ $DRAFT == "enabled" ]; then diff --git a/configure.ac b/configure.ac index 1df3f675..6969fdf2 100644 --- a/configure.ac +++ b/configure.ac @@ -553,7 +553,7 @@ AM_CONDITIONAL(HAVE_CURVE, test "x$curve_library" != "x") AM_CONDITIONAL(USE_WEPOLL, test "$poller" = "wepoll") # Check requiring packages for WebSocket -sha1_library="" +ws_crypto_library="" AC_ARG_ENABLE([ws], [AS_HELP_STRING([--disable-ws], [Disable WebSocket transport [default=no]])]) @@ -561,28 +561,43 @@ AC_ARG_ENABLE([ws], AC_ARG_WITH([nss], [AS_HELP_STRING([--with-nss], [use nss instead of built-in sha1 [default=no]])]) +AC_ARG_WITH([tls], + [AS_HELP_STRING([--with-tls], [Enable TLS (WSS transport) [default=no]])]) + if test "x$enable_ws" != "xno"; then - if test "x$with_nss" = "xyes"; then + if test "x$with_tls" = "xyes"; then + PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.1.4], [ + ws_crypto_library="gnutls" + AC_DEFINE(ZMQ_USE_GNUTLS, [1], [Use GNUTLS for TLS]) + AC_DEFINE(ZMQ_HAVE_WS, [1], [Using websocket]) + AC_DEFINE(ZMQ_HAVE_WSS, [1], [WSS enabled]) + AC_MSG_NOTICE(Using gnutls) + ],[ + AC_MSG_ERROR([GnuTLS is not installed. Install it, then run configure again]) + ]) + elif test "x$with_nss" = "xyes"; then PKG_CHECK_MODULES([NSS3], [nss], [ PKGCFG_NAMES_PRIVATE="$PKGCFG_NAMES_PRIVATE nss" AC_DEFINE(ZMQ_USE_NSS, [1], [Using NSS]) AC_DEFINE(ZMQ_HAVE_WS, [1], [Using websocket]) - sha1_library="nss" + ws_crypto_library="nss" AC_MSG_NOTICE(Using NSS) ], [ - AC_MSG_ERROR(nss is not installed. Install it, then run configure again) + AC_MSG_ERROR([nss is not installed. Install it, then run configure again]) ]) else - AC_DEFINE(ZMQ_USE_BUILTIN_SHA1, [1], [Using built-in sha1]) AC_DEFINE(ZMQ_HAVE_WS, [1], [Using websocket]) - sha1_library="builtin" + AC_DEFINE(ZMQ_USE_BUILTIN_SHA1, [1], [Using built-in sha1]) AC_MSG_NOTICE(Using builting SHA1) + ws_crypto_library="builtin" fi fi -AM_CONDITIONAL(HAVE_WS, test "x$sha1_library" != "x") -AM_CONDITIONAL(USE_NSS, test "x$sha1_library" = "xnss") -AM_CONDITIONAL(USE_BUILTIN_SHA1, test "x$sha1_library" = "xbuiltin") +AM_CONDITIONAL(HAVE_WS, test "x$ws_crypto_library" != "x") +AM_CONDITIONAL(USE_NSS, test "x$ws_crypto_library" = "xnss") +AM_CONDITIONAL(USE_BUILTIN_SHA1, test "x$ws_crypto_library" = "xbuiltin") +AM_CONDITIONAL(USE_GNUTLS, test "x$ws_crypto_library" = "xgnutls") +AM_CONDITIONAL(HAVE_WSS, test "x$ws_crypto_library" = "xgnutls") # build using pgm have_pgm_library="no" diff --git a/include/zmq.h b/include/zmq.h index 03be03c2..a005aedd 100644 --- a/include/zmq.h +++ b/include/zmq.h @@ -670,6 +670,12 @@ ZMQ_EXPORT void zmq_threadclose (void *thread_); #define ZMQ_SOCKS_PASSWORD 100 #define ZMQ_IN_BATCH_SIZE 101 #define ZMQ_OUT_BATCH_SIZE 102 +#define ZMQ_WSS_KEY_PEM 103 +#define ZMQ_WSS_CERT_PEM 104 +#define ZMQ_WSS_TRUST_PEM 105 +#define ZMQ_WSS_HOSTNAME 106 +#define ZMQ_WSS_TRUST_SYSTEM 107 + /* DRAFT Context options */ #define ZMQ_ZERO_COPY_RECV 10 diff --git a/src/address.cpp b/src/address.cpp index 4763ee44..23138377 100644 --- a/src/address.cpp +++ b/src/address.cpp @@ -68,6 +68,12 @@ zmq::address_t::~address_t () } #endif +#ifdef ZMQ_HAVE_WSS + else if (protocol == protocol_name::wss) { + LIBZMQ_DELETE (resolved.ws_addr); + } +#endif + #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS else if (protocol == protocol_name::ipc) { @@ -96,6 +102,10 @@ int zmq::address_t::to_string (std::string &addr_) const if (protocol == protocol_name::ws && resolved.ws_addr) return resolved.ws_addr->to_string (addr_); #endif +#ifdef ZMQ_HAVE_WSS + if (protocol == protocol_name::wss && resolved.ws_addr) + return resolved.ws_addr->to_string (addr_); +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS if (protocol == protocol_name::ipc && resolved.ipc_addr) diff --git a/src/address.hpp b/src/address.hpp index b38e75b4..1f683d6c 100644 --- a/src/address.hpp +++ b/src/address.hpp @@ -64,6 +64,9 @@ static const char udp[] = "udp"; #ifdef ZMQ_HAVE_WS static const char ws[] = "ws"; #endif +#ifdef ZMQ_HAVE_WSS +static const char wss[] = "wss"; +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS static const char ipc[] = "ipc"; diff --git a/src/ctx.cpp b/src/ctx.cpp index 9018ba7b..83627dbf 100644 --- a/src/ctx.cpp +++ b/src/ctx.cpp @@ -56,6 +56,10 @@ #include #endif +#ifdef ZMQ_USE_GNUTLS +#include +#endif + #define ZMQ_CTX_TAG_VALUE_GOOD 0xabadcafe #define ZMQ_CTX_TAG_VALUE_BAD 0xdeadbeef @@ -95,6 +99,10 @@ zmq::ctx_t::ctx_t () : #ifdef ZMQ_USE_NSS NSS_NoDB_Init (NULL); #endif + +#ifdef ZMQ_USE_GNUTLS + gnutls_global_init (); +#endif } bool zmq::ctx_t::check_tag () @@ -131,6 +139,10 @@ zmq::ctx_t::~ctx_t () NSS_Shutdown (); #endif +#ifdef ZMQ_USE_GNUTLS + gnutls_global_deinit (); +#endif + // Remove the tag, so that the object is considered dead. _tag = ZMQ_CTX_TAG_VALUE_BAD; } diff --git a/src/options.cpp b/src/options.cpp index c95b63a9..8d0041e8 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -247,7 +247,8 @@ zmq::options_t::options_t () : out_batch_size (8192), zero_copy (true), router_notify (0), - monitor_event_version (1) + monitor_event_version (1), + wss_trust_system (false) { memset (curve_public_key, 0, CURVE_KEYSIZE); memset (curve_secret_key, 0, CURVE_KEYSIZE); @@ -784,6 +785,26 @@ int zmq::options_t::setsockopt (int option_, return 0; } break; + + case ZMQ_WSS_KEY_PEM: + // TODO: check if valid certificate + wss_key_pem = std::string ((char *) optval_, optvallen_); + return 0; + case ZMQ_WSS_CERT_PEM: + // TODO: check if valid certificate + wss_cert_pem = std::string ((char *) optval_, optvallen_); + return 0; + case ZMQ_WSS_TRUST_PEM: + // TODO: check if valid certificate + wss_trust_pem = std::string ((char *) optval_, optvallen_); + return 0; + case ZMQ_WSS_HOSTNAME: + wss_hostname = std::string ((char *) optval_, optvallen_); + return 0; + case ZMQ_WSS_TRUST_SYSTEM: + return do_setsockopt_int_as_bool_strict (optval_, optvallen_, + &wss_trust_system); + #endif default: diff --git a/src/options.hpp b/src/options.hpp index 8c87d8f5..be8084eb 100644 --- a/src/options.hpp +++ b/src/options.hpp @@ -286,6 +286,13 @@ struct options_t // Version of monitor events to emit int monitor_event_version; + + // WSS Keys + std::string wss_key_pem; + std::string wss_cert_pem; + std::string wss_trust_pem; + std::string wss_hostname; + bool wss_trust_system; }; inline bool get_effective_conflate_option (const options_t &options) diff --git a/src/session_base.cpp b/src/session_base.cpp index b9f7aa10..1812afbb 100644 --- a/src/session_base.cpp +++ b/src/session_base.cpp @@ -114,8 +114,14 @@ zmq::session_base_t::session_base_t (class io_thread_t *io_thread_, _socket (socket_), _io_thread (io_thread_), _has_linger_timer (false), - _addr (addr_) + _addr (addr_), + _wss_hostname (NULL) { + if (options_.wss_hostname.length () > 0) { + _wss_hostname = (char *) malloc (options_.wss_hostname.length () + 1); + assert (_wss_hostname); + strcpy (_wss_hostname, options_.wss_hostname.c_str ()); + } } const zmq::endpoint_uri_pair_t &zmq::session_base_t::get_endpoint () const @@ -138,6 +144,9 @@ zmq::session_base_t::~session_base_t () if (_engine) _engine->terminate (); + if (_wss_hostname) + free (_wss_hostname); + LIBZMQ_DELETE (_addr); } @@ -563,6 +572,10 @@ zmq::session_base_t::connecter_factory_entry_t connecter_factory_entry_t (protocol_name::ws, &zmq::session_base_t::create_connecter_ws), #endif +#ifdef ZMQ_HAVE_WSS + connecter_factory_entry_t (protocol_name::wss, + &zmq::session_base_t::create_connecter_wss), +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS connecter_factory_entry_t (protocol_name::ipc, @@ -690,7 +703,16 @@ zmq::own_t *zmq::session_base_t::create_connecter_ws (io_thread_t *io_thread_, bool wait_) { return new (std::nothrow) - ws_connecter_t (io_thread_, this, options, _addr, wait_); + ws_connecter_t (io_thread_, this, options, _addr, wait_, false, NULL); +} +#endif + +#ifdef ZMQ_HAVE_WSS +zmq::own_t *zmq::session_base_t::create_connecter_wss (io_thread_t *io_thread_, + bool wait_) +{ + return new (std::nothrow) ws_connecter_t (io_thread_, this, options, _addr, + wait_, true, _wss_hostname); } #endif diff --git a/src/session_base.hpp b/src/session_base.hpp index 2dd6a458..9dac32f1 100644 --- a/src/session_base.hpp +++ b/src/session_base.hpp @@ -120,6 +120,7 @@ class session_base_t : public own_t, public io_object_t, public i_pipe_events own_t *create_connecter_ipc (io_thread_t *io_thread_, bool wait_); own_t *create_connecter_tcp (io_thread_t *io_thread_, bool wait_); own_t *create_connecter_ws (io_thread_t *io_thread_, bool wait_); + own_t *create_connecter_wss (io_thread_t *io_thread_, bool wait_); typedef void (session_base_t::*start_connecting_fun_t) ( io_thread_t *io_thread); @@ -191,6 +192,10 @@ class session_base_t : public own_t, public io_object_t, public i_pipe_events // Protocol and address to use when connecting. address_t *_addr; + // TLS handshake, we need to take a copy when the session is created, + // in order to maintain the value at the creation time + char *_wss_hostname; + session_base_t (const session_base_t &); const session_base_t &operator= (const session_base_t &); }; diff --git a/src/socket_base.cpp b/src/socket_base.cpp index fc4787a8..05c77f43 100644 --- a/src/socket_base.cpp +++ b/src/socket_base.cpp @@ -338,6 +338,9 @@ int zmq::socket_base_t::check_protocol (const std::string &protocol_) const #ifdef ZMQ_HAVE_WS && protocol_ != protocol_name::ws #endif +#ifdef ZMQ_HAVE_WSS + && protocol_ != protocol_name::wss +#endif #if defined ZMQ_HAVE_OPENPGM // pgm/epgm transports only available if 0MQ is compiled with OpenPGM. && protocol_ != "pgm" @@ -635,9 +638,15 @@ int zmq::socket_base_t::bind (const char *endpoint_uri_) } #ifdef ZMQ_HAVE_WS +#ifdef ZMQ_HAVE_WSS + if (protocol == protocol_name::ws || protocol == protocol_name::wss) { + ws_listener_t *listener = new (std::nothrow) ws_listener_t ( + io_thread, this, options, protocol == protocol_name::wss); +#else if (protocol == protocol_name::ws) { ws_listener_t *listener = - new (std::nothrow) ws_listener_t (io_thread, this, options); + new (std::nothrow) ws_listener_t (io_thread, this, options, false); +#endif alloc_assert (listener); rc = listener->set_local_address (address.c_str ()); if (rc != 0) { @@ -895,7 +904,11 @@ int zmq::socket_base_t::connect (const char *endpoint_uri_) paddr->resolved.tcp_addr = NULL; } #ifdef ZMQ_HAVE_WS +#ifdef ZMQ_HAVE_WSS + else if (protocol == protocol_name::ws || protocol == protocol_name::wss) { +#else else if (protocol == protocol_name::ws) { +#endif paddr->resolved.ws_addr = new (std::nothrow) ws_address_t (); alloc_assert (paddr->resolved.ws_addr); rc = paddr->resolved.ws_addr->resolve (address.c_str (), false, diff --git a/src/stream_engine_base.cpp b/src/stream_engine_base.cpp index e81d1267..66c989bf 100644 --- a/src/stream_engine_base.cpp +++ b/src/stream_engine_base.cpp @@ -275,14 +275,8 @@ bool zmq::stream_engine_base_t::in_event_internal () size_t bufsize = 0; _decoder->get_buffer (&_inpos, &bufsize); - const int rc = tcp_read (_inpos, bufsize); + int rc = read (_inpos, bufsize); - if (rc == 0) { - // connection closed by peer - errno = EPIPE; - error (connection_error); - return false; - } if (rc == -1) { if (errno != EAGAIN) { error (connection_error); @@ -370,7 +364,7 @@ void zmq::stream_engine_base_t::out_event () // arbitrarily large. However, we assume that underlying TCP layer has // limited transmission buffer and thus the actual number of bytes // written should be reasonably modest. - const int nbytes = tcp_write (_s, _outpos, _outsize); + const int nbytes = write (_outpos, _outsize); // IO error has occurred. We stop waiting for output events. // The engine is not terminated until we detect input error; @@ -744,7 +738,20 @@ void zmq::stream_engine_base_t::timer_event (int id_) assert (false); } -int zmq::stream_engine_base_t::tcp_read (void *data_, size_t size_) +int zmq::stream_engine_base_t::read (void *data_, size_t size_) { - return zmq::tcp_read (_s, data_, size_); + int rc = zmq::tcp_read (_s, data_, size_); + + if (rc == 0) { + // connection closed by peer + errno = EPIPE; + return -1; + } + + return rc; +} + +int zmq::stream_engine_base_t::write (const void *data_, size_t size_) +{ + return zmq::tcp_write (_s, data_, size_); } diff --git a/src/stream_engine_base.hpp b/src/stream_engine_base.hpp index 2c6e6c3d..1639ecf5 100644 --- a/src/stream_engine_base.hpp +++ b/src/stream_engine_base.hpp @@ -90,7 +90,6 @@ class stream_engine_base_t : public io_object_t, public i_engine int decode_and_push (msg_t *msg_); void set_handshake_timer (); - int tcp_read (void *data_, size_t size_); virtual bool handshake () { return true; }; virtual void plug_internal (){}; @@ -100,6 +99,9 @@ class stream_engine_base_t : public io_object_t, public i_engine virtual int process_heartbeat_message (msg_t *msg_) { return -1; }; virtual int produce_pong_message (msg_t *msg_) { return -1; }; + virtual int read (void *data, size_t size_); + virtual int write (const void *data_, size_t size_); + void set_pollout () { io_object_t::set_pollout (_handle); } void set_pollin () { io_object_t::set_pollin (_handle); } session_base_t *session () { return _session; } diff --git a/src/ws_connecter.cpp b/src/ws_connecter.cpp index 48724356..1801945a 100644 --- a/src/ws_connecter.cpp +++ b/src/ws_connecter.cpp @@ -42,6 +42,10 @@ #include "session_base.hpp" #include "ws_engine.hpp" +#ifdef ZMQ_HAVE_WSS +#include "wss_engine.hpp" +#endif + #if !defined ZMQ_HAVE_WINDOWS #include #include @@ -67,12 +71,15 @@ zmq::ws_connecter_t::ws_connecter_t (class io_thread_t *io_thread_, class session_base_t *session_, const options_t &options_, address_t *addr_, - bool delayed_start_) : + bool delayed_start_, + bool wss_, + const char *tls_hostname_) : stream_connecter_base_t ( io_thread_, session_, options_, addr_, delayed_start_), - _connect_timer_started (false) + _connect_timer_started (false), + _wss (wss_), + _hostname (tls_hostname_) { - zmq_assert (_addr->protocol == protocol_name::ws); } zmq::ws_connecter_t::~ws_connecter_t () @@ -264,8 +271,18 @@ void zmq::ws_connecter_t::create_engine (fd_t fd, endpoint_type_connect); // Create the engine object for this connection. - ws_engine_t *engine = new (std::nothrow) - ws_engine_t (fd, options, endpoint_pair, *_addr->resolved.ws_addr, true); + i_engine *engine = NULL; + if (_wss) +#ifdef ZMQ_HAVE_WSS + engine = new (std::nothrow) + wss_engine_t (fd, options, endpoint_pair, *_addr->resolved.ws_addr, + true, NULL, _hostname); +#else + assert (false); +#endif + else + engine = new (std::nothrow) ws_engine_t ( + fd, options, endpoint_pair, *_addr->resolved.ws_addr, true); alloc_assert (engine); // Attach the engine to the corresponding session object. diff --git a/src/ws_connecter.hpp b/src/ws_connecter.hpp index 2faa3a6a..bc779826 100644 --- a/src/ws_connecter.hpp +++ b/src/ws_connecter.hpp @@ -45,7 +45,9 @@ class ws_connecter_t : public stream_connecter_base_t zmq::session_base_t *session_, const options_t &options_, address_t *addr_, - bool delayed_start_); + bool delayed_start_, + bool wss_, + const char *tls_hostname_); ~ws_connecter_t (); protected: @@ -88,6 +90,9 @@ class ws_connecter_t : public stream_connecter_base_t ws_connecter_t (const ws_connecter_t &); const ws_connecter_t &operator= (const ws_connecter_t &); + + bool _wss; + const char *_hostname; }; } diff --git a/src/ws_engine.cpp b/src/ws_engine.cpp index ffc15652..a20e12c2 100644 --- a/src/ws_engine.cpp +++ b/src/ws_engine.cpp @@ -33,8 +33,12 @@ along with this program. If not, see . #include #include #define SHA_DIGEST_LENGTH 20 -#else +#elif defined ZMQ_USE_BUILTIN_SHA1 #include "../external/sha1/sha1.h" +#elif defined ZMQ_USE_GNUTLS +#define SHA_DIGEST_LENGTH 20 +#include +#include #endif #if !defined ZMQ_HAVE_WINDOWS @@ -105,7 +109,7 @@ zmq::ws_engine_t::~ws_engine_t () { } -void zmq::ws_engine_t::plug_internal () +void zmq::ws_engine_t::start_ws_handshake () { if (_client) { unsigned char nonce[16]; @@ -135,7 +139,11 @@ void zmq::ws_engine_t::plug_internal () _outsize = size; set_pollout (); } +} +void zmq::ws_engine_t::plug_internal () +{ + start_ws_handshake (); set_pollin (); in_event (); } @@ -147,6 +155,7 @@ int zmq::ws_engine_t::routing_id_msg (msg_t *msg_) if (_options.routing_id_size > 0) memcpy (msg_->data (), _options.routing_id, _options.routing_id_size); _next_msg = &ws_engine_t::pull_msg_from_session; + return 0; } @@ -197,12 +206,8 @@ bool zmq::ws_engine_t::handshake () bool zmq::ws_engine_t::server_handshake () { - int nbytes = tcp_read (_read_buffer, WS_BUFFER_SIZE); - if (nbytes == 0) { - errno = EPIPE; - error (zmq::i_engine::connection_error); - return false; - } else if (nbytes == -1) { + int nbytes = read (_read_buffer, WS_BUFFER_SIZE); + if (nbytes == -1) { if (errno != EAGAIN) error (zmq::i_engine::connection_error); return false; @@ -470,12 +475,8 @@ bool zmq::ws_engine_t::server_handshake () bool zmq::ws_engine_t::client_handshake () { - int nbytes = tcp_read (_read_buffer, WS_BUFFER_SIZE); - if (nbytes == 0) { - errno = EPIPE; - error (zmq::i_engine::connection_error); - return false; - } else if (nbytes == -1) { + int nbytes = read (_read_buffer, WS_BUFFER_SIZE); + if (nbytes == -1) { if (errno != EAGAIN) error (zmq::i_engine::connection_error); return false; @@ -868,12 +869,20 @@ static void compute_accept_key (char *key, unsigned char *hash) (unsigned int) strlen (magic_string)); HASH_End (ctx, hash, &len, SHA_DIGEST_LENGTH); HASH_Destroy (ctx); -#else +#elif defined ZMQ_USE_BUILTIN_SHA1 sha1_ctxt ctx; SHA1_Init (&ctx); SHA1_Update (&ctx, (unsigned char *) key, strlen (key)); SHA1_Update (&ctx, (unsigned char *) magic_string, strlen (magic_string)); SHA1_Final (hash, &ctx); +#elif defined ZMQ_USE_GNUTLS + gnutls_hash_hd_t hd; + gnutls_hash_init (&hd, GNUTLS_DIG_SHA1); + gnutls_hash (hd, key, strlen (key)); + gnutls_hash (hd, magic_string, strlen (magic_string)); + gnutls_hash_deinit (hd, hash); +#else +#error "No sha1 implementation set" #endif } diff --git a/src/ws_engine.hpp b/src/ws_engine.hpp index 63adb51b..e3a15048 100644 --- a/src/ws_engine.hpp +++ b/src/ws_engine.hpp @@ -36,7 +36,6 @@ #include "stream_engine_base.hpp" #include "ws_address.hpp" - #define WS_BUFFER_SIZE 8192 #define MAX_HEADER_NAME_LENGTH 1024 #define MAX_HEADER_VALUE_LENGTH 2048 @@ -138,6 +137,7 @@ class ws_engine_t : public stream_engine_base_t protected: bool handshake (); void plug_internal (); + void start_ws_handshake (); private: int routing_id_msg (msg_t *msg_); diff --git a/src/ws_listener.cpp b/src/ws_listener.cpp index 0cfb7508..fd8fe236 100644 --- a/src/ws_listener.cpp +++ b/src/ws_listener.cpp @@ -44,6 +44,10 @@ #include "ws_engine.hpp" #include "session_base.hpp" +#ifdef ZMQ_HAVE_WSS +#include "wss_engine.hpp" +#endif + #ifndef ZMQ_HAVE_WINDOWS #include #include @@ -63,9 +67,33 @@ zmq::ws_listener_t::ws_listener_t (io_thread_t *io_thread_, socket_base_t *socket_, - const options_t &options_) : - stream_listener_base_t (io_thread_, socket_, options_) + const options_t &options_, + bool wss_) : + stream_listener_base_t (io_thread_, socket_, options_), + _wss (wss_) { +#ifdef ZMQ_HAVE_WSS + if (_wss) { + int rc = gnutls_certificate_allocate_credentials (&_tls_cred); + assert (rc == GNUTLS_E_SUCCESS); + + gnutls_datum_t cert = {(unsigned char *) options_.wss_cert_pem.c_str (), + (unsigned int) options_.wss_cert_pem.length ()}; + gnutls_datum_t key = {(unsigned char *) options_.wss_key_pem.c_str (), + (unsigned int) options_.wss_key_pem.length ()}; + rc = gnutls_certificate_set_x509_key_mem (_tls_cred, &cert, &key, + GNUTLS_X509_FMT_PEM); + assert (rc == GNUTLS_E_SUCCESS); + } +#endif +} + +zmq::ws_listener_t::~ws_listener_t () +{ +#ifdef ZMQ_HAVE_WSS + if (_wss) + gnutls_certificate_free_credentials (_tls_cred); +#endif } void zmq::ws_listener_t::in_event () @@ -256,8 +284,17 @@ void zmq::ws_listener_t::create_engine (fd_t fd) get_socket_name (fd, socket_end_local), get_socket_name (fd, socket_end_remote), endpoint_type_bind); - ws_engine_t *engine = new (std::nothrow) - ws_engine_t (fd, options, endpoint_pair, _address, false); + i_engine *engine = NULL; + if (_wss) +#ifdef ZMQ_HAVE_WSS + engine = new (std::nothrow) wss_engine_t ( + fd, options, endpoint_pair, _address, false, _tls_cred, NULL); +#else + assert (false); +#endif + else + engine = new (std::nothrow) + ws_engine_t (fd, options, endpoint_pair, _address, false); alloc_assert (engine); // Choose I/O thread to run connecter in. Given that we are already diff --git a/src/ws_listener.hpp b/src/ws_listener.hpp index 7f74c4be..a40d193e 100644 --- a/src/ws_listener.hpp +++ b/src/ws_listener.hpp @@ -34,6 +34,10 @@ #include "ws_address.hpp" #include "stream_listener_base.hpp" +#if ZMQ_USE_GNUTLS +#include +#endif + namespace zmq { class ws_listener_t : public stream_listener_base_t @@ -41,7 +45,10 @@ class ws_listener_t : public stream_listener_base_t public: ws_listener_t (zmq::io_thread_t *io_thread_, zmq::socket_base_t *socket_, - const options_t &options_); + const options_t &options_, + bool wss_); + + ~ws_listener_t (); // Set address to listen on. int set_local_address (const char *addr_); @@ -67,6 +74,11 @@ class ws_listener_t : public stream_listener_base_t ws_listener_t (const ws_listener_t &); const ws_listener_t &operator= (const ws_listener_t &); + + bool _wss; +#if ZMQ_HAVE_WSS + gnutls_certificate_credentials_t _tls_cred; +#endif }; } diff --git a/src/wss_engine.cpp b/src/wss_engine.cpp new file mode 100644 index 00000000..62018e53 --- /dev/null +++ b/src/wss_engine.cpp @@ -0,0 +1,194 @@ +/* +Copyright (c) 2007-2019 Contributors as noted in the AUTHORS file + +This file is part of libzmq, the ZeroMQ core engine in C++. + +libzmq is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License (LGPL) as published +by the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +As a special exception, the Contributors give you permission to link +this library with independent modules to produce an executable, +regardless of the license terms of these independent modules, and to +copy and distribute the resulting executable under terms of your choice, +provided that you also meet, for each linked independent module, the +terms and conditions of the license of that module. An independent +module is a module which is not derived from or based on this library. +If you modify this library, you must extend this exception to your +version of the library. + +libzmq is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +*/ + +#include "precompiled.hpp" +#include "wss_engine.hpp" + +static int verify_certificate_callback (gnutls_session_t session) +{ + unsigned int status; + const char *hostname; + + // read hostname + hostname = (const char *) gnutls_session_get_ptr (session); + + int rc = gnutls_certificate_verify_peers3 (session, hostname, &status); + assert (rc >= 0); + + if (status != 0) { + // TODO: somehow log the error + // Certificate is not trusted + return GNUTLS_E_CERTIFICATE_ERROR; + } + + // notify gnutls to continue handshake normally + return 0; +} + + +zmq::wss_engine_t::wss_engine_t (fd_t fd_, + const options_t &options_, + const endpoint_uri_pair_t &endpoint_uri_pair_, + ws_address_t &address_, + bool client_, + void *tls_server_cred_, + const char *hostname_) : + ws_engine_t (fd_, options_, endpoint_uri_pair_, address_, client_), + _established (false), + _tls_client_cred (NULL) +{ + int rc = 0; + + if (client_) { + // TODO: move to session_base, to allow changing the socket options between connect calls + rc = gnutls_certificate_allocate_credentials (&_tls_client_cred); + assert (rc == 0); + + if (options_.wss_trust_system) + gnutls_certificate_set_x509_system_trust (_tls_client_cred); + + if (options_.wss_trust_pem.length () > 0) { + gnutls_datum_t trust = { + (unsigned char *) options_.wss_trust_pem.c_str (), + (unsigned int) options_.wss_trust_pem.length ()}; + rc = gnutls_certificate_set_x509_trust_mem ( + _tls_client_cred, &trust, GNUTLS_X509_FMT_PEM); + assert (rc >= 0); + } + + gnutls_certificate_set_verify_function (_tls_client_cred, + verify_certificate_callback); + + rc = gnutls_init (&_tls_session, GNUTLS_CLIENT | GNUTLS_NONBLOCK); + assert (rc == GNUTLS_E_SUCCESS); + + if (hostname_) + gnutls_server_name_set (_tls_session, GNUTLS_NAME_DNS, hostname_, + strlen (hostname_)); + + gnutls_session_set_ptr (_tls_session, (void *) hostname_); + + rc = gnutls_credentials_set (_tls_session, GNUTLS_CRD_CERTIFICATE, + _tls_client_cred); + assert (rc == GNUTLS_E_SUCCESS); + } else { + assert (tls_server_cred_); + + rc = gnutls_init (&_tls_session, GNUTLS_SERVER | GNUTLS_NONBLOCK); + assert (rc == GNUTLS_E_SUCCESS); + + rc = gnutls_credentials_set (_tls_session, GNUTLS_CRD_CERTIFICATE, + tls_server_cred_); + assert (rc == GNUTLS_E_SUCCESS); + } + + gnutls_set_default_priority (_tls_session); + gnutls_transport_set_int (_tls_session, fd_); + gnutls_handshake_set_timeout (_tls_session, + GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); +} + +zmq::wss_engine_t::~wss_engine_t () +{ + gnutls_deinit (_tls_session); + + if (_tls_client_cred) + gnutls_certificate_free_credentials (_tls_client_cred); +} + +void zmq::wss_engine_t::plug_internal () +{ + set_pollin (); + in_event (); +} + +bool zmq::wss_engine_t::handshake () +{ + if (!_established) { + int rc = gnutls_handshake (_tls_session); + + // TODO: when E_AGAIN is returned we might need to call gnutls_handshake for out_event as well, see gnutls_record_get_direction + + if (rc == GNUTLS_E_SUCCESS) { + start_ws_handshake (); + _established = true; + return false; + } else if (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED + || rc == GNUTLS_E_WARNING_ALERT_RECEIVED) + return false; + else { + error (zmq::i_engine::connection_error); + return false; + } + } + + return ws_engine_t::handshake (); +} + +int zmq::wss_engine_t::read (void *data_, size_t size_) +{ + ssize_t rc = gnutls_record_recv (_tls_session, data_, size_); + + if (rc == GNUTLS_E_REHANDSHAKE) { + gnutls_alert_send (_tls_session, GNUTLS_AL_WARNING, + GNUTLS_A_NO_RENEGOTIATION); + return 0; + } + + if (rc == GNUTLS_E_INTERRUPTED) { + errno = EINTR; + return -1; + } + + if (rc == GNUTLS_E_AGAIN) { + errno = EAGAIN; + return -1; + } + + // TODO: change return type to ssize_t (signed) + return rc; +} + +int zmq::wss_engine_t::write (const void *data_, size_t size_) +{ + ssize_t rc = gnutls_record_send (_tls_session, data_, size_); + + if (rc == GNUTLS_E_INTERRUPTED) { + errno = EINTR; + return -1; + } + + if (rc == GNUTLS_E_AGAIN) { + errno = EAGAIN; + return -1; + } + + // TODO: change return type to ssize_t (signed) + return rc; +} diff --git a/src/wss_engine.hpp b/src/wss_engine.hpp new file mode 100644 index 00000000..76c3cdb3 --- /dev/null +++ b/src/wss_engine.hpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2007-2019 Contributors as noted in the AUTHORS file + + This file is part of libzmq, the ZeroMQ core engine in C++. + + libzmq is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + As a special exception, the Contributors give you permission to link + this library with independent modules to produce an executable, + regardless of the license terms of these independent modules, and to + copy and distribute the resulting executable under terms of your choice, + provided that you also meet, for each linked independent module, the + terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. + If you modify this library, you must extend this exception to your + version of the library. + + libzmq is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef __ZMQ_WSS_ENGINE_HPP_INCLUDED__ +#define __ZMQ_WSS_ENGINE_HPP_INCLUDED__ + +#include +#include "ws_engine.hpp" + +#define WSS_BUFFER_SIZE 8192 + +namespace zmq +{ +class wss_engine_t : public ws_engine_t +{ + public: + wss_engine_t (fd_t fd_, + const options_t &options_, + const endpoint_uri_pair_t &endpoint_uri_pair_, + ws_address_t &address_, + bool client_, + void *tls_server_cred_, + const char *hostname_); + ~wss_engine_t (); + + protected: + bool handshake (); + void plug_internal (); + int read (void *data, size_t size_); + int write (const void *data_, size_t size_); + + + private: + bool _established; + gnutls_certificate_credentials_t _tls_client_cred; + gnutls_session_t _tls_session; +}; +} + +#endif diff --git a/src/zmtp_engine.cpp b/src/zmtp_engine.cpp index 3b5a9e84..43221327 100644 --- a/src/zmtp_engine.cpp +++ b/src/zmtp_engine.cpp @@ -148,13 +148,8 @@ int zmq::zmtp_engine_t::receive_greeting () { bool unversioned = false; while (_greeting_bytes_read < _greeting_size) { - const int n = tcp_read (_greeting_recv + _greeting_bytes_read, - _greeting_size - _greeting_bytes_read); - if (n == 0) { - errno = EPIPE; - error (connection_error); - return -1; - } + const int n = read (_greeting_recv + _greeting_bytes_read, + _greeting_size - _greeting_bytes_read); if (n == -1) { if (errno != EAGAIN) error (connection_error); diff --git a/tests/test_wss_transport.cpp b/tests/test_wss_transport.cpp new file mode 100644 index 00000000..8e6f065c --- /dev/null +++ b/tests/test_wss_transport.cpp @@ -0,0 +1,150 @@ +/* + Copyright (c) 2007-2016 Contributors as noted in the AUTHORS file + + This file is part of libzmq, the ZeroMQ core engine in C++. + + libzmq is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License (LGPL) as published + by the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + As a special exception, the Contributors give you permission to link + this library with independent modules to produce an executable, + regardless of the license terms of these independent modules, and to + copy and distribute the resulting executable under terms of your choice, + provided that you also meet, for each linked independent module, the + terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. + If you modify this library, you must extend this exception to your + version of the library. + + libzmq is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include +#include "testutil.hpp" +#include "testutil_unity.hpp" + +SETUP_TEARDOWN_TESTCONTEXT + +const char *key = + "-----BEGIN PRIVATE KEY-----\n" + "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\n" + "NX3nbNTMU7FmPoUgF8jm+hS9ecfWVf9c\n" + "-----END PRIVATE KEY-----"; + +const char *cert = + "-----BEGIN CERTIFICATE-----\n" + "MIIFjzCCA3egAwIBAgIUMVYFTVoSsqvKRSfeaBZfD0WlDhkwDQYJKoZIhvcNAQEL\n" + "BQAwVzELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE\n" + "CgwTRGVmYXVsdCBDb21wYW55IEx0ZDETMBEGA1UEAwwKemVyb21xLm9yZzAeFw0x\n" + "OTEwMDExMTEyMjVaFw0xOTEwMzExMTEyMjVaMFcxCzAJBgNVBAYTAlhYMRUwEwYD\n" + "VQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQx\n" + "EzARBgNVBAMMCnplcm9tcS5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" + "AoICAQCrXKFPWrRqbdNoltLhL8YYva5au+f3ntrOybMJmhWJdkXL1DxC5F6XDNNz\n" + "YET+WTrBpwfY1yX6OYZwBpnh9K/Kb00wJTkd6MxeqEV2eTp7iAt/BzmWNXIausyk\n" + "XuBuWq+M+oFXXlTbgFXLlqV8/B+1klxFuSjNxbDN+IBLgz7k86F2sAa6RoZ2jHWs\n" + "DmmnPHHUX7XZbs8IgBcwL112Z7QvX/0x/JQFn5ulxWlkvYsgunjebSaR2gQeKFZ8\n" + "f3E8J6bgUk2INEafKndzRX/hpZ0Q0g0R4DPTcSDSXdRb6do5Fgre/qhiKkRHqQPY\n" + "1aIZTG0byG60vtDacJ+NGM21hP0BtGxg+/ES1TdMSdmz9LaCGaV3ydoEC6qz+eXW\n" + "Id5jL82D7ywft679GQtmq8/iOwb6Z5sz76Xu8aiBtwYWrW3tlRfvPS/0nxHYLPhQ\n" + "1RJZKWW0EFUtar7aizde3fKDkUI1CvWUfLN5SrvA3TMHQCsbqXNRKMXRBW4M0jel\n" + "z1cy67l9IMa5vVzzjvxCdIyHjE1k5MzKhQgCihDYn0QdEuEbGjOE3sU/g22K2t33\n" + "QvDGGnwH3yoNvjgLQ/IEwcixrPm1cxQYmYhYrt5MPn4sNxVw3ne/LgRfRdIyCmGN\n" + "MN7QHG0s1ciTqxaKJPUyUDAoqOy9t17oKk0HVmp6YjzY2NEIkwIDAQABo1MwUTAd\n" + "BgNVHQ4EFgQUjU31StK8ffuKYL6IfYmCuHvgzr4wHwYDVR0jBBgwFoAUjU31StK8\n" + "ffuKYL6IfYmCuHvgzr4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\n" + "AgEAPv4vOG5C3HXlPe+fRPqtR28cpjNddJWgFRkYxp5vntN6mVrswkUzD/a8sZ6t\n" + "ly4bGgQPGjshCOLvlRQera+XxBMB2kafWL+2YiRsLRl0eCeTY04Rn3MUVFwuet9/\n" + "gk1Xh3j5dMPh3oAo9ZVT+/rYc9376YDYm5IPxZXPEA/huXc8iK8NXCWoUvkYMimC\n" + "x3dzyyW2hp3mJEjOQS8jSayZfsS/UjhV0KYwDPKdjUbHYR7hGqLrEXjIBz5ee8On\n" + "9olSYvZ7/TGIzZTSZXYUx9mbq763OTMjRGLTVj+fD0rsa5Toz4TXsOjzppS8cqL9\n" + "kzNmUG6qVpO4Q/+wKgfeUy6HqxGSxFqH6W0QdQP1rTtBTayhSdHppH5Dupx+7S4p\n" + "pmaL6k535DlFnFQZjIXIqGnP/oXwIjn5la66EqdU0fPLprH4sqVXAM032swyHFop\n" + "RIM6NV8u0fRjQXqyJDktMXPUYNaV+rwXbtImjVaoelK8LvSwzKc6NLEEHgsa3HMO\n" + "6z93LtCk+ocCrAABQor1S/fAq5TpL6btaUzAi2qfj5yWgZZnt7LkxLp+tHXtfif+\n" + "E/XAbpLYzkzTYi50IgEBkS1sjT5IOK9Yr0al2tDcQFGpS25SOz7BhCfnj3+MBD//\n" + "m4Y13hEvpYRBDnfhXCvwD9/wd6Xq1wA+lueDpwWbrfriTJo=\n" + "-----END CERTIFICATE-----"; + +void test_roundtrip () +{ + void *sb = test_context_socket (ZMQ_REP); + zmq_setsockopt (sb, ZMQ_WSS_CERT_PEM, cert, strlen (cert)); + zmq_setsockopt (sb, ZMQ_WSS_KEY_PEM, key, strlen (key)); + TEST_ASSERT_SUCCESS_ERRNO (zmq_bind (sb, "wss://*:5556/roundtrip")); + + void *sc = test_context_socket (ZMQ_REQ); + zmq_setsockopt (sc, ZMQ_WSS_TRUST_PEM, cert, strlen (cert)); + zmq_setsockopt (sc, ZMQ_WSS_HOSTNAME, "zeromq.org", strlen ("zeromq.org")); + TEST_ASSERT_SUCCESS_ERRNO ( + zmq_connect (sc, "wss://127.0.0.1:5556/roundtrip")); + + bounce (sb, sc); + + test_context_socket_close (sc); + test_context_socket_close (sb); +} + +int main () +{ + setup_test_environment (); + + UNITY_BEGIN (); + RUN_TEST (test_roundtrip); + return UNITY_END (); +}