From 8999d084c2371f803eebd553330a9a8eef398b15 Mon Sep 17 00:00:00 2001 From: Wouter van Kleunen Date: Mon, 4 Jan 2021 19:26:10 +0100 Subject: [PATCH] Broker check certificate load error and perform periodic reloading --- example/CMakeLists.txt | 37 +++++++---- example/broker.conf | 12 +++- example/broker.cpp | 101 ++++++++++++++++++++++++----- test/system/CMakeLists.txt | 4 ++ test/system/test_server_tls.hpp | 16 +++++ test/system/test_server_tls_ws.hpp | 16 +++++ test/unit/CMakeLists.txt | 4 ++ 7 files changed, 156 insertions(+), 34 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 3055c2e99..5de77b527 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -50,6 +50,11 @@ FOREACH (source_file ${exec_PROGRAMS}) GET_FILENAME_COMPONENT (source_file_we ${source_file} NAME_WE) ADD_EXECUTABLE (${source_file_we} ${source_file}) TARGET_LINK_LIBRARIES (${source_file_we} mqtt_cpp_iface) + + IF (WIN32 AND MQTT_USE_STATIC_OPENSSL) + TARGET_LINK_LIBRARIES (${source_file_we} Crypt32) + ENDIF () + IF (MQTT_USE_LOG) TARGET_COMPILE_DEFINITIONS (${source_file_we} PUBLIC $,,BOOST_LOG_DYN_LINK>) TARGET_LINK_LIBRARIES (${source_file_we} Boost::log) @@ -58,19 +63,23 @@ FOREACH (source_file ${exec_PROGRAMS}) TARGET_LINK_LIBRARIES (${source_file_we} Boost::program_options) ENDFOREACH () -FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../example/broker.conf DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.crt.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.key.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/cacert.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +FILE(COPY broker.conf DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) +FILE(COPY ../test/certs/mosquitto.org.crt DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) +FILE(COPY ../test/certs/server.crt.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) +FILE(COPY ../test/certs/server.key.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) +FILE(COPY ../test/certs/cacert.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) IF ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/mosquitto.org.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Release) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.crt.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Release) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.key.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Release) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/cacert.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Release) -ELSE () - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/mosquitto.org.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.crt.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/server.key.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - FILE(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../test/certs/cacert.pem DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -ENDIF () + FILE(COPY broker.conf DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Release) + FILE(COPY ../test/certs/mosquitto.org.crt DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Release") + FILE(COPY ../test/certs/server.crt.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Release") + FILE(COPY ../test/certs/server.key.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Release") + FILE(COPY ../test/certs/cacert.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Release") + + FILE(COPY broker.conf DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Debug) + FILE(COPY ../test/certs/mosquitto.org.crt DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Debug") + FILE(COPY ../test/certs/server.crt.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Debug") + FILE(COPY ../test/certs/server.key.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Debug") + FILE(COPY ../test/certs/cacert.pem DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Debug") + + ENDIF () diff --git a/example/broker.conf b/example/broker.conf index e9b2b26e8..afde5d1b1 100644 --- a/example/broker.conf +++ b/example/broker.conf @@ -1,7 +1,13 @@ # Default configuration for MQTT-CPP Broker verbose=1 -certificate=broker.crt.pem -private_key=broker.key.pem +certificate=server.crt.pem +private_key=server.key.pem + +# Reload interval for the certificate and private key files (hours) +# When configured the broker will perform automatic loading of +# cert/key update. If not set or set to 0 (default), then no +# reloading is performed. +# certificate_reload_interval=24 # Configuration for TCP [tcp] @@ -17,4 +23,4 @@ port=1883 # Configuration for Websocket with TLS [wss] -# port=10433 +# port=10443 diff --git a/example/broker.cpp b/example/broker.cpp index 0119ef827..f3957db22 100644 --- a/example/broker.cpp +++ b/example/broker.cpp @@ -16,20 +16,79 @@ #include #if defined(MQTT_USE_TLS) -boost::asio::ssl::context init_ctx(boost::program_options::variables_map const& vm) +boost::asio::ssl::context init_ctx() { - if (vm.count("certificate") == 0 && vm.count("private_key") == 0) { - throw std::runtime_error("TLS requested but certificate and/or private_key not specified"); - } - boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::single_dh_use); - ctx.use_certificate_file(vm["certificate"].as(), boost::asio::ssl::context::pem); - ctx.use_private_key_file(vm["private_key"].as(), boost::asio::ssl::context::pem); return ctx; } + +template +void reload_ctx(Server& server, boost::asio::steady_timer& reload_timer, + std::string const& certificate_filename, + std::string const& key_filename, + unsigned int certificate_reload_interval, + char const* name, bool first_load = true) +{ + MQTT_LOG("mqtt_broker", info) << "Reloading certificates for server " << name; + + if (certificate_reload_interval > 0) { + reload_timer.expires_after(std::chrono::hours(certificate_reload_interval)); + reload_timer.async_wait( + [&server, &reload_timer, certificate_filename, key_filename, certificate_reload_interval, name] + (boost::system::error_code const& e) { + + BOOST_ASSERT(!e || e == boost::asio::error::operation_aborted); + + if (!e) { + reload_ctx(server, reload_timer, certificate_filename, key_filename, certificate_reload_interval, name, false); + } + }); + } + + auto context = init_ctx(); + + boost::system::error_code ec; + context.use_certificate_file(certificate_filename, boost::asio::ssl::context::pem, ec); + if (ec) { + auto message = "Failed to load certificate file: " + ec.message(); + if (first_load) { + throw std::runtime_error(message); + } + + MQTT_LOG("mqtt_broker", warning) << message; + return; + } + + context.use_private_key_file(key_filename, boost::asio::ssl::context::pem, ec); + if (ec) { + auto message = "Failed to load private key file: " + ec.message(); + if (first_load) { + throw std::runtime_error(message); + } + + MQTT_LOG("mqtt_broker", warning) << message; + return; + } + + server.get_ssl_context() = std::move(context); +} + +template +void load_ctx(Server& server, boost::asio::steady_timer& reload_timer, boost::program_options::variables_map const& vm, char const* name) +{ + if (vm.count("certificate") == 0 && vm.count("private_key") == 0) { + throw std::runtime_error("TLS requested but certificate and/or private_key not specified"); + } + + reload_ctx(server, reload_timer, + vm["certificate"].as(), + vm["private_key"].as(), + vm["certificate_reload_interval"].as(), + name, true); +} #endif // defined(MQTT_USE_TLS) void run_broker(boost::program_options::variables_map const& vm) @@ -38,33 +97,40 @@ void run_broker(boost::program_options::variables_map const& vm) boost::asio::io_context ioc; MQTT_NS::broker::broker_t b(ioc); - std::unique_ptr s; + MQTT_NS::optional s; if (vm.count("tcp.port")) { - s = std::make_unique(ioc, b, vm["tcp.port"].as()); + s.emplace(ioc, b, vm["tcp.port"].as()); } #if defined(MQTT_USE_WS) - std::unique_ptr s_ws; + MQTT_NS::optional s_ws; if (vm.count("ws.port")) { - s_ws = std::make_unique(ioc, b, vm["ws.port"].as()); + s_ws.emplace(ioc, b, vm["ws.port"].as()); } #endif // defined(MQTT_USE_WS) #if defined(MQTT_USE_TLS) - std::unique_ptr s_tls; + MQTT_NS::optional s_tls; + MQTT_NS::optional s_lts_timer; + if (vm.count("tls.port")) { - s_tls = std::make_unique(ioc, init_ctx(vm), b, vm["tls.port"].as()); + s_tls.emplace(ioc, init_ctx(), b, vm["tls.port"].as()); + s_lts_timer.emplace(ioc); + load_ctx(s_tls.value(), s_lts_timer.value(), vm, "TLS"); } #endif // defined(MQTT_USE_TLS) #if defined(MQTT_USE_TLS) && defined(MQTT_USE_WS) - std::unique_ptr s_tls_ws; + MQTT_NS::optional s_tls_ws; + MQTT_NS::optional s_tls_ws_timer; + if (vm.count("wss.port")) { - s_tls_ws = std::make_unique(ioc, init_ctx(vm), b, vm["wss.port"].as()); + s_tls_ws.emplace(ioc, init_ctx(), b, vm["wss.port"].as()); + s_tls_ws_timer.emplace(ioc); + load_ctx(s_tls_ws.value(), s_tls_ws_timer.value(), vm, "WSS"); } #endif // defined(MQTT_USE_TLS) && defined(MQTT_USE_WS) - ioc.run(); } catch(std::exception &e) { MQTT_LOG("mqtt_broker", error) << e.what(); @@ -84,7 +150,8 @@ int main(int argc, char **argv) { #endif // defined(MQTT_USE_LOG) ("certificate", boost::program_options::value(), "Certificate file for TLS connections") ("private_key", boost::program_options::value(), "Private key file for TLS connections") - ; + ("certificate_reload_interval", boost::program_options::value()->default_value(0), "Reload interval for the certificate and private key files (hours)\n 0 - Disabled") + ; boost::program_options::options_description notls_desc("TCP Server options"); notls_desc.add_options() diff --git a/test/system/CMakeLists.txt b/test/system/CMakeLists.txt index 9bbe337af..b46797782 100644 --- a/test/system/CMakeLists.txt +++ b/test/system/CMakeLists.txt @@ -83,6 +83,10 @@ FOREACH (source_file ${check_PROGRAMS}) TARGET_LINK_LIBRARIES ( ${source_file_we} mqtt_cpp_iface Boost::unit_test_framework ) + IF (WIN32 AND MQTT_USE_STATIC_OPENSSL) + TARGET_LINK_LIBRARIES (${source_file_we} Crypt32) + ENDIF () + IF (MQTT_USE_LOG) TARGET_COMPILE_DEFINITIONS (${source_file_we} PUBLIC $,,BOOST_LOG_DYN_LINK>) TARGET_LINK_LIBRARIES ( diff --git a/test/system/test_server_tls.hpp b/test/system/test_server_tls.hpp index 2ace47fa8..480e29b68 100644 --- a/test/system/test_server_tls.hpp +++ b/test/system/test_server_tls.hpp @@ -59,6 +59,22 @@ class test_server_tls { server_.close(); } + /** + * @brief Get boost asio ssl context. + * @return ssl context + */ + MQTT_NS::tls::context& get_ssl_context() { + return server_.get_ssl_context(); + } + + /** + * @brief Get boost asio ssl context. + * @return ssl context + */ + MQTT_NS::tls::context const& get_ssl_context() const { + return server_.get_ssl_context(); + } + private: MQTT_NS::server_tls<> server_; MQTT_NS::broker::broker_t& b_; diff --git a/test/system/test_server_tls_ws.hpp b/test/system/test_server_tls_ws.hpp index 8604a6312..1351ded1f 100644 --- a/test/system/test_server_tls_ws.hpp +++ b/test/system/test_server_tls_ws.hpp @@ -60,6 +60,22 @@ class test_server_tls_ws { server_.close(); } + /** + * @brief Get boost asio ssl context. + * @return ssl context + */ + MQTT_NS::tls::context& get_ssl_context() { + return server_.get_ssl_context(); + } + + /** + * @brief Get boost asio ssl context. + * @return ssl context + */ + MQTT_NS::tls::context const& get_ssl_context() const { + return server_.get_ssl_context(); + } + private: MQTT_NS::server_tls_ws<> server_; MQTT_NS::broker::broker_t& b_; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 04b3e1f8f..0fc1a16b5 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -39,6 +39,10 @@ FOREACH (source_file ${check_PROGRAMS}) TARGET_LINK_LIBRARIES ( ${source_file_we} mqtt_cpp_iface Boost::unit_test_framework ) + IF (WIN32 AND MQTT_USE_STATIC_OPENSSL) + TARGET_LINK_LIBRARIES (${source_file_we} Crypt32) + ENDIF () + IF (MQTT_USE_LOG) TARGET_COMPILE_DEFINITIONS (${source_file_we} PUBLIC $,,BOOST_LOG_DYN_LINK>) TARGET_LINK_LIBRARIES (