SSL

Protocols such as HTTP and WebSocket have a secure version that requires an SSL/TLS layer. CAF bundles functions and classes for implementing secure communication in the namespace caf::net::ssl. It is worth mentioning that classes and functions in this namespace only provide convenient access to SSL capabilities by wrapping an actual SSL implementation (usually OpenSSL).

Context

CAF users usually only need to care about the class context. This is the central state of an SSL-enabled server or client. On a server, all incoming connection will use the configuration from this context.

To create a new context, we can use one of these factory member functions:

  • enable(bool flag)
  • make(tls min_version, tls max_version = tls::any)
  • make_server(tls min_version, tls max_version = tls::any)
  • make_client(tls min_version, tls max_version = tls::any)
  • make(dtls min_version, dtls max_version = dtls::any)
  • make_server(dtls min_version, dtls max_version = dtls::any)
  • make_client(dtls min_version, dtls max_version = dtls::any)

All make* functions return an caf::expected<caf::net::ssl::context>. However, the factory function enable returns caf::expected<void> instead. This function is meant as an entry point for creating a context through a series of chained and_then invocations on the expected. Here is an example to illustrate how it works in practice:

namespace ws = caf::net::web_socket;
auto pem = ssl::format::pem;
auto key_file = caf::get_as<std::string>(cfg, "tls.key-file");
auto cert_file = caf::get_as<std::string>(cfg, "tls.cert-file");
if (!key_file != !cert_file) {
  std::cerr << "*** inconsistent TLS config: declare neither file or both\n";
  return EXIT_FAILURE;
}
auto server
  = ws::with(sys)
      .context(ssl::context::enable(key_file && cert_file)
                 .and_then(ssl::emplace_server(ssl::tls::v1_2))
                 .and_then(ssl::use_private_key_file(key_file, pem))
                 .and_then(ssl::use_certificate_file(cert_file, pem)))
      // ...

Passing false to ssl::context::enable returns an expected with a default-constructed caf::error. Since the expected contains an error, all subsequent and_then calls turn into no-ops. However, a default-constructed error means “no error”. Hence, CAF continues without an SSL context in the example and the server will use plain (unencrypted) WebSocket communication.

Passing true to ssl::context::enable returns an “empty” expected<void>. The next call to and_then will call the function object (with no arguments) and return a new expected. In the example, we use emplace_server to create a function object that will call context::make_server(min, max) when called without arguments. Hence, we convert an expected<void> to an expected<ssl::context> with this step. The functions ssl::use_private_key_file and ssl::use_certificate_file return functions objects (lambdas) that take an ssl::context as argument and return expected<ssl::context> again. In this way, we can chain functions that modify a context with and_then to describe the setup for building up an SSL context in a declarative way. If all of the steps for building the SSL context succeed, the server will use WebSocket over SSL to encrypt communication to clients. If any of the steps produces an actual error (for example if the key file does not exist), CAF will produce an error and not start the server at all.

For the full class interface, please refer to the Doxygen documentation.