Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
695f8e84f7
commit
0d22946a0e
1835 changed files with 170291 additions and 2849 deletions
218
include/dpf/net/client_link.hpp
Normal file
218
include/dpf/net/client_link.hpp
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/// @file dpf/net/client_link.hpp
|
||||
/// @brief Client-to-server links: TLS 1.3, the server always verified.
|
||||
/// @details A client that supplies inputs (for example, one share to each
|
||||
/// party) connects with `connect_server`. It verifies the server
|
||||
/// unless `client_security::verify` is off: a pinned server key, a CA
|
||||
/// chain for the host name, or, when neither is configured, the
|
||||
/// built-in development certificate, which a `client_listener` with no
|
||||
/// certificate or identity presents. The development certificate's
|
||||
/// private key is public, so that pairing works out of the box and is
|
||||
/// logged as providing no security. A server may also check client
|
||||
/// keys (`server_security::client_pins`). Both ends get an ordinary
|
||||
/// `async_stream_array` of `lanes` lanes and the link's
|
||||
/// `link_security`.
|
||||
#ifndef LIBDPF_INCLUDE_DPF_NET_CLIENT_LINK_HPP__
|
||||
#define LIBDPF_INCLUDE_DPF_NET_CLIENT_LINK_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "dpf/net/asio_ns.hpp"
|
||||
|
||||
#include "dpf/log.hpp"
|
||||
#include "dpf/net/async_stream_array.hpp"
|
||||
#include "dpf/net/connect.hpp"
|
||||
#include "dpf/net/link_log.hpp"
|
||||
#include "dpf/net/policy.hpp"
|
||||
#include "dpf/net/security.hpp"
|
||||
#include "dpf/net/socket_tune.hpp"
|
||||
#include "dpf/net/tls.hpp"
|
||||
|
||||
namespace dpf
|
||||
{
|
||||
namespace net
|
||||
{
|
||||
|
||||
/// @brief One client link and how it was secured.
|
||||
struct client_connection
|
||||
{
|
||||
#if DPF_HAS_OPENSSL
|
||||
std::shared_ptr<tls_context> context;
|
||||
#endif
|
||||
std::unique_ptr<async_stream_array> link;
|
||||
link_security security;
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
inline constexpr std::uint32_t client_magic = 0x4c435044u; // 'DPCL'
|
||||
|
||||
#if DPF_HAS_OPENSSL
|
||||
/// @brief Both ends send `{magic, lanes}` inside TLS and must agree.
|
||||
inline void client_hello(asio::io_context & io, tls_stream & s, std::size_t lanes,
|
||||
bool server, std::chrono::milliseconds budget, const std::string & who)
|
||||
{
|
||||
std::uint8_t mine[8];
|
||||
std::uint8_t theirs[8];
|
||||
put_u32(mine, client_magic);
|
||||
put_u32(mine + 4, static_cast<std::uint32_t>(lanes));
|
||||
if (server)
|
||||
{
|
||||
tls_read(io, s, theirs, sizeof(theirs), budget, who);
|
||||
tls_write(io, s, mine, sizeof(mine), budget, who);
|
||||
}
|
||||
else
|
||||
{
|
||||
tls_write(io, s, mine, sizeof(mine), budget, who);
|
||||
tls_read(io, s, theirs, sizeof(theirs), budget, who);
|
||||
}
|
||||
if (get_u32(theirs) != client_magic)
|
||||
throw std::runtime_error(who + ": the peer is not a libdpf client link");
|
||||
if (get_u32(theirs + 4) != lanes)
|
||||
throw std::runtime_error(who + ": lanes " + std::to_string(get_u32(theirs + 4))
|
||||
+ " vs " + std::to_string(lanes) + " (peer vs this side)");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief Connect to the server at `host:port` and verify it.
|
||||
inline client_connection connect_server(asio::io_context & io, const std::string & host,
|
||||
unsigned short port, const client_security & sec, std::size_t lanes = 1,
|
||||
const wire_policy & pol = {}, const deadlines & lim = {})
|
||||
{
|
||||
#if DPF_HAS_OPENSSL
|
||||
const std::string where = host + ":" + std::to_string(port);
|
||||
client_connection out;
|
||||
out.context = make_client_tls_context(sec);
|
||||
asio::ip::tcp::socket sock(io);
|
||||
connect_until(sock, host, port, lim.connect);
|
||||
tune_tcp(sock, pol.socket);
|
||||
const int fd = sock.native_handle();
|
||||
tls_stream s(std::move(sock), *out.context);
|
||||
if (sec.verify && !sec.ca_file.empty())
|
||||
tls_expect_host(s, sec.server_name.empty() ? host : sec.server_name);
|
||||
tls_handshake(io, s, false, lim.handshake, "client: TLS handshake with " + where);
|
||||
out.security = tls_describe(s);
|
||||
try
|
||||
{
|
||||
check_server(out.security, s, sec, where);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::error_code e;
|
||||
s.lowest_layer().close(e);
|
||||
throw;
|
||||
}
|
||||
if (!sec.verify)
|
||||
DPF_LOG(error, "client.verify_off").kv("server", where)
|
||||
.kv("detail", "client_verify=off: any server certificate is accepted, so "
|
||||
"this connection is encrypted but the server is not authenticated");
|
||||
else if (out.security.peer_auth == "development"
|
||||
&& log::first_time("client.development." + where))
|
||||
DPF_LOG(warning, "client.development_certificate").kv("server", where)
|
||||
.kv("detail", "the server presented the built-in development certificate, "
|
||||
"whose private key is public: this connection is encrypted but the "
|
||||
"server is not authenticated (pin its key or configure client_ca)");
|
||||
detail::client_hello(io, s, lanes, false, lim.handshake, "client link to " + where);
|
||||
out.link = std::make_unique<async_tls_mux_stream_array>(io, std::move(s), 1, 0, lanes,
|
||||
pol);
|
||||
log_link_up("connect", "server", transport::mux, lanes, 0, 0, fd, pol.socket,
|
||||
&out.security);
|
||||
return out;
|
||||
#else
|
||||
(void)io;
|
||||
(void)host;
|
||||
(void)port;
|
||||
(void)sec;
|
||||
(void)lanes;
|
||||
(void)pol;
|
||||
(void)lim;
|
||||
throw std::logic_error("connect_server: built without OpenSSL");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// @brief Server side: accept clients, each on its own TLS link.
|
||||
class client_listener
|
||||
{
|
||||
public:
|
||||
client_listener(asio::io_context & io, server_security sec, std::size_t lanes = 1,
|
||||
wire_policy pol = {}, deadlines lim = {})
|
||||
: io_(&io), sec_(std::move(sec)), lanes_(lanes), pol_(pol), lim_(lim)
|
||||
{
|
||||
#if DPF_HAS_OPENSSL
|
||||
ctx_ = make_server_tls_context(sec_, development_);
|
||||
if (development_ && log::first_time("server.development"))
|
||||
DPF_LOG(warning, "server.development_certificate")
|
||||
.kv("detail", "presenting the built-in development certificate, whose "
|
||||
"private key is public: clients cannot tell this server from any "
|
||||
"other (set server_cert/server_key or server_identity)");
|
||||
#else
|
||||
throw std::logic_error("client_listener: built without OpenSSL");
|
||||
#endif
|
||||
}
|
||||
|
||||
/// @brief Bind `port` (0 = ephemeral) and return it.
|
||||
unsigned short listen(unsigned short port = 0)
|
||||
{
|
||||
if (!acceptor_)
|
||||
{
|
||||
acceptor_ = std::make_unique<asio::ip::tcp::acceptor>(*io_);
|
||||
open_listener(*acceptor_, port);
|
||||
log_listen(acceptor_->local_endpoint().port(), false, true);
|
||||
}
|
||||
return acceptor_->local_endpoint().port();
|
||||
}
|
||||
|
||||
bool development() const noexcept { return development_; }
|
||||
|
||||
/// @brief Wait (up to the accept deadline) for one client.
|
||||
client_connection accept()
|
||||
{
|
||||
listen(0);
|
||||
client_connection out;
|
||||
#if DPF_HAS_OPENSSL
|
||||
out.context = ctx_;
|
||||
asio::ip::tcp::socket sock(*io_);
|
||||
accept_until(*acceptor_, sock, lim_.accept);
|
||||
tune_tcp(sock, pol_.socket);
|
||||
const int fd = sock.native_handle();
|
||||
tls_stream s(std::move(sock), *ctx_);
|
||||
tls_handshake(*io_, s, true, lim_.handshake, "server: TLS handshake with a client");
|
||||
out.security = tls_describe(s);
|
||||
check_client(out.security, sec_);
|
||||
detail::client_hello(*io_, s, lanes_, true, lim_.handshake, "client link");
|
||||
out.link = std::make_unique<async_tls_mux_stream_array>(*io_, std::move(s), 0, 1,
|
||||
lanes_, pol_);
|
||||
log_link_up("accept", "client", transport::mux, lanes_, 0, 0, fd, pol_.socket,
|
||||
&out.security);
|
||||
if (!sec_.client_pins.empty() && out.security.peer_auth == "none")
|
||||
DPF_LOG(warning, "server.client_unauthenticated")
|
||||
.kv("client_key", out.security.peer_key ? out.security.peer_key->base64()
|
||||
: std::string("none"))
|
||||
.kv("detail", "the client presented no pinned key");
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
asio::io_context * io_ = nullptr;
|
||||
server_security sec_;
|
||||
std::size_t lanes_ = 1;
|
||||
wire_policy pol_{};
|
||||
deadlines lim_{};
|
||||
bool development_ = false;
|
||||
#if DPF_HAS_OPENSSL
|
||||
std::shared_ptr<tls_context> ctx_;
|
||||
#endif
|
||||
std::unique_ptr<asio::ip::tcp::acceptor> acceptor_;
|
||||
};
|
||||
|
||||
} // namespace net
|
||||
} // namespace dpf
|
||||
|
||||
#endif // LIBDPF_INCLUDE_DPF_NET_CLIENT_LINK_HPP__
|
||||
Loading…
Add table
Add a link
Reference in a new issue