~singpolyma/biboumi

7b3e0e0cf3eddd3537455a3605b04a48ee663f47 — louiz’ 6 years ago 1472adf
Make botan’s policy configurable from a file

fix #3244
M CHANGELOG.rst => CHANGELOG.rst +2 -0
@@ 15,6 15,8 @@ Version 5.0
   messages, instead of the whole archive.
 - Multiline topics are now properly handled
 - Configuration options can be overridden by values found in the process env.
 - Botan’s TLS policies can be customized by the administrator, for each
   IRC server, with simple text files.

Version 4.1 - 2017-03-21
========================

M CMakeLists.txt => CMakeLists.txt +2 -0
@@ 336,6 336,8 @@ install(TARGETS ${PROJECT_NAME} RUNTIME                     DESTINATION bin)
install(FILES   ${MAN_PAGE}                                 DESTINATION share/man/man1         OPTIONAL COMPONENT documentation)
install(FILES   ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION lib/systemd/system     COMPONENT init)
install(FILES   conf/biboumi.cfg                            DESTINATION /etc/biboumi           COMPONENT configuration)
file(GLOB policy_files conf/*policy.txt)
install(FILES   ${policy_files}                             DESTINATION /etc/biboumi           COMPONENT configuration)

#
## Dist target

A conf/irc.freenode.net.policy.txt => conf/irc.freenode.net.policy.txt +1 -0
@@ 0,0 1,1 @@
require_cert_revocation_info = false

A conf/irc.mozilla.org.policy.txt => conf/irc.mozilla.org.policy.txt +1 -0
@@ 0,0 1,1 @@
minimum_dh_group_size = 1024

A conf/policy.txt => conf/policy.txt +2 -0
@@ 0,0 1,2 @@
require_cert_revocation_info = false
use_ecc_point_compression = true

M doc/biboumi.1.rst => doc/biboumi.1.rst +35 -0
@@ 163,6 163,40 @@ identd_port

The TCP port on which to listen for identd queries.  The default is the standard value: 113.

policy_directory
----------------

A directory that should contain the policy files, used to customize
Botan’s behaviour when negociating the TLS connections with the IRC
servers. If not specified, the directory is the one where biboumi’s
configuration file is located: for example if biboumi reads its
configuration from /etc/biboumi/biboumi.cfg, the policy_directory value
will be /etc/biboumi.


TLS configuration
=================

Various settings of the TLS connections can be customized using policy
files. The files should be located in the directory specified by the
configuration option `policy_directory`_.  When attempting to connect to
an IRC server using TLS, biboumi will use Botan’s default TLS policy, and
then will try to load some policy files to override the values found in
these files.  For example, if policy_directory is /etc/biboumi, when
trying to connect to irc.example.com, biboumi will try to read
/etc/biboumi/policy.txt, use the values found to override the default
values, then it will try to read /etc/biboumi/irc.example.com.policy.txt
and re-override the policy with the values found in this file.

The policy.txt file applies to all the connections, and
irc.example.policy.txt will only apply (in addition to policy.txt) when
connecting to that specific server.

To see the list of possible options to configure, refer to `Botan’s TLS
documentation <https://botan.randombit.net/manual/tls.html#tls-policies>`_.

By default, biboumi provides a few policy files, to work around some
issues found with a few well-known IRC servers.

Usage
=====


@@ 628,3 662,4 @@ protection against flood or any sort of abuse that your users may cause on
the IRC servers. Some XMPP server however offer the possibility to restrict
what JID can access a gateway. Use that feature if you wish to grant access
to your biboumi instance only to a list of trusted users.


M src/network/tcp_socket_handler.cpp => src/network/tcp_socket_handler.cpp +8 -6
@@ 14,6 14,8 @@
#ifdef BOTAN_FOUND
# include <botan/hex.h>
# include <botan/tls_exceptn.h>
# include <config/config.hpp>
# include <utils/dirname.hpp>

namespace
{


@@ 22,11 24,6 @@ namespace
      static Botan::AutoSeeded_RNG rng{};
      return rng;
    }
    BiboumiTLSPolicy& get_policy()
    {
      static BiboumiTLSPolicy policy{};
      return policy;
    }
    Botan::TLS::Session_Manager_In_Memory& get_session_manager()
    {
      static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()};


@@ 233,6 230,11 @@ void TCPSocketHandler::consume_in_buffer(const std::size_t size)
void TCPSocketHandler::start_tls(const std::string& address, const std::string& port)
{
  Botan::TLS::Server_Information server_info(address, "irc", std::stoul(port));
  auto policy_directory = Config::get("policy_directory", utils::dirname(Config::get_filename()));
  if (!policy_directory.empty() && policy_directory[policy_directory.size()-1] != '/')
    policy_directory += '/';
  this->policy.load(policy_directory + "policy.txt");
  this->policy.load(policy_directory + address + ".policy.txt");
  this->tls = std::make_unique<Botan::TLS::Client>(
# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32)
      *this,


@@ 242,7 244,7 @@ void TCPSocketHandler::start_tls(const std::string& address, const std::string& 
      [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); },
      [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); },
# endif
      get_session_manager(), this->credential_manager, get_policy(),
      get_session_manager(), this->credential_manager, this->policy,
      get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version());
}


M src/network/tcp_socket_handler.hpp => src/network/tcp_socket_handler.hpp +2 -15
@@ 23,21 23,7 @@
# include <botan/types.h>
# include <botan/botan.h>
# include <botan/tls_session_manager.h>

class BiboumiTLSPolicy: public Botan::TLS::Policy
{
public:
# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,33)
  bool use_ecc_point_compression() const override
  {
    return true;
  }
  bool require_cert_revocation_info() const override
  {
    return false;
  }
# endif
};
# include <network/tls_policy.hpp>

# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32)
#  define BOTAN_TLS_CALLBACKS_OVERRIDE override final


@@ 230,6 216,7 @@ protected:
protected:
  BasicCredentialsManager credential_manager;
private:
  BiboumiTLSPolicy policy;
  /**
   * We use a unique_ptr because we may not want to create the object at
   * all. The Botan::TLS::Client object generates a handshake message and

A src/network/tls_policy.cpp => src/network/tls_policy.cpp +48 -0
@@ 0,0 1,48 @@
#include "biboumi.h"

#ifdef BOTAN_FOUND

#include <fstream>

#include <utils/tolower.hpp>

#include <network/tls_policy.hpp>
#include <logger/logger.hpp>

bool BiboumiTLSPolicy::load(const std::string& filename)
{
  std::ifstream is(filename.data());
  if (is)
    {
      try {
          this->load(is);
          log_info("Successfully loaded policy file: ", filename);
          return true;
        } catch (const Botan::Exception& e) {
          log_error("Failed to parse policy_file ", filename, ": ", e.what());
          return false;
        }
    }
  log_info("Could not open policy file: ", filename);
  return false;
}

void BiboumiTLSPolicy::load(std::istream& is)
{
  const auto dict = Botan::read_cfg(is);
  for (const auto& pair: dict)
    {
      // Workaround for options that are not overridden in Botan::TLS::Text_Policy
      if (pair.first == "require_cert_revocation_info")
        this->req_cert_revocation_info = !(pair.second == "0" || utils::tolower(pair.second) == "false");
      else
        this->set(pair.first, pair.second);
    }
}

bool BiboumiTLSPolicy::require_cert_revocation_info() const
{
  return this->req_cert_revocation_info;
}

#endif

A src/network/tls_policy.hpp => src/network/tls_policy.hpp +28 -0
@@ 0,0 1,28 @@
#pragma once

#include "biboumi.h"

#ifdef BOTAN_FOUND

#include <botan/tls_policy.h>

class BiboumiTLSPolicy: public Botan::TLS::Text_Policy
{
public:
  BiboumiTLSPolicy():
      Botan::TLS::Text_Policy({})
  {}
  bool load(const std::string& filename);
  void load(std::istream& iss);

  BiboumiTLSPolicy(const BiboumiTLSPolicy &) = delete;
  BiboumiTLSPolicy(BiboumiTLSPolicy &&) = delete;
  BiboumiTLSPolicy &operator=(const BiboumiTLSPolicy &) = delete;
  BiboumiTLSPolicy &operator=(BiboumiTLSPolicy &&) = delete;

  bool require_cert_revocation_info() const override;
protected:
  bool req_cert_revocation_info{true};
};

#endif

A src/utils/dirname.cpp => src/utils/dirname.cpp +16 -0
@@ 0,0 1,16 @@
#include <utils/dirname.hpp>

namespace utils
{
    std::string dirname(const std::string filename)
    {
      if (filename.empty())
        return "./";
      if (filename == ".." || filename == ".")
        return filename;
      auto pos = filename.rfind('/');
      if (pos == std::string::npos)
        return "./";
      return filename.substr(0, pos + 1);
    }
}

A src/utils/dirname.hpp => src/utils/dirname.hpp +6 -0
@@ 0,0 1,6 @@
#include <string>

namespace utils
{
std::string dirname(const std::string filename);
}

M src/utils/xdg.hpp => src/utils/xdg.hpp +0 -2
@@ 10,5 10,3 @@
 */
std::string xdg_config_path(const std::string& filename);
std::string xdg_data_path(const std::string& filename);



A tests/network.cpp => tests/network.cpp +42 -0
@@ 0,0 1,42 @@
#include "catch.hpp"
#include <network/tls_policy.hpp>

TEST_CASE("tls_policy")
{
  BiboumiTLSPolicy policy;
  const auto default_minimum_signature_strength = policy.minimum_signature_strength();
  const auto default_session_ticket_lifetime = policy.session_ticket_lifetime();
  const auto default_minimum_rsa_bits = policy.minimum_rsa_bits();

  policy.load("does not exist");
  WHEN("we fail to load the file")
    {
      THEN("all values are the default ones")
        {
          CHECK(policy.minimum_signature_strength() == default_minimum_signature_strength);
          CHECK(policy.minimum_rsa_bits() == default_minimum_rsa_bits);
        }
      AND_WHEN("we load a valid first file")
        {
          std::istringstream iss("minimum_signature_strength  =      128\nminimum_rsa_bits=12\n");
          policy.load(iss);
          THEN("the specified values are updated, and the rest is still the default")
            {
              CHECK(policy.minimum_signature_strength() == 128);
              CHECK(policy.minimum_rsa_bits() == 12);
              CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime);
            }
          AND_WHEN("we load a second file")
            {
              std::istringstream iss("minimum_signature_strength  =      15");
              policy.load(iss);
              THEN("the specified values are updated, and the rest is untouched")
                {
                  CHECK(policy.minimum_signature_strength() == 15);
                  CHECK(policy.minimum_rsa_bits() == 12);
                  CHECK(policy.session_ticket_lifetime() == default_session_ticket_lifetime);
                }
            }
        }
    }
}

M tests/utils.cpp => tests/utils.cpp +14 -1
@@ 10,6 10,7 @@
#include <utils/time.hpp>
#include <utils/system.hpp>
#include <utils/scopeguard.hpp>
#include <utils/dirname.hpp>

using namespace std::string_literals;



@@ 157,4 158,16 @@ TEST_CASE("system_name")
{
  CHECK(utils::get_system_name() != "Unknown");
  CHECK(!utils::get_system_name().empty());
}
\ No newline at end of file
}

TEST_CASE("dirname")
{
  CHECK(utils::dirname("/") == "/");
  CHECK(utils::dirname("coucou.txt") == "./");
  CHECK(utils::dirname("../coucou.txt") == "../");
  CHECK(utils::dirname("/etc/biboumi/coucou.txt") == "/etc/biboumi/");
  CHECK(utils::dirname("..") == "..");
  CHECK(utils::dirname("../") == "../");
  CHECK(utils::dirname(".") == ".");
  CHECK(utils::dirname("./") == "./");
}