From e234229ae80da0fa9967b797f7b5f4f381cba4b4 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 5 Apr 2020 14:22:31 +0200 Subject: All certificates configurable per site --- TODO | 2 +- config.cpp | 16 ++--- config.h | 4 +- https.cpp | 177 ++++++++++++++++++++++++++++++++++++++++++++------- https.h | 15 ++++- server_certificate.h | 67 ------------------- webserver.conf | 6 +- 7 files changed, 181 insertions(+), 106 deletions(-) delete mode 100644 server_certificate.h diff --git a/TODO b/TODO index 5ec5b0e..26cd06d 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ Certbot: https://certbot.eff.org/lets-encrypt/debianbuster-other Webbox Debian 10 -alternative hosts www, lists, ... drop privileges: www-data,www-data, ...? Speed up DocRoot, use string_view +read: The socket was closed due to a timeout diff --git a/config.cpp b/config.cpp index 6050768..072b1bd 100644 --- a/config.cpp +++ b/config.cpp @@ -58,6 +58,10 @@ void Config::readConfigfile(std::string filename) path.params[param.first.data()] = param.second.data(); } site_struct.paths.push_back(path); + } else if (x.first == "certpath"s) { + site_struct.cert_path = x.second.data(); + } else if (x.first == "keypath"s) { + site_struct.key_path = x.second.data(); } else throw std::runtime_error("Unknown element: "s + x.first); } @@ -80,10 +84,6 @@ void Config::readConfigfile(std::string filename) socket_struct.protocol = SocketProtocol::HTTPS; else throw std::runtime_error("Unknown protocol: "s + x.second.data()); - } else if (x.first == "certpath"s) { - socket_struct.cert_path = x.second.data(); - } else if (x.first == "keypath"s) { - socket_struct.key_path = x.second.data(); } else throw std::runtime_error("Unknown element: "s + x.first); } @@ -165,14 +165,14 @@ void Config::dump() const std::cout << " " << param.first << ": " << param.second << std::endl; } } + if (site.key_path != ""s) { + std::cout << " Key: " << site.key_path.generic_string() << std::endl; + std::cout << " Cert: " << site.cert_path.generic_string() << std::endl; + } } for (const auto& socket: m_sockets) { std::cout << "Socket: " << socket.address << ":" << socket.port << " (" << (socket.protocol == SocketProtocol::HTTP ? "HTTP" : "HTTPS") << ")" << std::endl; - if (socket.protocol == SocketProtocol::HTTPS) { - std::cout << " Key: " << socket.key_path.generic_string() << std::endl; - std::cout << " Cert: " << socket.cert_path.generic_string() << std::endl; - } std::cout << " Serving:"; for (const auto& site: socket.serve_sites) { std::cout << " " << site; diff --git a/config.h b/config.h index 1c938c0..801b5fa 100644 --- a/config.h +++ b/config.h @@ -26,6 +26,8 @@ struct Site std::string name; std::unordered_set hosts; std::vector paths; + fs::path cert_path; + fs::path key_path; }; enum class SocketProtocol @@ -40,8 +42,6 @@ struct Socket std::string port; SocketProtocol protocol; std::vector serve_sites; // if empty, automatically expand to all configured sites - fs::path cert_path; - fs::path key_path; }; class Config diff --git a/https.cpp b/https.cpp index 9d0784e..0e45272 100644 --- a/https.cpp +++ b/https.cpp @@ -1,22 +1,27 @@ #include "https.h" +#include "config.h" +#include "file.h" #include "server.h" #include #if BOOST_VERSION == 107100 -#include "server_certificate.h" - #include + +#include #include #include #include #include #include +#include #include #include + #include +#include #include #include #include @@ -519,39 +524,161 @@ private: } }; -} // anonymous namespace -//------------------------------------------------------------------------------ +/* Load a signed certificate into the ssl context, and configure + the context for use with a server. -namespace HTTPS { + For this to work with the browser or operating system, it is + necessary to import the "Beast Test CA" certificate into + the local certificate store, browser, or operating system + depending on your environment Please see the documentation + accompanying the Beast certificate for more details. +*/ +void load_server_certificate(boost::asio::ssl::context& ctx, fs::path cert_path, fs::path key_path) +{ + /* + The certificate was generated from CMD.EXE on Windows 10 using: + + winpty openssl dhparam -out dh.pem 2048 + winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" + */ + + std::string const dh = + "-----BEGIN DH PARAMETERS-----\n" + "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" + "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" + "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" + "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" + "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" + "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" + "-----END DH PARAMETERS-----\n"; + + ctx.set_password_callback( + [](std::size_t, + boost::asio::ssl::context_base::password_purpose) + { + return "test"; + }); + + ctx.set_options( + boost::asio::ssl::context::default_workarounds | + boost::asio::ssl::context::no_sslv2 | + boost::asio::ssl::context::single_dh_use); + + std::string cert; + if (cert_path == "") { + cert = + "-----BEGIN CERTIFICATE-----\n" + "MIIDnTCCAoWgAwIBAgIULkYtO+2Ddeg+qLZ+aDQpmA5b4L0wDQYJKoZIhvcNAQEL\n" + "BQAwXjELMAkGA1UEBhMCREUxEDAOBgNVBAgMB0JhdmFyaWExDzANBgNVBAcMBk11\n" + "bmljaDEVMBMGA1UECgwMUmVpY2h3ZWluIElUMRUwEwYDVQQDDAxyZWljaHdlaW4u\n" + "aXQwHhcNMjAwNDA1MDgwNzIyWhcNNDcwODIyMDgwNzIyWjBeMQswCQYDVQQGEwJE\n" + "RTEQMA4GA1UECAwHQmF2YXJpYTEPMA0GA1UEBwwGTXVuaWNoMRUwEwYDVQQKDAxS\n" + "ZWljaHdlaW4gSVQxFTATBgNVBAMMDHJlaWNod2Vpbi5pdDCCASIwDQYJKoZIhvcN\n" + "AQEBBQADggEPADCCAQoCggEBALJNb0WLbz+xP+YITMMk+eeK/SIOCRFs/9aZIAyK\n" + "ParGauxa+8d25mlfJTAo6/G0h3sA240JHyNpOzVOogPU+v4dRWyGO0w5vHVD0caB\n" + "rDb1eEfmLtqfKLLUL9iPDReUh6WAE7qoNDtfoT551uSMIae1cpPUduVTnSkEgw8k\n" + "NjJSHYT800jSB2R+e7tJG3ErXDM63R3B8RbitZPoWACjpBxDT+Qrj0fBFS4AWw6b\n" + "z09uitv0RrgI6CW7xRh3UAdRwEBGHiU6HTIthX6LNgez1UL0sfu1iZ22wNmYZP/S\n" + "sL3b20WtSH9LN2PRJ4q3AGt6RMbmSGr65ljha9xkTFna0Y8CAwEAAaNTMFEwHQYD\n" + "VR0OBBYEFKd5/MGFZUAUV502vJ/Kcswax8WVMB8GA1UdIwQYMBaAFKd5/MGFZUAU\n" + "V502vJ/Kcswax8WVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB\n" + "AIBS4AfM7wiunQ2UZQQ5A0Un99+BLax9e+h11h/jGeJ+/9maY/E9MK6UG9LXoOv2\n" + "z32Q7Ta2xKeRu6GC/qupwYJ0Xt3LENOfogsaNCAgxKlAN48LGlRyCTvzWsEMh28j\n" + "RaelWonh2qQoiryKLVnRwrg8g1Bu4v+V437cIBmeZPxf0spEL9EVqlN+iS8plmel\n" + "7/F4ULdybKGq39tgicuS7JhnY21ZzOFoq0bWnKBbAeTndmuROdb3pEppxW6pwu0q\n" + "TFdMrSJE38kiQh2O9IchPQbTZ+Rdj0HE9NxStlrNr5bu6rjikRm50/G3JoXpzYdp\n" + "AN4ZI2QZ6R6Y+TzDixKecNk=\n" + "-----END CERTIFICATE-----\n" + ; + } else { + cert = File::getFile(cert_path); + } + + ctx.use_certificate_chain( + boost::asio::buffer(cert.data(), cert.size())); + + std::string key; + if (key_path == "") { + key = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyTW9Fi28/sT/m\n" + "CEzDJPnniv0iDgkRbP/WmSAMij2qxmrsWvvHduZpXyUwKOvxtId7ANuNCR8jaTs1\n" + "TqID1Pr+HUVshjtMObx1Q9HGgaw29XhH5i7anyiy1C/Yjw0XlIelgBO6qDQ7X6E+\n" + "edbkjCGntXKT1HblU50pBIMPJDYyUh2E/NNI0gdkfnu7SRtxK1wzOt0dwfEW4rWT\n" + "6FgAo6QcQ0/kK49HwRUuAFsOm89Pborb9Ea4COglu8UYd1AHUcBARh4lOh0yLYV+\n" + "izYHs9VC9LH7tYmdtsDZmGT/0rC929tFrUh/Szdj0SeKtwBrekTG5khq+uZY4Wvc\n" + "ZExZ2tGPAgMBAAECggEBAK9bJKIa3dCgPB257/TEOtsTgJyrfROcRYkCk9iBZOC9\n" + "v46wdIrZTwY2wtY4iMPwLoY0c7ijTfJ/nfFxYjmujyK4Gvz+jvcKmWQizP8TrRFo\n" + "HWFo6o+slFQ8BspO9itIspd7/OtIXgY+qNBO959Sig7sjsEA5eXoc9pRS6vqizq0\n" + "j4G/UO5Amr/l3ciEJiqMJgZsIVLDKaGlqFTymydSqkB8UHQYWK1kunQxhK4Ldycu\n" + "hTooQE7tXM0zvoFVV6v1fldV5OFsZk2kPMNtvMO6ZEpOM4rNMlg+vJy8kB1fb3Gs\n" + "iFE/DCUpZsMSserQMU9/hfrYlndgsFD5Sr1EVGEebhECgYEA1gc9qx+ugdhYTY5j\n" + "tJDXjOsnw8KY/l/1y+mQ8XNJ9MVdBGy1WB+uWB4teiyJchV49gn2XlKUK2rcCBvZ\n" + "vC5CwPmFi2t70JezQgnXtDlbR0bARPlRd741i4rBpD7hEiZNCTOd2HFBpUg/CGWN\n" + "E4n1ksazBm6jvv3Jo6WAa07Z390CgYEA1USrFqmc/fKGQpTCdH0qYZv3hQtrb1TQ\n" + "9YnrbhtaC0haPpintZKjvhU3tCd1fPuIDXtMAgaaKSyoGiE2aMvLxt1/eV08BkMi\n" + "kGIss9poYNi5+6ZD9QAHmHJhzZtVGj8U5L8379XmwxAByiBRVVE8CW1X/e6+iJpz\n" + "+CLgN+zEVlsCgYEAsuOAdtxXJm4meERwL8b0cvNF3Eh1Sf/42MPTAwzCntSrh3w5\n" + "InvwY/RtPHWnN/ScksEG7BWHhLafTCPDHJdp8hNcvIhNB68UBDln0loyYePP5pag\n" + "sj4IUSbb7SUlR989elhrMTKQlM5K6QDAJrmjyVdM4S5urL9A3wgAyzAvyP0CgYAO\n" + "paGuc8WxdzebWQYl4/bGL2UHgSpGwid7xZYiwMQlZDm2dNuHz+NpCaICwHcEN243\n" + "ptEojnWGAGgnK0LGXcDIDqxTlICr2W6FRgjV7Vkf1aKoUtn1+KOM58YpzdJBdDWm\n" + "JC/eS+2GVhIZZLDRUDv0VcsmSIBTd3AhiZumm588YwKBgBZfNqfmHAwIP2pM1wml\n" + "Ck3vaLLvonghj3iQW9CFJ/SqLOnfT4KJkFObR6oGbxY0RtXsCrmSqidIKgDd0Kkq\n" + "L6QbHp2j3+16GBdmLNUJlfjBTNPJp69IDKztjeCX7/8JZs79p/LAv+I9Sh4lVw4O\n" + "IrDprlB0yzP5zigcsAZeViYJ\n" + "-----END PRIVATE KEY-----\n" + ; + } else { + key = File::getFile(key_path); + } + ctx.use_private_key( + boost::asio::buffer(key.data(), key.size()), + boost::asio::ssl::context::file_format::pem); + + ctx.use_tmp_dh( + boost::asio::buffer(dh.data(), dh.size())); +} int servername_callback(SSL *s, int *al, void *arg) { int type {SSL_get_servername_type(s)}; std::string server_name {SSL_get_servername(s, type)}; - Server* server = (Server*)arg; - if (server_name == "lists.antcom.de"s) { - SSL_set_SSL_CTX(s, server->m_ctx.native_handle()); - } else { - SSL_set_SSL_CTX(s, server->m_ctx2.native_handle()); - } + HTTPS::Server::ctx_type* ctx_map = (HTTPS::Server::ctx_type*)arg; + + ssl::context& ctx = *(ctx_map->at(server_name)); + + SSL_set_SSL_CTX(s, ctx.native_handle()); + return SSL_TLSEXT_ERR_OK; } +} // anonymous namespace +//------------------------------------------------------------------------------ + +namespace HTTPS { + Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket): ::Server(config, ioc), m_socket(socket) { - for (const auto& site: socket.serve_sites) { - std::cout << "Creating SSL context/cert for site " << site << std::endl; - } + for (const auto& serve_site: socket.serve_sites) { + for (const auto& site: config.Sites()) { + if (site.name == serve_site) { + std::shared_ptr ctx {std::make_shared(tls_method)}; - load_server_certificate(m_ctx, m_socket.cert_path, m_socket.key_path); - load_server_certificate(m_ctx2, "/home/ernie/code/webserver/cert.pem", "/home/ernie/code/webserver/key.pem"); - - SSL_CTX_set_tlsext_servername_callback(m_ctx.native_handle(), servername_callback); - SSL_CTX_set_tlsext_servername_arg(m_ctx.native_handle(), this); - - SSL_CTX_set_tlsext_servername_callback(m_ctx2.native_handle(), servername_callback); - SSL_CTX_set_tlsext_servername_arg(m_ctx2.native_handle(), this); + std::cout << "Creating SSL context/cert for site " << serve_site << std::endl; + + load_server_certificate(*ctx, site.cert_path, site.key_path); + SSL_CTX_set_tlsext_servername_callback(ctx->native_handle(), servername_callback); + SSL_CTX_set_tlsext_servername_arg(ctx->native_handle(), &m_ctx); + + for (const auto& host: site.hosts) { + std::cout << " Adding Host " << host << std::endl; + m_ctx.emplace(host, ctx); + } + } + } + } } Server::~Server() @@ -563,10 +690,14 @@ int Server::start() auto const address = net::ip::make_address(m_socket.address); auto const port = static_cast(std::atoi(m_socket.port.data())); + load_server_certificate(m_ctx_dummy, "", ""); // initial dummy, before we can add specific ctx w/ certificate + SSL_CTX_set_tlsext_servername_callback(m_ctx_dummy.native_handle(), servername_callback); + SSL_CTX_set_tlsext_servername_arg(m_ctx_dummy.native_handle(), &m_ctx); + // Create and launch a listening port std::make_shared( m_ioc, - m_ctx, + m_ctx_dummy, tcp::endpoint{address, port}, m_config, m_socket)->run(); diff --git a/https.h b/https.h index 13a67a7..e009b78 100644 --- a/https.h +++ b/https.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include #include #include @@ -12,12 +16,17 @@ namespace ssl = boost::asio::ssl; // from namespace HTTPS { +static const ssl::context_base::method tls_method {ssl::context::tlsv13}; + class Server: public ::Server { - // The SSL context is required, and holds certificates public: - ssl::context m_ctx{ssl::context::tlsv13}; - ssl::context m_ctx2{ssl::context::tlsv13}; + typedef std::unordered_map> ctx_type; + +private: + ctx_type m_ctx; + ssl::context m_ctx_dummy{tls_method}; // Initial use, will be replaced by host specific context (with specific certificate) + const Socket& m_socket; public: diff --git a/server_certificate.h b/server_certificate.h deleted file mode 100644 index 1dc12a4..0000000 --- a/server_certificate.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP -#define BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP - -#include -#include -#include -#include - -#include "config.h" -#include "file.h" - -/* Load a signed certificate into the ssl context, and configure - the context for use with a server. - - For this to work with the browser or operating system, it is - necessary to import the "Beast Test CA" certificate into - the local certificate store, browser, or operating system - depending on your environment Please see the documentation - accompanying the Beast certificate for more details. -*/ -inline -void -load_server_certificate(boost::asio::ssl::context& ctx, fs::path cert_path, fs::path key_path) -{ - /* - The certificate was generated from CMD.EXE on Windows 10 using: - - winpty openssl dhparam -out dh.pem 2048 - winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" - */ - - std::string const cert = File::getFile(cert_path); - std::string const key = File::getFile(key_path); - std::string const dh = - "-----BEGIN DH PARAMETERS-----\n" - "MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n" - "/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n" - "4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n" - "tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n" - "oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n" - "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" - "-----END DH PARAMETERS-----\n"; - - ctx.set_password_callback( - [](std::size_t, - boost::asio::ssl::context_base::password_purpose) - { - return "test"; - }); - - ctx.set_options( - boost::asio::ssl::context::default_workarounds | - boost::asio::ssl::context::no_sslv2 | - boost::asio::ssl::context::single_dh_use); - - ctx.use_certificate_chain( - boost::asio::buffer(cert.data(), cert.size())); - - ctx.use_private_key( - boost::asio::buffer(key.data(), key.size()), - boost::asio::ssl::context::file_format::pem); - - ctx.use_tmp_dh( - boost::asio::buffer(dh.data(), dh.size())); -} - -#endif diff --git a/webserver.conf b/webserver.conf index 46d4120..38af8ab 100644 --- a/webserver.conf +++ b/webserver.conf @@ -22,6 +22,8 @@ /var/lib/webbox --> + /home/ernie/code/webserver/fullchain.pem + /home/ernie/code/webserver/privkey.pem marx @@ -29,6 +31,8 @@ /home/ernie/homepage/test1 + /home/ernie/code/webserver/cert.pem + /home/ernie/code/webserver/key.pem