TLS connector can be created without connecting so that TLS options can be set before connecting.

This commit is contained in:
fpagliughi
2025-12-28 23:01:31 -05:00
parent 6cdb47baf8
commit fe4712fb5f
6 changed files with 167 additions and 18 deletions

View File

@@ -5,7 +5,7 @@
// --------------------------------------------------------------------------
// This file is part of the "sockpp" C++ socket library.
//
// Copyright (c) 2023 Frank Pagliughi
// Copyright (c) 2023-2025 Frank Pagliughi
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -81,7 +81,9 @@ int main(int argc, char* argv[]) {
return 1;
}
sockpp::tls_connector conn{ctx, addr, ec};
// Connect with an SNI check
sockpp::tls_connector conn{ctx, addr, host, ec};
if (ec) {
cerr << "Error connecting to server: " << ec.message() << endl;

View File

@@ -2,6 +2,14 @@
//
// Example app showing some options for connecting to a secure server.
//
// This shows:
// - Setting up a TLS client context, w/ options from the command line
// - Althoug these could be done in a single call, this uses separate
// steps to:
// - Resolve the server address
// - Make an insecure connection
// - Make the TLS connector from the insecure TCP connector
//
// --------------------------------------------------------------------------
// This file is part of the "sockpp" C++ socket library.
//
@@ -38,6 +46,7 @@
#include <getopt.h>
#include <chrono>
#include <fstream>
#include <iostream>
#include <string>
@@ -49,24 +58,28 @@
#include "sockpp/version.h"
using namespace std;
using namespace std::chrono_literals;
const string DEFAULT_HOST = "example.org";
const in_port_t DEFAULT_PORT = 443;
// --------------------------------------------------------------------------
int main(int argc, char* argv[]) {
bool verify = false;
string trustStore, certFile, keyFile;
string trustStore, certFile, keyFile, hostName;
int c, iOpt;
static option longOpts[] = {
{"verify", no_argument, 0, 'v'},
{"trust-store", required_argument, 0, 't'},
{"cert", required_argument, 0, 'c'},
{"key", required_argument, 0, 'k'},
{0, 0, 0, 0}
{"verify", no_argument, 0, 'v'}, {"trust-store", required_argument, 0, 't'},
{"cert", required_argument, 0, 'c'}, {"key", required_argument, 0, 'k'},
{"hostname", required_argument, 0, 'h'}, {0, 0, 0, 0}
};
cout << "Sample TLS test connector for 'sockpp' " << sockpp::SOCKPP_VERSION << '\n'
<< endl;
while ((c = getopt_long(argc, argv, "t:c:k:v", longOpts, &iOpt)) != -1) {
while ((c = getopt_long(argc, argv, "t:c:k:h:v", longOpts, &iOpt)) != -1) {
switch (c) {
case 'v':
verify = true;
@@ -84,6 +97,10 @@ int main(int argc, char* argv[]) {
keyFile = string{optarg};
break;
case 'h':
hostName = string(optarg);
break;
default:
cerr << "Unknown option: " << optarg << endl;
return 1;
@@ -92,9 +109,13 @@ int main(int argc, char* argv[]) {
int narg = argc - optind;
string host = (narg > 0) ? argv[optind] : "example.org";
string host = (narg > 0) ? argv[optind] : DEFAULT_HOST;
in_port_t port = (narg > 1) ? atoi(argv[optind + 1]) : 443;
// The default host requires SNI
if (host == DEFAULT_HOST)
hostName = host;
sockpp::initialize();
auto ctxBldr = sockpp::tls_context_builder::client();
@@ -132,13 +153,35 @@ int main(int argc, char* argv[]) {
return 1;
}
sockpp::tls_connector conn{ctx, addr, ec};
cout << "Connecting to server at " << host << ':' << port << endl;
sockpp::tcp_connector tcp_conn{addr, 10s, ec};
if (ec) {
cerr << "Error connecting to server: " << ec.message() << endl;
return 1;
}
cout << "Securing the connection..." << endl;
sockpp::tls_connector conn{ctx};
if (ec) {
cerr << "Error creating the TLS connector: " << ec.message() << endl;
return 1;
}
if (!hostName.empty()) {
cout << "Using SNI host name: " << hostName << "..." << endl;
if (auto res = conn.set_host_name(hostName); !res) {
cerr << "Error: " << res.error_message() << endl;
return 1;
}
}
if (auto res = conn.tls_connect(std::move(tcp_conn)); !res) {
cerr << "Error securing the connection: " << res.error_message() << endl;
return 1;
}
cout << "Successful connection to " << addr << endl;
if (auto opt_cert = conn.peer_certificate(); !opt_cert) {
@@ -161,7 +204,7 @@ int main(int argc, char* argv[]) {
ofstream pemfil("peer.pem");
auto pem = cert.to_pem();
pemfil.write(pem.data(), pem.size());
cout << "\nWrote peer certificate to peer.pem" << endl;
cout << "Wrote peer certificate to peer.pem" << endl;
}
if (auto res = conn.write("HELO"); !res) {
@@ -170,11 +213,15 @@ int main(int argc, char* argv[]) {
return 1;
}
cout << "\nSuccessfully wrote to server." << endl;
/*
char buf[512];
if (auto res = conn.read(buf, sizeof(buf)); !res) {
cerr << "Error: " << res.error_message() << endl;
return 1;
}
*/
return 0;
}

View File

@@ -54,7 +54,21 @@ class tls_connector : public tls_socket
public:
/**
* Creates a TLS connector and connects to the server.
* Creates an unconnected TLS connector.
* @param ctx The TLS context.
* @throws std::system_error If it fails to find the server
* @throws tls_error If it fails to make a secure TLS connection
*/
tls_connector(const tls_context& ctx) : base{ctx} {}
/**
* Creates an unconnected TLS connector.
* Once created, TLS options can be set on the object
* @param ctx The TLS context.
* @param ec Gets the error code on failure
*/
tls_connector(const tls_context& ctx, error_code& ec) noexcept : base{ctx, ec} {}
/**
* Creates a TLS connector and attempts to connect to the server.
* @param ctx The TLS context.
* @param addr The address of the remote server.
* @throws std::system_error If it fails to find the server
@@ -65,14 +79,36 @@ public:
if (auto res = tls_connect(); !res)
throw tls_error{res.error()};
}
/**
* Creates a TLS connector, connects to the server, and checks the SNI
* host name..
* @param ctx The TLS context.
* @param addr The address of the remote server.
* @param hostname The host name for an
* @throws std::system_error If it fails to find the server
* @throws tls_error If it fails to make a secure TLS connection
*/
tls_connector(const tls_context& ctx, const sock_address& addr, string& hostname)
: base{ctx, connector{addr}} {
if (auto res = set_host_name(hostname); !res)
throw tls_error{res.error()};
if (auto res = tls_connect(); !res)
throw tls_error{res.error()};
}
/**
* Creates a TLS connector and connects to the server.
* @param ctx The TLS context.
* @param addr The address of the remote server.
* @param ec Gets the error code on failure
*/
tls_connector(const tls_context& ctx, const sock_address& addr, error_code& ec) noexcept
tls_connector(
const tls_context& ctx, const sock_address& addr, string& hostname, error_code& ec
) noexcept
: base{ctx, connector{addr}, ec} {
if (!ec) {
if (auto res = set_host_name(hostname); !res)
ec = res.error();
}
if (!ec) {
if (auto res = tls_connect(); !res)
ec = res.error();
@@ -114,13 +150,24 @@ public:
}
return *this;
}
/**
* Connect the TLS session.
* This assumes that the underlying, insecure connection has already
* been made, and then this call secures the connection.
* @return The error code on failure.
*/
result<> tls_connect() noexcept { return tls_check_res_none(::SSL_connect(ssl())); }
/**
* Connect the TLS session.
* This assumes that the underlying, insecure, connection has already
* been made.
* @return The error code on failure.
*/
result<> tls_connect() noexcept { return tls_check_res_none(::SSL_connect(ssl())); }
result<> tls_connect(stream_socket&& sock) noexcept {
if (auto res = attach(std::move(sock)); !res)
return res;
return tls_check_res_none(::SSL_connect(ssl()));
}
/**
* Connect the TLS session.
* @return The error code on failure.

View File

@@ -85,6 +85,18 @@ class tls_socket : public stream_socket
tls_socket& operator=(const socket&) = delete;
public:
/**
* Creates a new, unconnected, TLS socket.
* @param ctx The TLS context
* @throws tls_error on failure
*/
tls_socket(const tls_context& ctx);
/**
* Creates a new, unconnected, TLS socket.
* @param ctx The TLS context
* @param ec The error code on failure
*/
tls_socket(const tls_context& ctx, error_code& ec) noexcept;
/**
* Creates a new TLS socket from an existing stream socket.
* @param ctx The TLS context
@@ -122,7 +134,12 @@ public:
* @return A reference to this object.
*/
tls_socket& operator=(tls_socket&& rhs);
/**
* Attach an insecure stream socket to this object.
* @param sock A stream socket
*/
result<> attach(stream_socket&& sock) noexcept;
;
/**
* Returns the peer's X.509 certificate.
*/
@@ -140,6 +157,14 @@ public:
string peer_certificate_status_message();
#endif
/**
* Sets the Server Name Indication (SNI) for use by Secure Sockets
* Layer (SSL).
*
* Call this before the TLS handshake.
*/
result<> set_host_name(const string& hostname);
// I/O primitives
using base::read;

View File

@@ -99,7 +99,8 @@ result<size_t> stream_socket::read(const std::vector<iovec>& ranges) {
#else
std::vector<WSABUF> bufs;
for (const auto& iovec : ranges) {
bufs.push_back({static_cast<ULONG>(iovec.iov_len), static_cast<CHAR*>(iovec.iov_base)}
bufs.push_back(
{static_cast<ULONG>(iovec.iov_len), static_cast<CHAR*>(iovec.iov_base)}
);
}
@@ -159,7 +160,8 @@ result<size_t> stream_socket::write(const std::vector<iovec>& ranges) {
#else
std::vector<WSABUF> bufs;
for (const auto& iovec : ranges) {
bufs.push_back({static_cast<ULONG>(iovec.iov_len), static_cast<CHAR*>(iovec.iov_base)}
bufs.push_back(
{static_cast<ULONG>(iovec.iov_len), static_cast<CHAR*>(iovec.iov_base)}
);
}

View File

@@ -43,6 +43,17 @@ namespace sockpp {
/////////////////////////////////////////////////////////////////////////////
tls_socket::tls_socket(const tls_context& ctx) : ssl_{::SSL_new(ctx.ctx_)} {
if (!ssl_)
throw tls_error::from_last_error();
}
tls_socket::tls_socket(const tls_context& ctx, error_code& ec) noexcept
: ssl_{::SSL_new(ctx.ctx_)} {
if (!ssl_)
ec = tls_last_error();
}
tls_socket::tls_socket(const tls_context& ctx, stream_socket&& sock)
: base{std::move(sock)}, ssl_{::SSL_new(ctx.ctx_)} {
if (!ssl_)
@@ -73,6 +84,17 @@ tls_socket& tls_socket::operator=(tls_socket&& rhs) {
return *this;
}
result<> tls_socket::attach(stream_socket&& sock) noexcept {
// TODO: Implement this
// if (!ssl_)
// throw tls_error::from_last_error();
auto h = sock.release();
base::reset(h);
return tls_check_res_none(::SSL_set_fd(ssl_, h));
}
std::optional<tls_certificate> tls_socket::peer_certificate() {
// TODO: Implement this
X509* cert = SSL_get1_peer_certificate(ssl_);
@@ -97,6 +119,10 @@ string tls_socket::peer_certificate_status_message() {
}
#endif
result<> tls_socket::set_host_name(const string& hostname) {
return tls_check_res_none(::SSL_set_tlsext_host_name(ssl_, hostname.c_str()));
}
result<size_t> tls_socket::read(void* buf, size_t n) {
size_t nx;
int ret = ::SSL_read_ex(ssl_, buf, n, &nx);