1 /* 2 * TLS ASIO Stream 3 * (C) 2018-2020 Jack Lloyd 4 * 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel 5 * 6 * Botan is released under the Simplified BSD License (see license.txt) 7 */ 8 9 #ifndef BOTAN_ASIO_STREAM_H_ 10 #define BOTAN_ASIO_STREAM_H_ 11 12 #include <botan/build.h> 13 14 // first version to be compatible with Networking TS (N4656) and boost::beast 15 #include <boost/version.hpp> 16 #if BOOST_VERSION >= 106600 17 18 #include <botan/asio_async_ops.h> 19 #include <botan/asio_context.h> 20 #include <botan/asio_error.h> 21 22 #include <botan/tls_callbacks.h> 23 #include <botan/tls_channel.h> 24 #include <botan/tls_client.h> 25 #include <botan/tls_magic.h> 26 #include <botan/tls_server.h> 27 28 // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>, 29 // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'. 30 #define BOOST_ASIO_DISABLE_SERIAL_PORT 31 #include <boost/asio.hpp> 32 #include <boost/beast/core.hpp> 33 34 #include <algorithm> 35 #include <memory> 36 #include <type_traits> 37 38 namespace Botan { 39 namespace TLS { 40 41 /** 42 * @brief boost::asio compatible SSL/TLS stream 43 * 44 * @tparam StreamLayer type of the next layer, usually a network socket 45 * @tparam ChannelT type of the native_handle, defaults to Botan::TLS::Channel, only needed for testing purposes 46 */ 47 template <class StreamLayer, class ChannelT = Channel> 48 class Stream 49 { 50 public: 51 //! \name construction 52 //! @{ 53 54 /** 55 * @brief Construct a new Stream 56 * 57 * @param context The context parameter is used to set up the underlying native handle. Using code is 58 * responsible for lifetime management of the context and must ensure that it is available for the 59 * lifetime of the stream. 60 * @param args Arguments to be forwarded to the construction of the next layer. 61 */ 62 template <typename... Args> Stream(Context & context,Args &&...args)63 explicit Stream(Context& context, Args&& ... args) 64 : m_context(context) 65 , m_nextLayer(std::forward<Args>(args)...) 66 , m_core(*this) 67 , m_shutdown_received(false) 68 , m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0') 69 , m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) 70 {} 71 72 /** 73 * @brief Construct a new Stream 74 * 75 * Convenience overload for boost::asio::ssl::stream compatibility. 76 * 77 * @param arg This argument is forwarded to the construction of the next layer. 78 * @param context The context parameter is used to set up the underlying native handle. Using code is 79 * responsible for lifetime management of the context and must ensure that is available for the 80 * lifetime of the stream. 81 */ 82 template <typename Arg> Stream(Arg && arg,Context & context)83 explicit Stream(Arg&& arg, Context& context) 84 : m_context(context) 85 , m_nextLayer(std::forward<Arg>(arg)) 86 , m_core(*this) 87 , m_shutdown_received(false) 88 , m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0') 89 , m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) 90 {} 91 92 virtual ~Stream() = default; 93 94 Stream(Stream&& other) = default; 95 Stream& operator=(Stream&& other) = default; 96 97 Stream(const Stream& other) = delete; 98 Stream& operator=(const Stream& other) = delete; 99 100 //! @} 101 //! \name boost::asio accessor methods 102 //! @{ 103 104 using next_layer_type = typename std::remove_reference<StreamLayer>::type; 105 next_layer()106 const next_layer_type& next_layer() const { return m_nextLayer; } next_layer()107 next_layer_type& next_layer() { return m_nextLayer; } 108 109 #if BOOST_VERSION >= 107000 110 /* 111 * From Boost 1.70 onwards Beast types no longer provide public access to the member function `lowest_layer()`. 112 * Instead, the new free-standing functions in Beast need to be used. 113 * See also: https://github.com/boostorg/beast/commit/6a658b5c3a36f8d58334f8b6582c01c3e87768ae 114 */ 115 using lowest_layer_type = typename boost::beast::lowest_layer_type<StreamLayer>; 116 lowest_layer()117 lowest_layer_type& lowest_layer() { return boost::beast::get_lowest_layer(m_nextLayer); } lowest_layer()118 const lowest_layer_type& lowest_layer() const { return boost::beast::get_lowest_layer(m_nextLayer); } 119 #else 120 using lowest_layer_type = typename next_layer_type::lowest_layer_type; 121 lowest_layer()122 lowest_layer_type& lowest_layer() { return m_nextLayer.lowest_layer(); } lowest_layer()123 const lowest_layer_type& lowest_layer() const { return m_nextLayer.lowest_layer(); } 124 #endif 125 126 using executor_type = typename next_layer_type::executor_type; get_executor()127 executor_type get_executor() noexcept { return m_nextLayer.get_executor(); } 128 129 using native_handle_type = typename std::add_pointer<ChannelT>::type; native_handle()130 native_handle_type native_handle() 131 { 132 if(m_native_handle == nullptr) 133 { throw Invalid_State("Invalid handshake state"); } 134 return m_native_handle.get(); 135 } 136 137 //! @} 138 //! \name configuration and callback setters 139 //! @{ 140 141 /** 142 * @brief Override the tls_verify_cert_chain callback 143 * 144 * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback 145 * used in the handshake. 146 * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback 147 * 148 * @note This function should only be called before initiating the TLS handshake 149 */ set_verify_callback(Context::Verify_Callback callback)150 void set_verify_callback(Context::Verify_Callback callback) 151 { 152 m_context.set_verify_callback(std::move(callback)); 153 } 154 155 /** 156 * @brief Compatibility overload of @ref set_verify_callback 157 * 158 * @param callback the callback implementation 159 * @param ec This parameter is unused. 160 */ set_verify_callback(Context::Verify_Callback callback,boost::system::error_code & ec)161 void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec) 162 { 163 BOTAN_UNUSED(ec); 164 m_context.set_verify_callback(std::move(callback)); 165 } 166 167 //! @throws Not_Implemented set_verify_depth(int depth)168 void set_verify_depth(int depth) 169 { 170 BOTAN_UNUSED(depth); 171 throw Not_Implemented("set_verify_depth is not implemented"); 172 } 173 174 /** 175 * Not Implemented. 176 * @param depth the desired verification depth 177 * @param ec Will be set to `Botan::ErrorType::NotImplemented` 178 */ set_verify_depth(int depth,boost::system::error_code & ec)179 void set_verify_depth(int depth, boost::system::error_code& ec) 180 { 181 BOTAN_UNUSED(depth); 182 ec = Botan::ErrorType::NotImplemented; 183 } 184 185 //! @throws Not_Implemented 186 template <typename verify_mode> set_verify_mode(verify_mode v)187 void set_verify_mode(verify_mode v) 188 { 189 BOTAN_UNUSED(v); 190 throw Not_Implemented("set_verify_mode is not implemented"); 191 } 192 193 /** 194 * Not Implemented. 195 * @param v the desired verify mode 196 * @param ec Will be set to `Botan::ErrorType::NotImplemented` 197 */ 198 template <typename verify_mode> set_verify_mode(verify_mode v,boost::system::error_code & ec)199 void set_verify_mode(verify_mode v, boost::system::error_code& ec) 200 { 201 BOTAN_UNUSED(v); 202 ec = Botan::ErrorType::NotImplemented; 203 } 204 205 //! @} 206 //! \name handshake methods 207 //! @{ 208 209 /** 210 * @brief Performs SSL handshaking. 211 * 212 * The function call will block until handshaking is complete or an error occurs. 213 * 214 * @param side The type of handshaking to be performed, i.e. as a client or as a server. 215 * @throws boost::system::system_error if error occured 216 */ handshake(Connection_Side side)217 void handshake(Connection_Side side) 218 { 219 boost::system::error_code ec; 220 handshake(side, ec); 221 boost::asio::detail::throw_error(ec, "handshake"); 222 } 223 224 /** 225 * @brief Performs SSL handshaking. 226 * 227 * The function call will block until handshaking is complete or an error occurs. 228 * 229 * @param side The type of handshaking to be performed, i.e. as a client or as a server. 230 * @param ec Set to indicate what error occurred, if any. 231 */ handshake(Connection_Side side,boost::system::error_code & ec)232 void handshake(Connection_Side side, boost::system::error_code& ec) 233 { 234 setup_native_handle(side, ec); 235 236 if(side == CLIENT) 237 { 238 // send client hello, which was written to the send buffer on client instantiation 239 send_pending_encrypted_data(ec); 240 } 241 242 while(!native_handle()->is_active() && !ec) 243 { 244 boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; 245 if(ec) 246 { return; } 247 248 process_encrypted_data(read_buffer, ec); 249 250 send_pending_encrypted_data(ec); 251 } 252 } 253 254 /** 255 * @brief Starts an asynchronous SSL handshake. 256 * 257 * This function call always returns immediately. 258 * 259 * @param side The type of handshaking to be performed, i.e. as a client or as a server. 260 * @param handler The handler to be called when the handshake operation completes. 261 * The equivalent function signature of the handler must be: void(boost::system::error_code) 262 */ 263 template <typename HandshakeHandler> 264 auto async_handshake(Connection_Side side, HandshakeHandler&& handler) -> 265 BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(boost::system::error_code)) 266 { 267 BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(HandshakeHandler, handler) type_check; 268 269 boost::system::error_code ec; 270 setup_native_handle(side, ec); 271 // If ec is set by setup_native_handle, the AsyncHandshakeOperation created below will do nothing but call the 272 // handler with the error_code set appropriately - no need to early return here. 273 274 boost::asio::async_completion<HandshakeHandler, void(boost::system::error_code)> init(handler); 275 276 detail::AsyncHandshakeOperation<typename std::decay<HandshakeHandler>::type, Stream> 277 op{std::move(init.completion_handler), *this, ec}; 278 279 return init.result.get(); 280 } 281 282 //! @throws Not_Implemented 283 template <typename ConstBufferSequence, typename BufferedHandshakeHandler> BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,void (boost::system::error_code,std::size_t))284 BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, 285 void(boost::system::error_code, std::size_t)) 286 async_handshake(Connection_Side side, const ConstBufferSequence& buffers, 287 BufferedHandshakeHandler&& handler) 288 { 289 BOTAN_UNUSED(side, buffers, handler); 290 BOOST_ASIO_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; 291 throw Not_Implemented("buffered async handshake is not implemented"); 292 } 293 294 //! @} 295 //! \name shutdown methods 296 //! @{ 297 298 /** 299 * @brief Shut down SSL on the stream. 300 * 301 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down 302 * or an error occurs. Note that this will not close the lowest layer. 303 * 304 * Note that this can be used in reaction of a received shutdown alert from the peer. 305 * 306 * @param ec Set to indicate what error occured, if any. 307 */ shutdown(boost::system::error_code & ec)308 void shutdown(boost::system::error_code& ec) 309 { 310 try_with_error_code([&] 311 { 312 native_handle()->close(); 313 }, ec); 314 315 send_pending_encrypted_data(ec); 316 } 317 318 /** 319 * @brief Shut down SSL on the stream. 320 * 321 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down 322 * or an error occurs. Note that this will not close the lowest layer. 323 * 324 * Note that this can be used in reaction of a received shutdown alert from the peer. 325 * 326 * @throws boost::system::system_error if error occured 327 */ shutdown()328 void shutdown() 329 { 330 boost::system::error_code ec; 331 shutdown(ec); 332 boost::asio::detail::throw_error(ec, "shutdown"); 333 } 334 335 private: 336 /** 337 * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler 338 * signature of `AsyncWriteOperation`. 339 * 340 * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of 341 * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor. 342 */ 343 template <typename Handler, typename Executor> 344 struct Wrapper 345 { operatorWrapper346 void operator()(boost::system::error_code ec, std::size_t) 347 { 348 handler(ec); 349 } 350 351 using executor_type = boost::asio::associated_executor_t<Handler, Executor>; 352 get_executorWrapper353 executor_type get_executor() const noexcept 354 { 355 return boost::asio::get_associated_executor(handler, io_executor); 356 } 357 358 using allocator_type = boost::asio::associated_allocator_t<Handler>; 359 get_allocatorWrapper360 allocator_type get_allocator() const noexcept 361 { 362 return boost::asio::get_associated_allocator(handler); 363 } 364 365 Handler handler; 366 Executor io_executor; 367 }; 368 369 public: 370 /** 371 * @brief Asynchronously shut down SSL on the stream. 372 * 373 * This function call always returns immediately. 374 * 375 * Note that this can be used in reaction of a received shutdown alert from the peer. 376 * 377 * @param handler The handler to be called when the shutdown operation completes. 378 * The equivalent function signature of the handler must be: void(boost::system::error_code) 379 */ 380 template <typename ShutdownHandler> async_shutdown(ShutdownHandler && handler)381 void async_shutdown(ShutdownHandler&& handler) 382 { 383 boost::system::error_code ec; 384 try_with_error_code([&] 385 { 386 native_handle()->close(); 387 }, ec); 388 // If ec is set by native_handle->close(), the AsyncWriteOperation created below will do nothing but call the 389 // handler with the error_code set appropriately - no need to early return here. 390 391 using ShutdownHandlerWrapper = Wrapper<ShutdownHandler, typename Stream::executor_type>; 392 393 ShutdownHandlerWrapper w{std::forward<ShutdownHandler>(handler), get_executor()}; 394 BOOST_ASIO_SHUTDOWN_HANDLER_CHECK(ShutdownHandler, w) type_check; 395 396 boost::asio::async_completion<ShutdownHandlerWrapper, void(boost::system::error_code, std::size_t)> 397 init(w); 398 399 detail::AsyncWriteOperation<typename std::decay<ShutdownHandlerWrapper>::type, Stream> 400 op{std::move(init.completion_handler), *this, boost::asio::buffer_size(send_buffer())}; 401 402 return init.result.get(); 403 } 404 405 //! @} 406 //! \name I/O methods 407 //! @{ 408 409 /** 410 * @brief Read some data from the stream. 411 * 412 * The function call will block until one or more bytes of data has been read successfully, or until an error 413 * occurs. 414 * 415 * @param buffers The buffers into which the data will be read. 416 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer 417 * has closed the connection but did not properly shut down the SSL connection. 418 * @return The number of bytes read. Returns 0 if an error occurred. 419 */ 420 template <typename MutableBufferSequence> read_some(const MutableBufferSequence & buffers,boost::system::error_code & ec)421 std::size_t read_some(const MutableBufferSequence& buffers, 422 boost::system::error_code& ec) 423 { 424 if(has_received_data()) 425 { return copy_received_data(buffers); } 426 427 boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; 428 if(ec) 429 { return 0; } 430 431 process_encrypted_data(read_buffer, ec); 432 433 if(ec) // something went wrong in process_encrypted_data() 434 { return 0; } 435 436 if(shutdown_received()) 437 { 438 // we just received a 'close_notify' from the peer and don't expect any more data 439 ec = boost::asio::error::eof; 440 } 441 else if(ec == boost::asio::error::eof) 442 { 443 // we did not expect this disconnection from the peer 444 ec = StreamError::StreamTruncated; 445 } 446 447 return !ec ? copy_received_data(buffers) : 0; 448 } 449 450 /** 451 * @brief Read some data from the stream. 452 * 453 * The function call will block until one or more bytes of data has been read successfully, or until an error 454 * occurs. 455 * 456 * @param buffers The buffers into which the data will be read. 457 * @return The number of bytes read. Returns 0 if an error occurred. 458 * @throws boost::system::system_error if error occured 459 */ 460 template <typename MutableBufferSequence> read_some(const MutableBufferSequence & buffers)461 std::size_t read_some(const MutableBufferSequence& buffers) 462 { 463 boost::system::error_code ec; 464 auto const n = read_some(buffers, ec); 465 boost::asio::detail::throw_error(ec, "read_some"); 466 return n; 467 } 468 469 /** 470 * @brief Write some data to the stream. 471 * 472 * The function call will block until one or more bytes of data has been written successfully, or until an error 473 * occurs. 474 * 475 * @param buffers The data to be written. 476 * @param ec Set to indicate what error occurred, if any. 477 * @return The number of bytes processed from the input buffers. 478 */ 479 template <typename ConstBufferSequence> write_some(const ConstBufferSequence & buffers,boost::system::error_code & ec)480 std::size_t write_some(const ConstBufferSequence& buffers, 481 boost::system::error_code& ec) 482 { 483 tls_encrypt(buffers, ec); 484 send_pending_encrypted_data(ec); 485 return !ec ? boost::asio::buffer_size(buffers) : 0; 486 } 487 488 /** 489 * @brief Write some data to the stream. 490 * 491 * The function call will block until one or more bytes of data has been written successfully, or until an error 492 * occurs. 493 * 494 * @param buffers The data to be written. 495 * @return The number of bytes written. 496 * @throws boost::system::system_error if error occured 497 */ 498 template <typename ConstBufferSequence> write_some(const ConstBufferSequence & buffers)499 std::size_t write_some(const ConstBufferSequence& buffers) 500 { 501 boost::system::error_code ec; 502 auto const n = write_some(buffers, ec); 503 boost::asio::detail::throw_error(ec, "write_some"); 504 return n; 505 } 506 507 /** 508 * @brief Start an asynchronous write. The function call always returns immediately. 509 * 510 * @param buffers The data to be written. 511 * @param handler The handler to be called when the write operation completes. Copies will be made of the handler 512 * as required. The equivalent function signature of the handler must be: 513 * void(boost::system::error_code, std::size_t) 514 */ 515 template <typename ConstBufferSequence, typename WriteHandler> 516 auto async_write_some(const ConstBufferSequence& buffers, WriteHandler&& handler) -> 517 BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, 518 void(boost::system::error_code, std::size_t)) 519 { 520 BOOST_ASIO_WRITE_HANDLER_CHECK(WriteHandler, handler) type_check; 521 522 boost::asio::async_completion<WriteHandler, void(boost::system::error_code, std::size_t)> init(handler); 523 524 boost::system::error_code ec; 525 tls_encrypt(buffers, ec); 526 if(ec) 527 { 528 // we cannot be sure how many bytes were committed here so clear the send_buffer and let the 529 // AsyncWriteOperation call the handler with the error_code set 530 consume_send_buffer(m_send_buffer.size()); 531 detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> 532 op{std::move(init.completion_handler), *this, std::size_t(0), ec}; 533 return init.result.get(); 534 } 535 536 detail::AsyncWriteOperation<typename std::decay<WriteHandler>::type, Stream> 537 op{std::move(init.completion_handler), *this, boost::asio::buffer_size(buffers)}; 538 539 return init.result.get(); 540 } 541 542 /** 543 * @brief Start an asynchronous read. The function call always returns immediately. 544 * 545 * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as 546 * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee 547 * that they remain valid until the handler is called. 548 * @param handler The handler to be called when the read operation completes. The equivalent function signature of 549 * the handler must be: 550 * void(boost::system::error_code, std::size_t) 551 */ 552 template <typename MutableBufferSequence, typename ReadHandler> 553 auto async_read_some(const MutableBufferSequence& buffers, ReadHandler&& handler) -> 554 BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, 555 void(boost::system::error_code, std::size_t)) 556 { 557 BOOST_ASIO_READ_HANDLER_CHECK(ReadHandler, handler) type_check; 558 559 boost::asio::async_completion<ReadHandler, void(boost::system::error_code, std::size_t)> init(handler); 560 561 detail::AsyncReadOperation<typename std::decay<ReadHandler>::type, Stream, MutableBufferSequence> 562 op{std::move(init.completion_handler), *this, buffers}; 563 return init.result.get(); 564 } 565 566 //! @} 567 568 //! @brief Indicates whether a close_notify alert has been received from the peer. shutdown_received()569 bool shutdown_received() const 570 { 571 return m_shutdown_received; 572 } 573 574 protected: 575 template <class H, class S, class M, class A> friend class detail::AsyncReadOperation; 576 template <class H, class S, class A> friend class detail::AsyncWriteOperation; 577 template <class H, class S, class A> friend class detail::AsyncHandshakeOperation; 578 579 /** 580 * @brief Helper class that implements Botan::TLS::Callbacks 581 * 582 * This class is provided to the stream's native_handle (Botan::TLS::Channel) and implements the callback 583 * functions triggered by the native_handle. 584 * 585 * @param receive_buffer reference to the buffer where decrypted data should be placed 586 * @param send_buffer reference to the buffer where encrypted data should be placed 587 */ 588 class StreamCore : public Botan::TLS::Callbacks 589 { 590 public: StreamCore(Stream & stream)591 StreamCore(Stream& stream) : m_stream(stream) {} 592 593 virtual ~StreamCore() = default; 594 tls_emit_data(const uint8_t data[],std::size_t size)595 void tls_emit_data(const uint8_t data[], std::size_t size) override 596 { 597 m_stream.m_send_buffer.commit( 598 boost::asio::buffer_copy(m_stream.m_send_buffer.prepare(size), boost::asio::buffer(data, size)) 599 ); 600 } 601 tls_record_received(uint64_t,const uint8_t data[],std::size_t size)602 void tls_record_received(uint64_t, const uint8_t data[], std::size_t size) override 603 { 604 m_stream.m_receive_buffer.commit( 605 boost::asio::buffer_copy(m_stream.m_receive_buffer.prepare(size), boost::asio::const_buffer(data, size)) 606 ); 607 } 608 tls_alert(Botan::TLS::Alert alert)609 void tls_alert(Botan::TLS::Alert alert) override 610 { 611 if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) 612 { 613 m_stream.set_shutdown_received(); 614 // Channel::process_alert will automatically write the corresponding close_notify response to the 615 // send_buffer and close the native_handle after this function returns. 616 } 617 } 618 tls_verify_cert_chain_ocsp_timeout()619 std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override 620 { 621 return std::chrono::milliseconds(1000); 622 } 623 tls_session_established(const Botan::TLS::Session &)624 bool tls_session_established(const Botan::TLS::Session&) override 625 { 626 // TODO: it should be possible to configure this in the using application (via callback?) 627 return true; 628 } 629 tls_verify_cert_chain(const std::vector<X509_Certificate> & cert_chain,const std::vector<std::shared_ptr<const OCSP::Response>> & ocsp_responses,const std::vector<Certificate_Store * > & trusted_roots,Usage_Type usage,const std::string & hostname,const TLS::Policy & policy)630 void tls_verify_cert_chain( 631 const std::vector<X509_Certificate>& cert_chain, 632 const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses, 633 const std::vector<Certificate_Store*>& trusted_roots, 634 Usage_Type usage, 635 const std::string& hostname, 636 const TLS::Policy& policy) override 637 { 638 if(m_stream.m_context.has_verify_callback()) 639 { 640 m_stream.m_context.get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); 641 } 642 else 643 { 644 Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); 645 } 646 } 647 648 private: 649 Stream& m_stream; 650 }; 651 input_buffer()652 const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; } send_buffer()653 boost::asio::const_buffer send_buffer() const { return m_send_buffer.data(); } 654 655 //! @brief Check if decrypted data is available in the receive buffer has_received_data()656 bool has_received_data() const { return m_receive_buffer.size() > 0; } 657 658 //! @brief Copy decrypted data into the user-provided buffer 659 template <typename MutableBufferSequence> copy_received_data(MutableBufferSequence buffers)660 std::size_t copy_received_data(MutableBufferSequence buffers) 661 { 662 // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the StreamCore with 663 // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record 664 // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller 665 // than the decrypted record, so this optimization might not be worth the additional complexity. 666 const auto copiedBytes = boost::asio::buffer_copy(buffers, m_receive_buffer.data()); 667 m_receive_buffer.consume(copiedBytes); 668 return copiedBytes; 669 } 670 671 //! @brief Check if encrypted data is available in the send buffer has_data_to_send()672 bool has_data_to_send() const { return m_send_buffer.size() > 0; } 673 674 //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer consume_send_buffer(std::size_t bytesConsumed)675 void consume_send_buffer(std::size_t bytesConsumed) { m_send_buffer.consume(bytesConsumed); } 676 677 // This is a helper construct to allow mocking the native_handle in test code. It is activated by explicitly 678 // specifying a (mocked) channel type template parameter when constructing the stream and does not attempt to 679 // instantiate the native_handle. 680 // Note: once we have C++17 we can achieve this much more elegantly using constexpr if. 681 template<class T = ChannelT> 682 typename std::enable_if<!std::is_same<Channel, T>::value>::type setup_native_handle(Connection_Side,boost::system::error_code &)683 setup_native_handle(Connection_Side, boost::system::error_code&) {} 684 685 /** 686 * @brief Create the native handle. 687 * 688 * Depending on the desired connection side, this function will create a Botan::TLS::Client or a 689 * Botan::TLS::Server. 690 * 691 * @param side The desired connection side (client or server) 692 * @param ec Set to indicate what error occurred, if any. 693 */ 694 template<class T = ChannelT> 695 typename std::enable_if<std::is_same<Channel, T>::value>::type setup_native_handle(Connection_Side side,boost::system::error_code & ec)696 setup_native_handle(Connection_Side side, boost::system::error_code& ec) 697 { 698 try_with_error_code([&] 699 { 700 if(side == CLIENT) 701 { 702 m_native_handle = std::unique_ptr<Client>( 703 new Client(m_core, 704 m_context.m_session_manager, 705 m_context.m_credentials_manager, 706 m_context.m_policy, 707 m_context.m_rng, 708 m_context.m_server_info, 709 Protocol_Version::latest_tls_version())); 710 } 711 else 712 { 713 m_native_handle = std::unique_ptr<Server>( 714 new Server(m_core, 715 m_context.m_session_manager, 716 m_context.m_credentials_manager, 717 m_context.m_policy, 718 m_context.m_rng, 719 false /* no DTLS */)); 720 } 721 }, ec); 722 } 723 724 /** @brief Synchronously write encrypted data from the send buffer to the next layer. 725 * 726 * If this function is called with an error code other than 'Success', it will do nothing and return 0. 727 * 728 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer 729 * has closed the connection but did not properly shut down the SSL connection. 730 * @return The number of bytes written. 731 */ send_pending_encrypted_data(boost::system::error_code & ec)732 size_t send_pending_encrypted_data(boost::system::error_code& ec) 733 { 734 if(ec) 735 { return 0; } 736 737 auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec); 738 consume_send_buffer(writtenBytes); 739 740 if(ec == boost::asio::error::eof && !shutdown_received()) 741 { 742 // transport layer was closed by peer without receiving 'close_notify' 743 ec = StreamError::StreamTruncated; 744 } 745 746 return writtenBytes; 747 } 748 749 /** 750 * @brief Pass plaintext data to the native handle for processing. 751 * 752 * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback. 753 */ 754 template <typename ConstBufferSequence> tls_encrypt(const ConstBufferSequence & buffers,boost::system::error_code & ec)755 void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec) 756 { 757 // NOTE: This is not asynchronous: it encrypts the data synchronously. 758 // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core, 759 // but is not actually written to the wire, yet. 760 for(auto it = boost::asio::buffer_sequence_begin(buffers); 761 !ec && it != boost::asio::buffer_sequence_end(buffers); 762 it++) 763 { 764 const boost::asio::const_buffer buffer = *it; 765 try_with_error_code([&] 766 { 767 native_handle()->send(static_cast<const uint8_t*>(buffer.data()), buffer.size()); 768 }, ec); 769 } 770 } 771 772 /** 773 * @brief Pass encrypted data to the native handle for processing. 774 * 775 * If an exception occurs while processing the data, an error code will be set. 776 * 777 * @param read_buffer Input buffer containing the encrypted data. 778 * @param ec Set to indicate what error occurred, if any. 779 */ process_encrypted_data(const boost::asio::const_buffer & read_buffer,boost::system::error_code & ec)780 void process_encrypted_data(const boost::asio::const_buffer& read_buffer, boost::system::error_code& ec) 781 { 782 try_with_error_code([&] 783 { 784 native_handle()->received_data(static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()); 785 }, ec); 786 } 787 788 //! @brief Catch exceptions and set an error_code 789 template <typename Fun> try_with_error_code(Fun f,boost::system::error_code & ec)790 void try_with_error_code(Fun f, boost::system::error_code& ec) 791 { 792 try 793 { 794 f(); 795 } 796 catch(const TLS_Exception& e) 797 { 798 ec = e.type(); 799 } 800 catch(const Botan::Exception& e) 801 { 802 ec = e.error_type(); 803 } 804 catch(const std::exception&) 805 { 806 ec = Botan::ErrorType::Unknown; 807 } 808 } 809 set_shutdown_received()810 void set_shutdown_received() 811 { 812 m_shutdown_received = true; 813 } 814 815 Context& m_context; 816 StreamLayer m_nextLayer; 817 818 boost::beast::flat_buffer m_receive_buffer; 819 boost::beast::flat_buffer m_send_buffer; 820 821 StreamCore m_core; 822 std::unique_ptr<ChannelT> m_native_handle; 823 824 bool m_shutdown_received; 825 826 // Buffer space used to read input intended for the core 827 std::vector<uint8_t> m_input_buffer_space; 828 const boost::asio::mutable_buffer m_input_buffer; 829 }; 830 831 } // namespace TLS 832 } // namespace Botan 833 834 #endif // BOOST_VERSION 835 #endif // BOTAN_ASIO_STREAM_H_ 836