diff --git a/.travis.yml b/.travis.yml index d761afc5..00b2cdee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -129,6 +129,7 @@ install: - export PATH="`pwd`":$PATH - git submodule update --init tools/build - git submodule update --init tools/boostdep + - git submodule update --init libs/align - git submodule update --init libs/asio - git submodule update --init libs/assert - git submodule update --init libs/config diff --git a/CHANGELOG.md b/CHANGELOG.md index e702e5de..f8d7cbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 176: * Tidy up Quick Reference * Fix array end calculation in utf8 assertion +* WebSocket masks use secure PRNG by default -------------------------------------------------------------------------------- diff --git a/appveyor.yml b/appveyor.yml index 179f8fbe..9522ca0d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,6 +23,7 @@ install: - cd boost-root - git submodule update --init tools/build - git submodule update --init tools/boostdep + - git submodule update --init libs/align - git submodule update --init libs/asio - git submodule update --init libs/assert - git submodule update --init libs/config diff --git a/doc/qbk/09_releases.qbk b/doc/qbk/09_releases.qbk index 1e959853..37324eb8 100644 --- a/doc/qbk/09_releases.qbk +++ b/doc/qbk/09_releases.qbk @@ -10,8 +10,11 @@ [section Release Notes] This version fixes a missing executor work guard in all composed operations -used in the implementatio. Users who are experiencing crashes related to -asynchronous completion handlers are encouraged to upgrade. +used in the implementation. Users who are experiencing crashes related to +asynchronous completion handlers are encouraged to upgrade. Also included +is an improved mechanism for generating random numbers used to mask outgoing +websocket frames when operating in the client mode. This resolves a +vulnerability described in the Beast Hybrid Assessment Report from Bishop Fox. [heading Boost 1.68] @@ -35,14 +38,20 @@ in future versions. * New [link beast.ref.boost__beast__http__is_mutable_body_writer `http::is_mutable_body_writer`] metafunction +* New [link beast.ref.boost__beast__websocket__seed_prng `websocket::seed_prng`] for manually providing entropy to the PRNG + +* New [link beast.ref.boost__beast__websocket__stream.secure_prng `websocket::stream::secure_prng`] to control whether the connection uses a secure PRNG + [*Improvements] +* Generated WebSocket masks use a secure PRNG by default + +* Improvements to [link beast.ref.boost__beast__buffers_adapter `buffers_adapter`] + * ([issue 1109]) Use a shared string for example HTTP server doc roots * ([issue 1079]) Add [link beast.ref.boost__beast__handler_ptr.has_value `handler_ptr::has_value`] -* Improvements to [link beast.ref.boost__beast__buffers_adapter `buffers_adapter`] - [*Fixes] * ([issue 1073]) Fix race in advanced server examples diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 4c84b64b..c5d9bef7 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -137,6 +137,7 @@ async_teardown is_upgrade + seed_prng teardown Options diff --git a/include/boost/beast/core/detail/chacha.hpp b/include/boost/beast/core/detail/chacha.hpp new file mode 100644 index 00000000..976ade56 --- /dev/null +++ b/include/boost/beast/core/detail/chacha.hpp @@ -0,0 +1,196 @@ +// +// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// +// This is a derivative work, original copyright follows: +// + +/* + Copyright (c) 2015 Orson Peters + + This software is provided 'as-is', without any express or implied warranty. In no event will the + authors be held liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, including commercial + applications, and to alter it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the + original software. If you use this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as + being the original software. + + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef BOOST_BEAST_CORE_DETAIL_CHACHA_HPP +#define BOOST_BEAST_CORE_DETAIL_CHACHA_HPP + +#include +#include +#include + +namespace boost { +namespace beast { +namespace detail { + +template +class chacha +{ + void generate_block(); + void chacha_core(); + + alignas(16) std::uint32_t block_[16]; + std::uint32_t keysetup_[8]; + std::uint64_t ctr_ = 0; + int idx_ = 16; + +public: + static constexpr std::size_t state_size = sizeof(chacha::keysetup_); + + using result_type = std::uint32_t; + + chacha(std::uint32_t const* v, std::uint64_t stream); + + std::uint32_t + operator()(); + +#if 0 + template + friend + bool + operator==(chacha const& lhs, chacha const& rhs); + + template + friend + bool + operator!=(chacha const& lhs, chacha const& rhs); + + static + constexpr + std::uint32_t + min() + { + return (std::numeric_limits::min)(); + } + + static + constexpr + std::uint32_t + max() + { + return (std::numeric_limits::max)(); + } +#endif +}; + +template +chacha:: +chacha(std::uint32_t const* v, std::uint64_t stream) +{ + for (int i = 0; i < 6; ++i) + keysetup_[i] = v[i]; + keysetup_[6] = v[6] + (stream & 0xffffffff); + keysetup_[7] = v[7] + ((stream >> 32) & 0xffffffff); +} + +template +std::uint32_t +chacha:: +operator()() +{ + if(idx_ == 16) + { + idx_ = 0; + ++ctr_; + generate_block(); + } + return block_[idx_++]; +} + +template +void +chacha:: +generate_block() +{ + std::uint32_t constexpr constants[4] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 }; + std::uint32_t input[16]; + for (int i = 0; i < 4; ++i) + input[i] = constants[i]; + for (int i = 0; i < 8; ++i) + input[4 + i] = keysetup_[i]; + input[12] = (ctr_ / 16) & 0xffffffffu; + input[13] = (ctr_ / 16) >> 32; + input[14] = input[15] = 0xdeadbeef; // Could use 128-bit counter. + for (int i = 0; i < 16; ++i) + block_[i] = input[i]; + chacha_core(); + for (int i = 0; i < 16; ++i) + block_[i] += input[i]; +} + +template +void +chacha:: +chacha_core() +{ + #define BOOST_BEAST_CHACHA_ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + #define BOOST_BEAST_CHACHA_QUARTERROUND(x, a, b, c, d) \ + x[a] = x[a] + x[b]; x[d] ^= x[a]; x[d] = BOOST_BEAST_CHACHA_ROTL32(x[d], 16); \ + x[c] = x[c] + x[d]; x[b] ^= x[c]; x[b] = BOOST_BEAST_CHACHA_ROTL32(x[b], 12); \ + x[a] = x[a] + x[b]; x[d] ^= x[a]; x[d] = BOOST_BEAST_CHACHA_ROTL32(x[d], 8); \ + x[c] = x[c] + x[d]; x[b] ^= x[c]; x[b] = BOOST_BEAST_CHACHA_ROTL32(x[b], 7) + + for (unsigned i = 0; i < R; i += 2) + { + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 0, 4, 8, 12); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 1, 5, 9, 13); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 2, 6, 10, 14); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 3, 7, 11, 15); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 0, 5, 10, 15); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 1, 6, 11, 12); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 2, 7, 8, 13); + BOOST_BEAST_CHACHA_QUARTERROUND(block_, 3, 4, 9, 14); + } + + #undef BOOST_BEAST_CHACHA_QUARTERROUND + #undef BOOST_BEAST_CHACHA_ROTL32 +} + +//#endif + +#if 0 +// Implement interface. + +template +bool +operator==(chacha const& lhs, chacha const& rhs) +{ + for (int i = 0; i < 8; ++i) + if (lhs.keysetup_[i] != rhs.keysetup_[i]) + return false; + return lhs.ctr_ == rhs.ctr_; +} + +template +bool +operator!=(chacha const& lhs, chacha const& rhs) +{ + return !(lhs == rhs); +} +#endif + +} // detail +} // beast +} // boost + +#endif diff --git a/include/boost/beast/websocket/detail/hybi13.hpp b/include/boost/beast/websocket/detail/hybi13.hpp index b9c67b82..424a9cc9 100644 --- a/include/boost/beast/websocket/detail/hybi13.hpp +++ b/include/boost/beast/websocket/detail/hybi13.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,14 +32,15 @@ using sec_ws_key_type = static_string< using sec_ws_accept_type = static_string< beast::detail::base64::encoded_size(20)>; -template +inline void -make_sec_ws_key(sec_ws_key_type& key, Gen& g) +make_sec_ws_key(sec_ws_key_type& key) { + auto p = stream_prng::prng(); char a[16]; for(int i = 0; i < 16; i += 4) { - auto const v = g(); + auto const v = p->secure(); a[i ] = v & 0xff; a[i+1] = (v >> 8) & 0xff; a[i+2] = (v >> 16) & 0xff; diff --git a/include/boost/beast/websocket/detail/mask.hpp b/include/boost/beast/websocket/detail/mask.hpp index 8958d793..2da2c9b4 100644 --- a/include/boost/beast/websocket/detail/mask.hpp +++ b/include/boost/beast/websocket/detail/mask.hpp @@ -24,66 +24,6 @@ namespace beast { namespace websocket { namespace detail { -// Pseudo-random source of mask keys -// -template -class maskgen_t -{ - Generator g_; - -public: - using result_type = - typename Generator::result_type; - - maskgen_t(); - - result_type - operator()() noexcept; - - void - rekey(); -}; - -template -maskgen_t::maskgen_t() -{ - rekey(); -} - -template -auto -maskgen_t::operator()() noexcept -> - result_type -{ - for(;;) - if(auto key = g_()) - return key; -} - -template -void -maskgen_t<_>::rekey() -{ - std::random_device rng; -#if 0 - std::array e; - for(auto& i : e) - i = rng(); - // VFALCO This constructor causes - // address sanitizer to fail, no idea why. - std::seed_seq ss(e.begin(), e.end()); - g_.seed(ss); -#else - g_.seed(rng()); -#endif -} - -// VFALCO NOTE This generator has 5KB of state! -//using maskgen = maskgen_t; -using maskgen = maskgen_t; - -//------------------------------------------------------------------------------ - using prepared_key = std::array; inline diff --git a/include/boost/beast/websocket/detail/stream_base.hpp b/include/boost/beast/websocket/detail/stream_base.hpp index 2e93cce3..db89dd17 100644 --- a/include/boost/beast/websocket/detail/stream_base.hpp +++ b/include/boost/beast/websocket/detail/stream_base.hpp @@ -16,9 +16,23 @@ #include #include #include +#include +#include +#include #include +#include #include #include +#include +#include + +// Turn this on to avoid using thread_local +//#define BOOST_BEAST_NO_THREAD_LOCAL 1 + +#ifdef BOOST_BEAST_NO_THREAD_LOCAL +#include +#include +#endif namespace boost { namespace beast { @@ -105,8 +119,191 @@ public: } }; +//------------------------------------------------------------------------------ + +struct stream_prng +{ + bool secure_prng_ = true; + + struct prng_type + { + std::minstd_rand fast; + beast::detail::chacha<20> secure; + +#if BOOST_BEAST_NO_THREAD_LOCAL + prng_type* next = nullptr; +#endif + + prng_type(std::uint32_t const* v, std::uint64_t stream) + : fast(static_cast( + v[0] + v[1] + v[2] + v[3] + v[4] + v[5] + v[6] + v[7] + stream)) + , secure(v, stream) + { + } + }; + + class prng_ref + { + prng_type* p_; + + public: + prng_ref& operator=(prng_ref&&) = delete; + + explicit + prng_ref(prng_type& p) + : p_(&p) + { + } + + prng_ref(prng_ref&& other) + : p_([&other] + { + auto p = other.p_; + other.p_ = nullptr; + return p; + }()) + { + } + +#ifdef BOOST_BEAST_NO_THREAD_LOCAL + ~prng_ref() + { + if(p_) + pool::impl().release(*p_); + } +#endif + + prng_type* + operator->() const + { + return p_; + } + }; + +#ifndef BOOST_BEAST_NO_THREAD_LOCAL + static + prng_ref + prng() + { + static std::atomic stream{0}; + thread_local prng_type p{seed(), stream++}; + return prng_ref(p); + } + +#else + static + prng_ref + prng() + { + return prng_ref(pool::impl().acquire()); + } + +#endif + + static + std::uint32_t const* + seed(std::seed_seq* ss = nullptr) + { + static seed_data d(ss); + return d.v; + } + + std::uint32_t + create_mask() + { + auto p = prng(); + if(secure_prng_) + for(;;) + if(auto key = p->secure()) + return key; + for(;;) + if(auto key = p->fast()) + return key; + } + +private: + struct seed_data + { + std::uint32_t v[8]; + + explicit + seed_data(std::seed_seq* pss) + { + if(! pss) + { + std::random_device g; + std::seed_seq ss{ + g(), g(), g(), g(), g(), g(), g(), g()}; + ss.generate(v, v+8); + } + else + { + pss->generate(v, v+8); + } + } + }; + +#ifdef BOOST_BEAST_NO_THREAD_LOCAL + class pool + { + prng_type* head_ = nullptr; + std::atomic n_{0}; + std::mutex m_; + + public: + ~pool() + { + for(auto p = head_; p;) + { + auto next = p->next; + p->~prng_type(); + boost::alignment::aligned_free(p); + p = next; + } + } + + prng_type& + acquire() + { + for(;;) + { + std::lock_guard lock(m_); + if(! head_) + break; + auto p = head_; + head_ = head_->next; + return *p; + } + auto p = boost::alignment::aligned_alloc( + 16, sizeof(prng_type)); + if(! p) + BOOST_THROW_EXCEPTION(std::bad_alloc{}); + return *(new(p) prng_type(seed(), n_++)); + } + + void + release(prng_type& p) + { + std::lock_guard lock(m_); + p.next = head_; + head_ = &p; + } + + static + pool& + impl() + { + static pool instance; + return instance; + } + }; +#endif +}; + +//------------------------------------------------------------------------------ + template -struct stream_base +struct stream_base : stream_prng { // State information for the permessage-deflate extension struct pmd_type @@ -165,7 +362,7 @@ struct stream_base }; template<> -struct stream_base +struct stream_base : stream_prng { // These stubs are for avoiding linking in the zlib // code when permessage-deflate is not enabled. diff --git a/include/boost/beast/websocket/impl/stream.ipp b/include/boost/beast/websocket/impl/stream.ipp index cf747c23..4aa72995 100644 --- a/include/boost/beast/websocket/impl/stream.ipp +++ b/include/boost/beast/websocket/impl/stream.ipp @@ -556,7 +556,7 @@ write_close(DynamicBuffer& db, close_reason const& cr) if(role_ == role_type::client) { fh.mask = true; - fh.key = wr_gen_(); + fh.key = this->create_mask(); } else { @@ -608,7 +608,7 @@ write_ping(DynamicBuffer& db, fh.len = data.size(); fh.mask = role_ == role_type::client; if(fh.mask) - fh.key = wr_gen_(); + fh.key = this->create_mask(); detail::write(db, fh); if(data.empty()) return; @@ -641,7 +641,7 @@ build_request(detail::sec_ws_key_type& key, req.set(http::field::host, host); req.set(http::field::upgrade, "websocket"); req.set(http::field::connection, "upgrade"); - detail::make_sec_ws_key(key, wr_gen_); + detail::make_sec_ws_key(key); req.set(http::field::sec_websocket_key, key); req.set(http::field::sec_websocket_version, "13"); build_request_pmd(req, is_deflate_supported{}); diff --git a/include/boost/beast/websocket/impl/write.ipp b/include/boost/beast/websocket/impl/write.ipp index ee2bc8d4..edaca798 100644 --- a/include/boost/beast/websocket/impl/write.ipp +++ b/include/boost/beast/websocket/impl/write.ipp @@ -406,7 +406,7 @@ operator()( remain_ = buffer_size(cb_); fh_.fin = fin_; fh_.len = remain_; - fh_.key = ws_.wr_gen_(); + fh_.key = ws_.create_mask(); detail::prepare_key(key_, fh_.key); ws_.wr_fb_.reset(); detail::write( @@ -458,7 +458,7 @@ operator()( n = clamp(remain_, ws_.wr_buf_size_); remain_ -= n; fh_.len = n; - fh_.key = ws_.wr_gen_(); + fh_.key = ws_.create_mask(); fh_.fin = fin_ ? remain_ == 0 : false; detail::prepare_key(key_, fh_.key); buffer_copy(buffer( @@ -521,7 +521,7 @@ operator()( } if(fh_.mask) { - fh_.key = ws_.wr_gen_(); + fh_.key = ws_.create_mask(); detail::prepared_key key; detail::prepare_key(key, fh_.key); detail::mask_inplace(b, key); @@ -666,7 +666,7 @@ write_some(bool fin, } if(fh.mask) { - fh.key = wr_gen_(); + fh.key = this->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); detail::mask_inplace(b, key); @@ -740,7 +740,7 @@ write_some(bool fin, // mask, no autofrag fh.fin = fin; fh.len = remain; - fh.key = wr_gen_(); + fh.key = this->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); detail::fh_buffer fh_buf; @@ -784,7 +784,7 @@ write_some(bool fin, ConstBufferSequence> cb{buffers}; for(;;) { - fh.key = wr_gen_(); + fh.key = this->create_mask(); detail::prepared_key key; detail::prepare_key(key, fh.key); auto const n = clamp(remain, wr_buf_size_); diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index 79c80ef9..315a23d6 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -206,7 +206,6 @@ class stream std::size_t wr_buf_opt_ // write buffer size option setting = 4096; detail::fh_buffer wr_fb_; // header buffer used for writes - detail::maskgen wr_gen_; // source of mask keys detail::pausation paused_rd_; // paused read op detail::pausation paused_wr_; // paused write op @@ -633,6 +632,37 @@ public: return rd_msg_max_; } + /** Set whether the PRNG is cryptographically secure + + This controls whether or not the source of pseudo-random + numbers used to produce the masks required by the WebSocket + protocol are of cryptographic quality. When the setting is + `true`, a strong algorithm is used which cannot be guessed + by observing outputs. When the setting is `false`, a much + faster algorithm is used. + Masking is only performed by streams operating in the client + mode. For streams operating in the server mode, this setting + has no effect. + By default, newly constructed streams use a secure PRNG. + + If the WebSocket stream is used with an encrypted SSL or TLS + next layer, if it is known to the application that intermediate + proxies are not vulnerable to cache poisoning, or if the + application is designed such that an attacker cannot send + arbitrary inputs to the stream interface, then the faster + algorithm may be used. + + For more information please consult the WebSocket protocol RFC. + + @param value `true` if the PRNG algorithm should be + cryptographically secure. + */ + void + secure_prng(bool value) + { + this->secure_prng_ = value; + } + /** Set the write buffer size option. Sets the size of the write buffer used by the implementation to @@ -3563,6 +3593,36 @@ private: error_code& ec); }; +/** Manually provide a one-time seed to initialize the PRNG + + This function invokes the specified seed sequence to produce a seed + suitable for use with the pseudo-random number generator used to + create masks and perform WebSocket protocol handshakes. + + If a seed is not manually provided, the implementation will + perform a one-time seed generation using `std::random_device`. This + function may be used when the application runs in an environment + where the random device is unreliable or does not provide sufficient + entropy. + + @par Preconditions + + This function may not be called after any websocket @ref stream objects + have been constructed. + + @param ss A reference to a `std::seed_seq` which will be used to seed + the pseudo-random number generator. The seed sequence should have at + least 256 bits of entropy. + + @see stream::secure_prng +*/ +inline +void +seed_prng(std::seed_seq& ss) +{ + detail::stream_prng::seed(&ss); +} + } // websocket } // beast } // boost diff --git a/test/beast/websocket/CMakeLists.txt b/test/beast/websocket/CMakeLists.txt index a9984897..4a269893 100644 --- a/test/beast/websocket/CMakeLists.txt +++ b/test/beast/websocket/CMakeLists.txt @@ -23,7 +23,6 @@ add_executable (tests-beast-websocket error.cpp frame.cpp handshake.cpp - mask.cpp option.cpp ping.cpp read1.cpp diff --git a/test/beast/websocket/Jamfile b/test/beast/websocket/Jamfile index e54d5b84..f4ea4bf9 100644 --- a/test/beast/websocket/Jamfile +++ b/test/beast/websocket/Jamfile @@ -13,7 +13,6 @@ local SOURCES = error.cpp frame.cpp handshake.cpp - mask.cpp option.cpp ping.cpp read1.cpp diff --git a/test/beast/websocket/mask.cpp b/test/beast/websocket/mask.cpp deleted file mode 100644 index a4993964..00000000 --- a/test/beast/websocket/mask.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include - -#include - -namespace boost { -namespace beast { -namespace websocket { -namespace detail { - -class mask_test : public beast::unit_test::suite -{ -public: - struct test_generator - { - using result_type = std::uint32_t; - - result_type n = 0; - - void - seed(std::seed_seq const&) - { - } - - void - seed(result_type const&) - { - } - - std::uint32_t - operator()() - { - return n++; - } - }; - - void run() override - { - maskgen_t mg; - BEAST_EXPECT(mg() != 0); - } -}; - -BEAST_DEFINE_TESTSUITE(beast,websocket,mask); - -} // detail -} // websocket -} // beast -} // boost diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index 2bac48ea..c71a3f44 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -22,6 +22,11 @@ public: void testOptions() { + { + std::seed_seq ss{42}; + seed_prng(ss); + } + stream ws{ioc_}; ws.auto_fragment(true); ws.write_buffer_size(2048); @@ -37,6 +42,9 @@ public: pass(); } + ws.secure_prng(true); + ws.secure_prng(false); + auto const bad = [&](permessage_deflate const& pmd) {