Commit 8fa1b449 authored by Dominik Charousset's avatar Dominik Charousset

Improve SSL wrapper API and and add pw handling

parent b713a845
......@@ -67,9 +67,11 @@ caf_add_component(
src/net/ssl/context.cpp
src/net/ssl/dtls.cpp
src/net/ssl/format.cpp
src/net/ssl/password.cpp
src/net/ssl/startup.cpp
src/net/ssl/tls.cpp
src/net/ssl/transport.cpp
src/net/ssl/verify.cpp
src/net/stream_oriented.cpp
src/net/stream_socket.cpp
src/net/stream_transport.cpp
......
......@@ -69,7 +69,7 @@ public:
/// from a client.
[[nodiscard]] ptrdiff_t accept();
/// Gracefully closes the SSL connection.
/// Gracefully closes the SSL connection without closing the socket.
ptrdiff_t close();
// -- reading and writing ----------------------------------------------------
......
......@@ -8,9 +8,12 @@
#include "caf/net/ssl/dtls.hpp"
#include "caf/net/ssl/format.hpp"
#include "caf/net/ssl/fwd.hpp"
#include "caf/net/ssl/password.hpp"
#include "caf/net/ssl/tls.hpp"
#include "caf/net/ssl/verify.hpp"
#include "caf/net/stream_socket.hpp"
#include <cstring>
#include <string>
namespace caf::net::ssl {
......@@ -27,6 +30,9 @@ public:
/// The opaque implementation type.
struct impl;
/// Stores additional data provided by the user.
struct user_data;
// -- constructors, destructors, and assignment operators --------------------
context() = delete;
......@@ -65,6 +71,30 @@ public:
return pimpl_ == nullptr;
}
/// Overrides the verification mode for this context.
/// @note calls @c SSL_CTX_set_verify
void set_verify_mode(verify_t flags);
/// Overrides the callback to obtain the password for encrypted PEM files.
/// @note calls @c SSL_CTX_set_default_passwd_cb
template <typename PasswordCallback>
void set_password_callback(PasswordCallback callback) {
set_password_callback_impl(password::make_callback(std::move(callback)));
}
/// Overrides the callback to obtain the password for encrypted PEM files with
/// a function that always returns @p password.
/// @note calls @c SSL_CTX_set_default_passwd_cb
void set_password(std::string password) {
auto cb = [pw = std::move(password)](char* buf, int len,
password::purpose) {
strncpy(buf, pw.c_str(), static_cast<size_t>(len));
buf[len - 1] = '\0';
return static_cast<int>(pw.size());
};
set_password_callback(std::move(cb));
}
// -- native handles ---------------------------------------------------------
/// Reinterprets `native_handle` as the native implementation type and takes
......@@ -111,29 +141,57 @@ public:
/// e.g., `9d66eef0.0` and `9d66eef0.1`.
/// @returns `true` on success, `false` otherwise and `last_error` can be used
/// to retrieve a human-readable error representation.
[[nodiscard]] bool load_verify_dir(const char* path);
/// @note Calls @c SSL_CTX_load_verify_locations
[[nodiscard]] bool add_verify_path(const char* path);
/// @copydoc add_verify_path
[[nodiscard]] bool add_verify_path(const std::string& path) {
return add_verify_path(path.c_str());
}
/// Loads a CA certificate file.
/// @param path Null-terminated string with a path to a single PEM file.
/// @returns `true` on success, `false` otherwise and `last_error` can be used
/// to retrieve a human-readable error representation.
/// @note Calls @c SSL_CTX_load_verify_locations
[[nodiscard]] bool load_verify_file(const char* path);
/// @copydoc load_verify_file
[[nodiscard]] bool load_verify_file(const std::string& path) {
return load_verify_file(path.c_str());
}
/// Loads the first certificate found in given file.
/// @param path Null-terminated string with a path to a single PEM file.
[[nodiscard]] bool use_certificate_from_file(const char* path,
format file_format);
[[nodiscard]] bool use_certificate_file(const char* path, format file_format);
/// Loads a certificate chain from a PEM-formatted file.
/// @note calls @c SSL_CTX_use_certificate_chain_file
[[nodiscard]] bool use_certificate_chain_file(const char* path);
/// @copydoc use_certificate_chain_file
[[nodiscard]] bool use_certificate_chain_file(const std::string& path) {
return use_certificate_chain_file(path.c_str());
}
/// Loads the first private key found in given file.
[[nodiscard]] bool use_private_key_from_file(const char* path,
format file_format);
[[nodiscard]] bool use_private_key_file(const char* path, format file_format);
/// @copydoc use_private_key_file
[[nodiscard]] bool use_private_key_file(const std::string& path,
format file_format) {
return use_private_key_file(path.c_str(), file_format);
}
private:
constexpr explicit context(impl* ptr) : pimpl_(ptr) {
// nop
}
void set_password_callback_impl(password::callback_ptr callback);
impl* pimpl_;
user_data* data_ = nullptr;
};
} // namespace caf::net::ssl
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include <memory>
namespace caf::net::ssl::password {
/// Purpose of PEM password.
enum class purpose {
/// SSL asks the password is reading/decryption.
reading,
/// SSL asks the password is for writing/encryption.
writing
};
class CAF_NET_EXPORT callback {
public:
virtual ~callback();
/// Reads the password into the buffer @p buf of size @p len.
/// @returns Written characters on success, -1 otherwise.
virtual int operator()(char* buf, int len, purpose flag) = 0;
};
using callback_ptr = std::unique_ptr<callback>;
template <class PasswordCallback>
callback_ptr make_callback(PasswordCallback callback) {
struct impl : callback {
explicit impl(PasswordCallback&& cb) : cb_(std::move(cb)) {
// nop
}
int operator()(char* buf, int len, purpose flag) override {
return cb_(buf, len, flag);
}
PasswordCallback cb_;
};
return std::make_unique<impl>(std::move(callback));
}
} // namespace caf::net::ssl::password
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include <string>
namespace caf::net::ssl {
/// Bitmask type for the SSL verification mode.
enum class verify_t {};
namespace verify {
CAF_NET_EXPORT extern const verify_t none;
CAF_NET_EXPORT extern const verify_t peer;
CAF_NET_EXPORT extern const verify_t fail_if_no_peer_cert;
CAF_NET_EXPORT extern const verify_t client_once;
} // namespace verify
constexpr int to_integer(verify_t x) {
return static_cast<int>(x);
}
inline verify_t& operator|=(verify_t& x, verify_t y) noexcept {
return x = static_cast<verify_t>(to_integer(x) | to_integer(y));
}
constexpr verify_t operator|(verify_t x, verify_t y) noexcept {
return static_cast<verify_t>(to_integer(x) | to_integer(y));
}
} // namespace caf::net::ssl
......@@ -33,11 +33,11 @@ acceptor::make_with_cert_file(tcp_accept_socket fd, const char* cert_file_path,
if (!ctx) {
return {make_error(sec::runtime_error, "unable to create SSL context")};
}
if (!ctx->use_certificate_from_file(cert_file_path, file_format)) {
if (!ctx->use_certificate_file(cert_file_path, file_format)) {
return {make_error(sec::runtime_error, "unable to load certificate file",
ctx->last_error_string())};
}
if (!ctx->use_private_key_from_file(key_file_path, file_format)) {
if (!ctx->use_private_key_file(key_file_path, file_format)) {
return {make_error(sec::runtime_error, "unable to load private key file",
ctx->last_error_string())};
}
......
......@@ -23,22 +23,34 @@ auto native(context::impl* ptr) {
} // namespace
// -- member types -------------------------------------------------------------
struct context::user_data {
password::callback_ptr pw_callback;
};
// -- constructors, destructors, and assignment operators ----------------------
context::context(context&& other) {
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
data_ = other.data_;
other.data_ = nullptr;
}
context& context::operator=(context&& other) {
SSL_CTX_free(native(pimpl_));
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
delete data_;
data_ = other.data_;
other.data_ = nullptr;
return *this;
}
context::~context() {
SSL_CTX_free(native(pimpl_)); // Already checks for NULL.
delete data_;
}
// -- factories ----------------------------------------------------------------
......@@ -147,6 +159,34 @@ expected<context> context::make_client(dtls vmin, dtls vmax) {
return {make_error(sec::logic_error, errstr)};
}
// -- properties ---------------------------------------------------------------
void context::set_verify_mode(verify_t flags) {
auto ptr = native(pimpl_);
SSL_CTX_set_verify(ptr, to_integer(flags), SSL_CTX_get_verify_callback(ptr));
}
namespace {
int c_password_callback(char* buf, int size, int rwflag, void* ptr) {
if (ptr == nullptr)
return -1;
auto flag = static_cast<password::purpose>(rwflag);
auto fn = static_cast<password::callback*>(ptr);
return (*fn)(buf, size, flag);
}
} // namespace
void context::set_password_callback_impl(password::callback_ptr callback) {
if (data_ == nullptr)
data_ = new user_data;
auto ptr = native(pimpl_);
data_->pw_callback = std::move(callback);
SSL_CTX_set_default_passwd_cb(ptr, c_password_callback);
SSL_CTX_set_default_passwd_cb_userdata(ptr, data_->pw_callback.get());
}
// -- native handles -----------------------------------------------------------
context context::from_native(void* native_handle) {
......@@ -217,7 +257,7 @@ bool context::set_default_verify_paths() {
return SSL_CTX_set_default_verify_paths(native(pimpl_)) == 1;
}
bool context::load_verify_dir(const char* path) {
bool context::add_verify_path(const char* path) {
ERR_clear_error();
return SSL_CTX_load_verify_locations(native(pimpl_), nullptr, path) == 1;
}
......@@ -227,16 +267,21 @@ bool context::load_verify_file(const char* path) {
return SSL_CTX_load_verify_locations(native(pimpl_), path, nullptr) == 1;
}
bool context::use_certificate_from_file(const char* path, format file_format) {
bool context::use_certificate_file(const char* path, format file_format) {
ERR_clear_error();
auto nff = native(file_format);
return SSL_CTX_use_certificate_file(native(pimpl_), path, nff) == 1;
}
bool context::use_certificate_chain_file(const char* path) {
ERR_clear_error();
return SSL_CTX_use_certificate_file(native(pimpl_), path, native(file_format))
== 1;
return SSL_CTX_use_certificate_chain_file(native(pimpl_), path) == 1;
}
bool context::use_private_key_from_file(const char* path, format file_format) {
bool context::use_private_key_file(const char* path, format file_format) {
ERR_clear_error();
return SSL_CTX_use_PrivateKey_file(native(pimpl_), path, native(file_format))
== 1;
auto nff = native(file_format);
return SSL_CTX_use_PrivateKey_file(native(pimpl_), path, nff) == 1;
}
} // namespace caf::net::ssl
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/ssl/password.hpp"
namespace caf::net::ssl::password {
callback::~callback() {
// nop
}
} // namespace caf::net::ssl::password
......@@ -62,6 +62,8 @@ void startup() {
CRYPTO_set_dynlock_create_callback(dynlock_create);
CRYPTO_set_dynlock_lock_callback(dynlock_lock);
CRYPTO_set_dynlock_destroy_callback(dynlock_destroy);
#else
OPENSSL_init_ssl(0, nullptr);
#endif
}
......@@ -72,6 +74,10 @@ void cleanup() {
CRYPTO_set_dynlock_lock_callback(nullptr);
CRYPTO_set_dynlock_destroy_callback(nullptr);
mutexes.clear();
#else
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
#endif
}
......
#include "caf/net/ssl/verify.hpp"
#include <openssl/ssl.h>
namespace caf::net::ssl::verify {
namespace {
constexpr verify_t i2v(int x) {
return static_cast<verify_t>(x);
}
} // namespace
const verify_t none = i2v(SSL_VERIFY_NONE);
const verify_t peer = i2v(SSL_VERIFY_PEER);
const verify_t fail_if_no_peer_cert = i2v(SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
const verify_t client_once = i2v(SSL_VERIFY_CLIENT_ONCE);
} // namespace caf::net::ssl::verify
......@@ -112,12 +112,12 @@ void dummy_tls_server(stream_socket fd, const char* cert_file,
auto guard = detail::make_scope_guard([fd] { close(fd); });
// Get and configure our SSL context.
auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
if (!ctx.use_certificate_from_file(cert_file, ssl::format::pem)) {
if (!ctx.use_certificate_file(cert_file, ssl::format::pem)) {
std::cerr << "*** failed to load certificate_file: "
<< ctx.last_error_string() << '\n';
return;
}
if (!ctx.use_private_key_from_file(key_file, ssl::format::pem)) {
if (!ctx.use_private_key_file(key_file, ssl::format::pem)) {
std::cerr << "*** failed to load private key file: "
<< ctx.last_error_string() << '\n';
return;
......@@ -231,10 +231,10 @@ SCENARIO("ssl::transport::make_server performs the server handshake") {
mpx->set_thread_id();
std::ignore = mpx->init();
auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
REQUIRE(ctx.use_certificate_from_file(cert_1_pem_path, //
ssl::format::pem));
REQUIRE(ctx.use_private_key_from_file(key_1_pem_path, //
ssl::format::pem));
REQUIRE(ctx.use_certificate_file(cert_1_pem_path, //
ssl::format::pem));
REQUIRE(ctx.use_private_key_file(key_1_pem_path, //
ssl::format::pem));
auto conn = unbox(ctx.new_connection(server_fd));
auto done = std::make_shared<bool>(false);
auto buf = std::make_shared<byte_buffer>();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment