From edb8e44b5a12776a6b5ce4bb5316e4c8acdd858a Mon Sep 17 00:00:00 2001
From: Roland Reichwein <mail@reichwein.it>
Date: Sat, 7 Jan 2023 12:09:14 +0100
Subject: Fix TLS certificate verification

---
 https.cpp                | 17 +++++++++--
 tests/test-webserver.cpp | 78 ++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 79 insertions(+), 16 deletions(-)

diff --git a/https.cpp b/https.cpp
index 10f76e0..523acb5 100644
--- a/https.cpp
+++ b/https.cpp
@@ -476,7 +476,7 @@ void load_server_certificate(boost::asio::ssl::context& ctx, const fs::path& cer
         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"
+        winpty openssl req -newkey rsa:4096 -sha256 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=DE\ST=BY\L=Munich\O=Reichwein\CN=reichwein.it"
     */
 
     std::string const dh =
@@ -531,7 +531,7 @@ void load_server_certificate(boost::asio::ssl::context& ctx, const fs::path& cer
 
     std::string key;
     if (key_path == "") {
-     // generate dummy self signed certificate. Will be replaced by real
+     // use dummy self signed key. Will be replaced by real
      // certificate if configured upon respective session
      key =
       "-----BEGIN PRIVATE KEY-----\n"
@@ -581,6 +581,15 @@ int ServerNameError(SSL *s, HTTPS::Server::ctx_type& ctx_map)
  return SSL_CLIENT_HELLO_SUCCESS; // OK for now
 }
 
+std::string unbracketed(const std::string& s)
+{
+ if (s.size() >= 2 && s.front() == '[' && s.back() == ']') {
+  return s.substr(1, s.size() - 2);
+ } else {
+  return s;
+ }
+}
+
 int servername_callback(SSL *s, int *al, void *arg)
 {
  HTTPS::Server::ctx_type& ctx_map = *(HTTPS::Server::ctx_type*)arg;
@@ -618,6 +627,8 @@ int servername_callback(SSL *s, int *al, void *arg)
  if (server_name.size() >= 5 && server_name[0] == '\0')
   server_name = server_name.substr(5);
 
+ server_name = unbracketed(server_name);
+
  auto it {ctx_map.find(server_name)};
  std::shared_ptr<ssl::context> ctx{};
  if (it != ctx_map.end()) {
@@ -668,7 +679,7 @@ void Server::load_certificates()
 
     for (const auto& host: site.second.hosts) {
      std::cout << "  Adding Host " << host << std::endl;
-     m_ctx.emplace(host, ctx);
+     m_ctx.emplace(unbracketed(host), ctx);
     }
    }
   }
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index feb4f2a..53aa9cc 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -52,10 +52,13 @@ namespace pt = boost::property_tree;
 using namespace boost::unit_test;
 using namespace Reichwein;
 
+const fs::path testConfigFilename{"./webserver.conf"};
+const fs::path testCertFilename{"./testchain.pem"};
+const fs::path testKeyFilename{"./testkey.pem"};
+
 class WebserverProcess
 {
 public:
- const fs::path testConfigFilename{"./webserver.conf"};
  WebserverProcess(): m_pid{}
  {
   File::setFile(testConfigFilename, R"CONFIG(<webserver>
@@ -74,8 +77,8 @@ public:
     <plugin>static-files</plugin>
     <target>.</target>
    </path>
-   <certpath>../fullchain.pem</certpath>
-   <keypath>../privkey.pem</keypath>
+   <certpath>testchain.pem</certpath>
+   <keypath>testkey.pem</keypath>
   </site>
  </sites>
  <sockets>
@@ -105,9 +108,57 @@ public:
   </socket>
  </sockets>
 </webserver>
-
 )CONFIG");
 
+  // test self signed certificate
+  File::setFile(testCertFilename, R"(-----BEGIN CERTIFICATE-----
+MIIC4zCCAcugAwIBAgIUeS9y+EsFWxf+foEx6SJ/R56rmX8wDQYJKoZIhvcNAQEL
+BQAwADAgFw0yMzAxMDYxNzIwNTFaGA8yMDUwMDUyNDE3MjA1MVowADCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALiZSICAcXng9j7zAb873U4TpuzvRVfh
+xS3gEhxqNPs6+ZQ43nAxDSdafzfGxpTkElTt/REj4oEOLw+QWI/jfbe4gDRDzf6V
+ij0fVuzp02JtJSS+dNrLv17NufBydOyD8oDrPehVrPlrZQhhkYMvLHAim+wikT2O
+s0es2R+avixxAZvx5EYgHba9T7R/pC/lA4BI3lEbVKjDA83hZvjPH1YdK+RYQS2g
+Jygdhe8qOSswXIwFAF3MMBpwRD3mz+vAJZP3lpBGsn+asO6Xd/5cjC8msgomS8Ji
+c9DMMNlrE1WU73wVG9n0OJcke2XEtzARVKJLlBPsug4oxDev6O4GakkCAwEAAaNT
+MFEwHQYDVR0OBBYEFE4i7Gtyn30qpIkH6f0/wuFA45pjMB8GA1UdIwQYMBaAFE4i
+7Gtyn30qpIkH6f0/wuFA45pjMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBAIlGv4b2yLmTOrXOPNst2y3J+GiRvuMKoAfDt5KLxUhbCmPgJzGDWn0l
+60xXBX/t2uo3dQa9yAIW64RqhEQX7uja/7B3PmJZlgF7+owvT8OZA4+UN1lLUvY4
+V7mUzuKuqo5jcX8EmZnHrJ4TGZ0dXbT1hAUgqIjnDChjWyvs4B9zZL5FTisPUic7
+MU+FcpKJ5M6iJ150d9hzLiwmJyPLkW5Grq0Jh22njUQwWW2vIMn4cA3CyS64+oi2
+DNnDgde3mYxXL8Oki7CbeCTpmUXcBHmQtWOvKZPCsOzMF4moTLC4DdElvOpwKCAK
+ABd6rubkarwvDV7wEo1eSuAHPZ/KhGo=
+-----END CERTIFICATE-----
+)");
+  File::setFile(testKeyFilename, R"(-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4mUiAgHF54PY+
+8wG/O91OE6bs70VX4cUt4BIcajT7OvmUON5wMQ0nWn83xsaU5BJU7f0RI+KBDi8P
+kFiP4323uIA0Q83+lYo9H1bs6dNibSUkvnTay79ezbnwcnTsg/KA6z3oVaz5a2UI
+YZGDLyxwIpvsIpE9jrNHrNkfmr4scQGb8eRGIB22vU+0f6Qv5QOASN5RG1SowwPN
+4Wb4zx9WHSvkWEEtoCcoHYXvKjkrMFyMBQBdzDAacEQ95s/rwCWT95aQRrJ/mrDu
+l3f+XIwvJrIKJkvCYnPQzDDZaxNVlO98FRvZ9DiXJHtlxLcwEVSiS5QT7LoOKMQ3
+r+juBmpJAgMBAAECggEACjs5suCbmYAb2d2VZlRitdP+Q6HX37D0YTBrBI7o6JdK
+U7oqrwBy/JBGHpDqewBgmTs3FGr/H/zJpDTRickXs9X6qhrreQ2wA6b/5gHoPMt0
+nHKfbqyOCuq/YGmxnBXMnDNdoynTfGAE0af5vRIBZiYu6vG4B9fHzURR5O/qVDNn
+WqJ+2Y8AAf4mJDCBEvJz2RaZwSq788i/d8oTSeCk93TDF+GHhUq6ymkORACj76ws
+8ohfoNQIG1VhdcTK2GOQjqctEFUm54t9N1nxD6VavMu5DlSSVsPTDbYuE5U4cy3T
+ThDtoYJgwz5KRflklwl3xoDJVx3B5wMUaqviRp6l0QKBgQC6MPG+EV2drsHwG0Wg
+gnP4uCSuFAfWBlHAQyZv5PMQBNfM8YjyMyL+O7cggGNJSOZr/X30EqoBe+LXrL3X
+Gtix8F1Ed0fbAarAgxIwq8MktzmastDq4XS+zwYPZ7UTbmbqvT3VYPga4Sh90fyY
+nPJpqZvhvGzQX22yeHS7vTSQOQKBgQD9z36EIYMuLl0HJK6gfjGHsy/Rx7bw1TmP
+aHmuF8Ra7rpDSOym0ImKWTOLEoLlQUsMz/FuVLCGP/ACjMFKsqh3Zy/0hVJOMDMR
+Z+ODT28Hcz4AMcTYDvcTYd70HhhZL+/eFCVk8Nk164saMuhifAkOgvwfaYs0m3ue
+S9jxlZKKkQKBgHgBzf6k8MMOfaAF4/XVv2wDPFkbPgW74vtaDK84UVX02ScWUx9Q
+yHA3Cwye09/LZgEazREA6qS0NfyvMVkwy5S9CVB01VKam3UjxhiqzMegdTd5o+CQ
+WpAVnaFWRcb1dM4+FVmv+5pPn6qhKv8uwaxLDtcLfNM9ftX2f77176g5AoGADWtQ
+DBpdfi6TWpJU7UVexwbxS00c3gTYAz4J2OuGxSwECxSq9nLmIrtunza+VvKpziac
+ZDH0F1UAEpJwkct6Xr3E6k+2N04TFSOCAupLO4CbUZVQDABWjd7J0+xXaze+neZA
+x+J4CYLHmv4ADVzzeaHxRJPm+UQTOB5YfQVkdxECgYBv3QuUMiBGKWgeheP4nAFU
+SVgqGBQwAtqb5DR1YVJ4LFPt+jyrQMby6mqSlzENYcidSP3Ogn22CvST+bAjbf6D
+D/ae1zeOHBls00ILHANv1Z/hXcEkiKnZdeP6O43xBfCS+Lps5daXgUbC0kw2R09S
+VZTqPHmb+db0rFA3XlAg2A==
+-----END PRIVATE KEY-----
+)");
   start();
  }
 
@@ -115,6 +166,8 @@ public:
  {
   stop();
   fs::remove(testConfigFilename);
+  fs::remove(testCertFilename);
+  fs::remove(testKeyFilename);
  }
 
  void start()
@@ -259,7 +312,7 @@ std::pair<std::string,std::string> HTTP(const std::string& target, bool ipv6 = t
 
 void load_root_certificates(boost::asio::ssl::context& ctx)
 {
- std::string cert_chain{File::getFile("../cert.pem")};
+ std::string cert_chain{File::getFile(testCertFilename)};
  ctx.add_certificate_authority(boost::asio::buffer(cert_chain.data(), cert_chain.size()));
 }
 
@@ -357,27 +410,26 @@ public:
 
 BOOST_DATA_TEST_CASE_F(Fixture, http_get, data::make({false, true}) * data::make({false, true}) * data::make({false, true}) * data::make({boost::beast::http::verb::head, boost::beast::http::verb::get}), ipv6, http11, https, method)
 {
- std::cout << "DEBUG: " << ipv6 << " " << http11 << " " << https << " " << method << std::endl;
  WebserverProcess serverProcess;
 
  BOOST_REQUIRE(serverProcess.isRunning());
  std::pair<std::string,std::string> response{https ? HTTPS("/webserver.conf", ipv6, http11, method) : HTTP("/webserver.conf", ipv6, http11, method)};
  BOOST_REQUIRE(serverProcess.isRunning());
- BOOST_REQUIRE_EQUAL(response.first, fmt::format("HTTP/{} 200 OK\r\nServer: Reichwein.IT Webserver " VERSION "\r\nContent-Type: application/text\r\nContent-Length: {}\r\n\r\n", http11 ? "1.1" : "1.0", method == boost::beast::http::verb::head ? 0 : 1021));
- BOOST_REQUIRE_EQUAL(response.second, method == boost::beast::http::verb::head ? ""s : File::getFile(serverProcess.testConfigFilename));
-#if 0
+ std::string::size_type size{File::getFile(testConfigFilename).size()};
+ BOOST_CHECK_GT(size, 0);
+ BOOST_REQUIRE_EQUAL(response.first, fmt::format("HTTP/{} 200 OK\r\nServer: Reichwein.IT Webserver " VERSION "\r\nContent-Type: application/text\r\nContent-Length: {}\r\n\r\n", http11 ? "1.1" : "1.0", method == boost::beast::http::verb::head ? 0 : size));
+ BOOST_REQUIRE_EQUAL(response.second, method == boost::beast::http::verb::head ? ""s : File::getFile(testConfigFilename));
+
  for (int i = 0; i < 10; i++) {
   std::pair<std::string,std::string> response{https ? HTTPS("/webserver.conf", ipv6, http11, method) : HTTP("/webserver.conf", ipv6, http11, method)};
   BOOST_REQUIRE(serverProcess.isRunning());
-  BOOST_REQUIRE_EQUAL(response.first, fmt::format("HTTP/{} 200 OK\r\nServer: Reichwein.IT Webserver " VERSION "\r\nContent-Type: application/text\r\nContent-Length: {}\r\n\r\n", http11 ? "1.1" : "1.0", method == boost::beast::http::verb::head ? 0 : 1021));
-  BOOST_REQUIRE_EQUAL(response.second, method == boost::beast::http::verb::head ? ""s : File::getFile(serverProcess.testConfigFilename));
+  BOOST_REQUIRE_EQUAL(response.first, fmt::format("HTTP/{} 200 OK\r\nServer: Reichwein.IT Webserver " VERSION "\r\nContent-Type: application/text\r\nContent-Length: {}\r\n\r\n", http11 ? "1.1" : "1.0", method == boost::beast::http::verb::head ? 0 : size));
+  BOOST_REQUIRE_EQUAL(response.second, method == boost::beast::http::verb::head ? ""s : File::getFile(testConfigFilename));
  }
-#endif
 }
 
 BOOST_DATA_TEST_CASE_F(Fixture, http_get_file_not_found, data::make({false, true}) * data::make({false, true}) * data::make({false, true}) * data::make({boost::beast::http::verb::head, boost::beast::http::verb::get}), ipv6, http11, https, method)
 {
- std::cout << "DEBUGe: " << ipv6 << " " << http11 << " " << https << " " << method << std::endl;
  WebserverProcess serverProcess;
 
  BOOST_REQUIRE(serverProcess.isRunning());
-- 
cgit v1.2.3