diff --git a/CMakeLists.txt b/CMakeLists.txt index 726864ad..03b5b964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,47 @@ if(APPLE) option(ZMQ_BUILD_FRAMEWORK "Build as OS X framework" OFF) endif() +# Disable webSocket transport +option(DISABLE_WS "Disable WebSocket transport" OFF) +option(WITH_NSS "Use NSS instead of builtin sha1" OFF) + +if (NOT DISABLE_WS) + list(APPEND sources + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_address.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_connecter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_decoder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_encoder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_engine.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_listener.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_address.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_connecter.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_decoder.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_encoder.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_engine.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_listener.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ws_protocol.hpp) + set(ZMQ_HAVE_WS 1) + + if (WITH_NSS) + find_package("NSS3") + if (NSS3_FOUND) + message(STATUS "Using NSS") + include_directories(${NSS3_INCLUDE_DIRS}) + set(pkg_config_names_private "${pkg_config_names_private} nss3") + set(pkg_config_libs_private "${pkg_config_libs_private} -lnss3") + set(ZMQ_USE_NSS 1) + else() + message(FATAL_ERROR + "nss is not installed. Install it, then run CMake again") + endif() + else() + list(APPEND sources ${CMAKE_CURRENT_SOURCE_DIR}/external/sha1/sha1.c ${CMAKE_CURRENT_SOURCE_DIR}/external/sha1/sha1.h) + message("Using builtin sha1") + set(ZMQ_USE_BUILTIN_SHA1 1) + endif() +endif() + + # Select curve encryption library, defaults to tweetnacl # To use libsodium instead, use --with-libsodium(must be installed) # To disable curve, use --disable-curve @@ -223,8 +264,6 @@ else() message(FATAL_ERROR "Invalid polling method") endif() -list(APPEND sources ${CMAKE_CURRENT_SOURCE_DIR}/external/sha1/sha1.c ${CMAKE_CURRENT_SOURCE_DIR}/external/sha1/sha1.h) - if(POLLER STREQUAL "epoll" AND WIN32) message(STATUS "Including wepoll") list(APPEND sources ${CMAKE_CURRENT_SOURCE_DIR}/external/wepoll/wepoll.c ${CMAKE_CURRENT_SOURCE_DIR}/external/wepoll/wepoll.h) @@ -785,12 +824,6 @@ set(cxx-sources gather.cpp ip_resolver.cpp zap_client.cpp - ws_address.cpp - ws_connecter.cpp - ws_decoder.cpp - ws_encoder.cpp - ws_engine.cpp - ws_listener.cpp zmtp_engine.cpp # at least for VS, the header files must also be listed address.hpp @@ -924,13 +957,6 @@ set(cxx-sources vmci_listener.hpp windows.hpp wire.hpp - ws_address.hpp - ws_connecter.hpp - ws_decoder.hpp - ws_encoder.hpp - ws_engine.hpp - ws_listener.hpp - ws_protocol.hpp xpub.hpp xsub.hpp ypipe.hpp @@ -1232,6 +1258,10 @@ endforeach() if(BUILD_SHARED) target_link_libraries(libzmq ${OPTIONAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + if(NSS3_FOUND) + target_link_libraries(libzmq ${NSS3_LIBRARIES}) + endif() + if(SODIUM_FOUND) target_link_libraries(libzmq ${SODIUM_LIBRARIES}) # On Solaris, libsodium depends on libssp @@ -1262,6 +1292,10 @@ endif() if(BUILD_STATIC) target_link_libraries(libzmq-static ${OPTIONAL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + if(NSS3_FOUND) + target_link_libraries(libzmq-static ${NSS3_LIBRARIES}) + endif() + if(SODIUM_FOUND) target_link_libraries(libzmq-static ${SODIUM_LIBRARIES}) # On Solaris, libsodium depends on libssp diff --git a/Makefile.am b/Makefile.am index 9be3018e..1b0e282a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -250,19 +250,6 @@ src_libzmq_la_SOURCES = \ src/vmci_listener.hpp \ src/windows.hpp \ src/wire.hpp \ - src/ws_address.cpp \ - src/ws_address.hpp \ - src/ws_connecter.cpp \ - src/ws_connecter.hpp \ - src/ws_decoder.cpp \ - src/ws_decoder.hpp \ - src/ws_encoder.cpp \ - src/ws_encoder.hpp \ - src/ws_engine.cpp \ - src/ws_engine.hpp \ - src/ws_listener.cpp \ - src/ws_listener.hpp \ - src/ws_protocol.hpp \ src/xpub.cpp \ src/xpub.hpp \ src/xsub.cpp \ @@ -281,9 +268,7 @@ src_libzmq_la_SOURCES = \ src/zap_client.hpp \ src/zmtp_engine.cpp \ src/zmtp_engine.hpp \ - src/zmq_draft.h \ - external/sha1/sha1.c \ - external/sha1/sha1.h + src/zmq_draft.h if USE_WEPOLL src_libzmq_la_SOURCES += \ @@ -297,6 +282,29 @@ src_libzmq_la_SOURCES += \ src/tweetnacl.h endif +if HAVE_WS +src_libzmq_la_SOURCES += \ + src/ws_address.cpp \ + src/ws_address.hpp \ + src/ws_connecter.cpp \ + src/ws_connecter.hpp \ + src/ws_decoder.cpp \ + src/ws_decoder.hpp \ + src/ws_encoder.cpp \ + src/ws_encoder.hpp \ + src/ws_engine.cpp \ + src/ws_engine.hpp \ + src/ws_listener.cpp \ + src/ws_listener.hpp \ + src/ws_protocol.hpp +endif + +if USE_BUILTIN_SHA1 +src_libzmq_la_SOURCES += \ + external/sha1/sha1.c \ + external/sha1/sha1.h +endif + if ON_MINGW src_libzmq_la_LDFLAGS = \ -no-undefined \ @@ -334,6 +342,11 @@ src_libzmq_la_CXXFLAGS = @LIBZMQ_EXTRA_CXXFLAGS@ $(CODE_COVERAGE_CXXFLAGS) \ $(LIBUNWIND_CFLAGS) src_libzmq_la_LIBADD = $(CODE_COVERAGE_LDFLAGS) $(LIBUNWIND_LIBS) +if HAVE_WS +src_libzmq_la_CPPFLAGS += ${NSS3_CFLAGS} +src_libzmq_la_LIBADD += ${NSS3_LIBS} +endif + if USE_LIBSODIUM src_libzmq_la_CPPFLAGS += ${sodium_CFLAGS} src_libzmq_la_LIBADD += ${sodium_LIBS} @@ -479,8 +492,7 @@ test_apps = \ tests/test_sodium \ tests/test_reconnect_ivl \ tests/test_mock_pub_sub \ - tests/test_socket_null \ - tests/test_ws_transport + tests/test_socket_null UNITY_CPPFLAGS = -I$(top_srcdir)/external/unity -DUNITY_USE_COMMAND_LINE_ARGS -DUNITY_EXCLUDE_FLOAT UNITY_LIBS = $(top_builddir)/external/unity/libunity.a @@ -795,10 +807,6 @@ tests_test_mock_pub_sub_SOURCES = tests/test_mock_pub_sub.cpp tests_test_mock_pub_sub_LDADD = ${TESTUTIL_LIBS} src/libzmq.la tests_test_mock_pub_sub_CPPFLAGS = ${TESTUTIL_CPPFLAGS} -tests_test_ws_transport_SOURCES = tests/test_ws_transport.cpp -tests_test_ws_transport_LDADD = ${TESTUTIL_LIBS} src/libzmq.la -tests_test_ws_transport_CPPFLAGS = ${TESTUTIL_CPPFLAGS} - if HAVE_CURVE test_apps += \ @@ -834,6 +842,14 @@ endif endif +if HAVE_WS +test_apps += \ + tests/test_ws_transport +tests_test_ws_transport_SOURCES = tests/test_ws_transport.cpp +tests_test_ws_transport_LDADD = ${TESTUTIL_LIBS} src/libzmq.la ${NSS3_LIBS} +tests_test_ws_transport_CPPFLAGS = ${TESTUTIL_CPPFLAGS} ${NSS3_CFLAGS} +endif + if !ON_MINGW if !ON_CYGWIN test_apps += \ diff --git a/builds/cmake/Modules/FindNSS3.cmake b/builds/cmake/Modules/FindNSS3.cmake new file mode 100644 index 00000000..9adb4db2 --- /dev/null +++ b/builds/cmake/Modules/FindNSS3.cmake @@ -0,0 +1,8 @@ +include(FindPackageHandleStandardArgs) + +if (NOT MSVC) + find_package(PkgConfig REQUIRED) + pkg_check_modules(NSS3 "nss>=3.19") + find_package_handle_standard_args(NSS3 DEFAULT_MSG NSS3_LIBRARIES NSS3_CFLAGS) +endif() + diff --git a/builds/cmake/platform.hpp.in b/builds/cmake/platform.hpp.in index 8e6fb402..d67eca62 100644 --- a/builds/cmake/platform.hpp.in +++ b/builds/cmake/platform.hpp.in @@ -51,6 +51,10 @@ #cmakedefine HAVE_ACCEPT4 #cmakedefine HAVE_STRNLEN +#cmakedefine ZMQ_USE_BUILTIN_SHA1 +#cmakedefine ZMQ_USE_NSS +#cmakedefine ZMQ_HAVE_WS + #cmakedefine ZMQ_HAVE_OPENPGM #cmakedefine ZMQ_MAKE_VALGRIND_HAPPY diff --git a/configure.ac b/configure.ac index 083c9e20..9511bd72 100644 --- a/configure.ac +++ b/configure.ac @@ -548,9 +548,40 @@ AM_CONDITIONAL(ENABLE_CURVE_KEYGEN, test "x$enable_curve" = "xyes" -a "x$zmq_ena AM_CONDITIONAL(USE_LIBSODIUM, test "$curve_library" = "libsodium") AM_CONDITIONAL(USE_TWEETNACL, test "$curve_library" = "tweetnacl") AM_CONDITIONAL(HAVE_CURVE, test "x$curve_library" != "x") - AM_CONDITIONAL(USE_WEPOLL, test "$poller" = "wepoll") +# Check requiring packages for WebSocket +sha1_library="" + +AC_ARG_ENABLE([ws], + [AS_HELP_STRING([--disable-ws], [Disable WebSocket transport [default=no]])]) + +AC_ARG_WITH([nss], + [AS_HELP_STRING([--with-nss], [use nss instead of built-in sha1 [default=no]])]) + +if test "x$enable_ws" != "xno"; then + if 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" + AC_MSG_NOTICE(Using NSS) + ], [ + 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_MSG_NOTICE(Using builting SHA1) + 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") + # build using pgm have_pgm_library="no" diff --git a/src/address.cpp b/src/address.cpp index 37a006b7..4763ee44 100644 --- a/src/address.cpp +++ b/src/address.cpp @@ -61,9 +61,13 @@ zmq::address_t::~address_t () LIBZMQ_DELETE (resolved.tcp_addr); } else if (protocol == protocol_name::udp) { LIBZMQ_DELETE (resolved.udp_addr); - } else if (protocol == protocol_name::ws) { + } +#ifdef ZMQ_HAVE_WS + else if (protocol == protocol_name::ws) { 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) { @@ -88,8 +92,10 @@ int zmq::address_t::to_string (std::string &addr_) const return resolved.tcp_addr->to_string (addr_); if (protocol == protocol_name::udp && resolved.udp_addr) return resolved.udp_addr->to_string (addr_); +#ifdef ZMQ_HAVE_WS if (protocol == protocol_name::ws && 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 4bc5c65c..b38e75b4 100644 --- a/src/address.hpp +++ b/src/address.hpp @@ -61,7 +61,9 @@ namespace protocol_name static const char inproc[] = "inproc"; static const char tcp[] = "tcp"; static const char udp[] = "udp"; +#ifdef ZMQ_HAVE_WS static const char ws[] = "ws"; +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS static const char ipc[] = "ipc"; @@ -93,7 +95,9 @@ struct address_t void *dummy; tcp_address_t *tcp_addr; udp_address_t *udp_addr; +#ifdef ZMQ_HAVE_WS ws_address_t *ws_addr; +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS ipc_address_t *ipc_addr; diff --git a/src/ctx.cpp b/src/ctx.cpp index fdf0c163..9018ba7b 100644 --- a/src/ctx.cpp +++ b/src/ctx.cpp @@ -52,6 +52,10 @@ #include #endif +#ifdef ZMQ_USE_NSS +#include +#endif + #define ZMQ_CTX_TAG_VALUE_GOOD 0xabadcafe #define ZMQ_CTX_TAG_VALUE_BAD 0xdeadbeef @@ -87,6 +91,10 @@ zmq::ctx_t::ctx_t () : // Initialise crypto library, if needed. zmq::random_open (); + +#ifdef ZMQ_USE_NSS + NSS_NoDB_Init (NULL); +#endif } bool zmq::ctx_t::check_tag () @@ -119,6 +127,10 @@ zmq::ctx_t::~ctx_t () // De-initialise crypto library, if needed. zmq::random_close (); +#ifdef ZMQ_USE_NSS + NSS_Shutdown (); +#endif + // Remove the tag, so that the object is considered dead. _tag = ZMQ_CTX_TAG_VALUE_BAD; } diff --git a/src/session_base.cpp b/src/session_base.cpp index 712fb105..b9f7aa10 100644 --- a/src/session_base.cpp +++ b/src/session_base.cpp @@ -559,8 +559,10 @@ zmq::session_base_t::connecter_factory_entry_t zmq::session_base_t::_connecter_factories[] = { connecter_factory_entry_t (protocol_name::tcp, &zmq::session_base_t::create_connecter_tcp), +#ifdef ZMQ_HAVE_WS connecter_factory_entry_t (protocol_name::ws, &zmq::session_base_t::create_connecter_ws), +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS connecter_factory_entry_t (protocol_name::ipc, @@ -683,12 +685,14 @@ zmq::own_t *zmq::session_base_t::create_connecter_tcp (io_thread_t *io_thread_, tcp_connecter_t (io_thread_, this, options, _addr, wait_); } +#ifdef ZMQ_HAVE_WS 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_); } +#endif #ifdef ZMQ_HAVE_OPENPGM void zmq::session_base_t::start_connecting_pgm (io_thread_t *io_thread_) diff --git a/src/socket_base.cpp b/src/socket_base.cpp index 8a35c51d..fc4787a8 100644 --- a/src/socket_base.cpp +++ b/src/socket_base.cpp @@ -54,6 +54,7 @@ #include "ipc_listener.hpp" #include "tipc_listener.hpp" #include "tcp_connecter.hpp" +#include "ws_address.hpp" #include "io_thread.hpp" #include "session_base.hpp" #include "config.hpp" @@ -65,7 +66,6 @@ #include "address.hpp" #include "ipc_address.hpp" #include "tcp_address.hpp" -#include "ws_address.hpp" #include "udp_address.hpp" #include "tipc_address.hpp" #include "mailbox.hpp" @@ -335,7 +335,9 @@ int zmq::socket_base_t::check_protocol (const std::string &protocol_) const && protocol_ != protocol_name::ipc #endif && protocol_ != protocol_name::tcp +#ifdef ZMQ_HAVE_WS && protocol_ != protocol_name::ws +#endif #if defined ZMQ_HAVE_OPENPGM // pgm/epgm transports only available if 0MQ is compiled with OpenPGM. && protocol_ != "pgm" @@ -632,6 +634,7 @@ int zmq::socket_base_t::bind (const char *endpoint_uri_) return 0; } +#ifdef ZMQ_HAVE_WS if (protocol == protocol_name::ws) { ws_listener_t *listener = new (std::nothrow) ws_listener_t (io_thread, this, options); @@ -652,6 +655,7 @@ int zmq::socket_base_t::bind (const char *endpoint_uri_) options.connected = true; return 0; } +#endif #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS @@ -889,7 +893,9 @@ int zmq::socket_base_t::connect (const char *endpoint_uri_) } // Defer resolution until a socket is opened paddr->resolved.tcp_addr = NULL; - } else if (protocol == protocol_name::ws) { + } +#ifdef ZMQ_HAVE_WS + else if (protocol == protocol_name::ws) { 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, @@ -899,6 +905,8 @@ int zmq::socket_base_t::connect (const char *endpoint_uri_) return -1; } } +#endif + #if !defined ZMQ_HAVE_WINDOWS && !defined ZMQ_HAVE_OPENVMS \ && !defined ZMQ_HAVE_VXWORKS else if (protocol == protocol_name::ipc) { diff --git a/src/ws_engine.cpp b/src/ws_engine.cpp index db795c0d..ffc15652 100644 --- a/src/ws_engine.cpp +++ b/src/ws_engine.cpp @@ -29,6 +29,14 @@ along with this program. If not, see . #include "precompiled.hpp" +#ifdef ZMQ_USE_NSS +#include +#include +#define SHA_DIGEST_LENGTH 20 +#else +#include "../external/sha1/sha1.h" +#endif + #if !defined ZMQ_HAVE_WINDOWS #include #include @@ -46,7 +54,6 @@ along with this program. If not, see . #include "err.hpp" #include "ip.hpp" #include "random.hpp" -#include "../external/sha1/sha1.h" #include "ws_decoder.hpp" #include "ws_encoder.hpp" @@ -66,6 +73,9 @@ along with this program. If not, see . static int encode_base64 (const unsigned char *in, int in_len, char *out, int out_len); +static void compute_accept_key (char *key, + unsigned char output[SHA_DIGEST_LENGTH]); + zmq::ws_engine_t::ws_engine_t (fd_t fd_, const options_t &options_, const endpoint_uri_pair_t &endpoint_uri_pair_, @@ -407,21 +417,8 @@ bool zmq::ws_engine_t::server_handshake () && _websocket_key[0] != '\0') { _server_handshake_state = handshake_complete; - const char *magic_string = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - char plain[MAX_HEADER_VALUE_LENGTH + 36 + 1]; - strcpy (plain, _websocket_key); - strcat (plain, magic_string); - - sha1_ctxt ctx; - SHA1_Init (&ctx); - SHA1_Update (&ctx, (unsigned char *) _websocket_key, - strlen (_websocket_key)); - SHA1_Update (&ctx, (unsigned char *) magic_string, - strlen (magic_string)); - unsigned char hash[SHA_DIGEST_LENGTH]; - SHA1_Final (hash, &ctx); + compute_accept_key (_websocket_key, hash); int accept_key_len = encode_base64 ( hash, SHA_DIGEST_LENGTH, _websocket_accept, @@ -855,3 +852,28 @@ encode_base64 (const unsigned char *in, int in_len, char *out, int out_len) out[io] = 0; return io; } + +static void compute_accept_key (char *key, unsigned char *hash) +{ + const char *magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +#ifdef ZMQ_USE_NSS + unsigned int len; + HASH_HashType type = HASH_GetHashTypeByOidTag (SEC_OID_SHA1); + HASHContext *ctx = HASH_Create (type); + assert (ctx); + + HASH_Begin (ctx); + HASH_Update (ctx, (unsigned char *) key, (unsigned int) strlen (key)); + HASH_Update (ctx, (unsigned char *) magic_string, + (unsigned int) strlen (magic_string)); + HASH_End (ctx, hash, &len, SHA_DIGEST_LENGTH); + HASH_Destroy (ctx); +#else + 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); +#endif +}