0

My goal is to be able to do load multiple PEM files in boost so it can do a correct SSL handshake with a client depending on the SNI sent in TLS which means a single ip address can host multiple https sites. I am looking through the boost document SSL section, but nothing there about SNI. Because my project depends on boost::asio and I don’t know if it is possible and how to refer the example code, s_server.c, in openssl directory ( https://github.com/openssl/openssl/blob/master/apps/s_server.c#L127 ) THANK YOU for any hints.

And my https server code is here:

template<class service_pool_policy = io_service_pool>
class httpTLS_server_ : private noncopyable {
public:
    template<class... Args>
    explicit httpTLS_server_(Args&&... args) : io_service_pool_(std::forward<Args>(args)...)

        , ctx_(boost::asio::ssl::context::tls_server)

    {
        http_cache::get().set_cache_max_age(86400);
        init_conn_callback();
    }

    void enable_http_cache(bool b) {
        http_cache::get().enable_cache(b);
    }

    template<typename F>
    void init_ssl_context(bool ssl_enable_v3, F&& f, std::string certificate_chain_file,
        std::string private_key_file, std::string tmp_dh_file) {

        unsigned long ssl_options = boost::asio::ssl::context::default_workarounds
            | boost::asio::ssl::context::no_sslv2
            | boost::asio::ssl::context::no_sslv3
            | boost::asio::ssl::context::no_tlsv1
            | boost::asio::ssl::context::single_dh_use;

        ctx_.set_options(ssl_options);
        ctx_.set_password_callback(std::forward<F>(f));
        ctx_.use_certificate_chain_file(std::move(certificate_chain_file));
        ctx_.use_private_key_file(std::move(private_key_file), boost::asio::ssl::context::pem);
        
    }

    //address :
    //      "0.0.0.0" : ipv4. use 'https://localhost/' to visit
    //      "::1" : ipv6. use 'https://[::1]/' to visit
    //      "" : ipv4 & ipv6.
    bool listen(std::string_view address, std::string_view port) {
        boost::asio::ip::tcp::resolver::query query(address.data(), port.data());
        return listen(query);
    }

    //support ipv6 & ipv4
    bool listen(std::string_view port) {
        boost::asio::ip::tcp::resolver::query query(port.data());
        return listen(query);
    }

    bool listen(const boost::asio::ip::tcp::resolver::query & query) {
        boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service());
        boost::asio::ip::tcp::resolver::iterator endpoints = resolver.resolve(query);

        bool r = false;

        for (; endpoints != boost::asio::ip::tcp::resolver::iterator(); ++endpoints) {
            boost::asio::ip::tcp::endpoint endpoint = *endpoints;

            auto acceptor = std::make_shared<boost::asio::ip::tcp::acceptor>(io_service_pool_.get_io_service());
            acceptor->open(endpoint.protocol());
            acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));

            try {
                acceptor->bind(endpoint);
                acceptor->listen();
                start_accept(acceptor);
                r = true;
            }
            catch (const std::exception& ex) {
                std::cout << ex.what() << "\n";
                //LOG_INFO << e.what();
            }
        }

        return r;
    }

    void stop() {
        io_service_pool_.stop();
    }

    void run() {
        if (!fs::exists(public_root_path_.data())) {
            fs::create_directories(public_root_path_.data());
        }

        if (!fs::exists(static_dir_.data())) {
            fs::create_directories(static_dir_.data());
        }

        io_service_pool_.run();
    }

    intptr_t run_one() {
        return io_service_pool_.run_one();
    }

    intptr_t poll() {
        return io_service_pool_.poll();
    }

    intptr_t poll_one() {
        return io_service_pool_.poll_one();
    }

    void set_static_dir(std::string&& path) {
        static_dir_ = public_root_path_+std::move(path)+"/";
    }

    const std::string& static_dir() const {
        return static_dir_;
    }

    //xM
    void set_max_req_buf_size(std::size_t max_buf_size) {
        max_req_buf_size_ = max_buf_size;
    }

    void set_keep_alive_timeout(long seconds) {
        keep_alive_timeout_ = seconds;
    }

    template<typename T>
    bool need_cache(T&& t) {
        if constexpr(std::is_same_v<T, enable_cache<bool>>) {
            return t.value;
        }
        else {
            return false;
        }
    }

    //set http handlers
    template<http_method... Is, typename Function, typename... AP>
    void set_http_handler(std::string_view name, Function&& f, AP&&... ap) {
        if constexpr(has_type<enable_cache<bool>, std::tuple<std::decay_t<AP>...>>::value) {//for cache
            bool b = false;
            ((!b&&(b = need_cache(std::forward<AP>(ap)))),...);
            if (!b) {
                http_cache::get().add_skip(name);
            }else{
                http_cache::get().add_single_cache(name);
            }
            auto tp = filter<enable_cache<bool>>(std::forward<AP>(ap)...);
            auto lm = [this, name, f = std::move(f)](auto... ap) {
                https_router_.register_handler<Is...>(name, std::move(f), std::move(ap)...);
            };
            std::apply(lm, std::move(tp));
        }
        else {
            https_router_.register_handler<Is...>(name, std::forward<Function>(f), std::forward<AP>(ap)...);
        }
    }

    void set_base_path(const std::string& key,const std::string& path)
    {
        base_path_[0] = std::move(key);
        base_path_[1] = std::move(path);
    }

    void set_res_cache_max_age(std::time_t seconds)
    {
        static_res_cache_max_age_ = seconds;
    }

    std::time_t get_res_cache_max_age()
    {
        return static_res_cache_max_age_;
    }

    void set_cache_max_age(std::time_t seconds)
    {
        http_cache::get().set_cache_max_age(seconds);
    }

    std::time_t get_cache_max_age()
    {
        return http_cache::get().get_cache_max_age();
    }

    //don't begin with "./" or "/", not absolutely path
    void set_public_root_directory(const std::string& name)
    {
        if(!name.empty()){
            public_root_path_ = "./"+name+"/";
        }
        else {
            public_root_path_ = "./";
        }
    }

    std::string get_public_root_directory()
    {
        return public_root_path_;
    }

    void set_download_check(std::function<bool(request_ssl& req, response& res)> checker) {
        download_check_ = std::move(checker);
    }

    //should be called before listen
    void set_upload_check(std::function<bool(request_ssl& req, response& res)> checker) {
        upload_check_ = std::move(checker);
    }

    void mapping_to_root_path(std::string relate_path) {
        relate_paths_.emplace_back("."+std::move(relate_path));
    }

private:
    void start_accept(std::shared_ptr<boost::asio::ip::tcp::acceptor> const& acceptor) {
        auto new_conn = std::make_shared<connection_ssl<Socket_ssl>>(
            io_service_pool_.get_io_service(), max_req_buf_size_, keep_alive_timeout_, https_handler_, static_dir_, 
            upload_check_?&upload_check_ : nullptr

            , ctx_

            );
        acceptor->async_accept(new_conn->socket(), [this, new_conn, acceptor](const boost::system::error_code& e) {
            if (!e) {
                new_conn->socket().set_option(boost::asio::ip::tcp::no_delay(true));
                new_conn->start();
            }
            else {
                std::cout << "server::handle_accept: " << e.message();
                //LOG_INFO << "server::handle_accept: " << e.message();
            }

            start_accept(acceptor);
        });
    }

    
    void init_conn_callback() {
        set_static_res_handler();
        https_handler_ = [this](request_ssl& req, response& res) {
            res.set_base_path(this->base_path_[0],this->base_path_[1]);
            res.set_url(req.get_url());
            try {
                bool success = https_router_.route(req.get_method(), req.get_url(), req, res);
                if (!success) {
                    //updated by neo
                    //res.set_status_and_content(status_type::bad_request, "the url is not right");
                    res.redirect("/");
                    //updated end
                }
            }
            catch (const std::exception& ex) {
                res.set_status_and_content(status_type::internal_server_error, ex.what()+std::string(" exception in business function"));
            }
            catch (...) {
                res.set_status_and_content(status_type::internal_server_error, "unknown exception in business function");
            }               
        };
    }

    
    service_pool_policy io_service_pool_;

    std::size_t max_req_buf_size_ = 3 * 1024 * 1024; //max request buffer size 3M
    long keep_alive_timeout_ = 60; //max request timeout 60s

    https_router https_router_;
    std::string static_dir_ = "./public/static/"; //default
    std::string base_path_[2] = {"base_path","/"};
    std::time_t static_res_cache_max_age_ = 0;
    std::string public_root_path_ = "./";

    boost::asio::ssl::context ctx_;
    //SSL_CTX ctx_ ;
    //SSL_CTX *ctx2 = NULL;


    https_handler https_handler_ = nullptr;
    std::function<bool(request_ssl& req, response& res)> download_check_;
    std::vector<std::string> relate_paths_;
    std::function<bool(request_ssl& req, response& res)> upload_check_ = nullptr;
};

using httpTLS_server = httpTLS_server_<io_service_pool>;
  • See https://stackoverflow.com/questions/5113333/how-to-implement-server-name-indication-sni – Alan Birtles Oct 19 '21 at 06:31
  • I've found something on boost(https://www.boost.org/doc/libs/1_74_0/doc/html/boost_asio/reference/ssl__stream/native_handle.html) – Neo Chen Oct 19 '21 at 15:57

0 Answers0